2022HFCTF的一道sql题
几天前接触了一下HFCTF,太菜了被打爆了,完全帮不上忙,之后看了一下学长们的解题过程学习了一下
首先就被前端的限制给迷惑了,但是其实可以在后端打,所以这个前端可以不用管
后端js给了源码,sql语句是这样的
给了创建的表,表为auth,username和password是列,那么我们只需要将username和password给查出来就可以了
然后还给了一个正则表达式
一开始能看出来后端过滤掉了union
和binary
,binary
这里不知道是干什么的,这里问了一下学长,才知道这个binary
可以将某个查询后的结果回显以二进制的方式输出
建议是自己在本地环境测试一下这个正则表达式,来知道剩下的过滤了些什么
随便试了一些例子
过滤了*
、-
、空格
、()
、#
,这就麻烦大了,因为连括号都没得用的话就代表不能使用substr
这样的函数来盲注了,而且想用注释符闭合语句的方法不可取,得用其他的方法来闭合语句或者是不闭合(到后面发现其实不用闭合)
之后看了看几个学长们的做法
这里附上Kevin学长提供的脚本,基本盲注思路还是差不多的,重点是研究一下这个payload
1 | import requests |
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列名后面,这样就可以区分出大小写了
官方文档给的解释
utf8mb4
:Unicode 字符集的 UTF-8 编码,每个字符使用一到四个字节
其他的这些_utf8mb4
和utf8mb4_0900_as_cs
都可以起到类似的效果
这里利用了报错注入和布尔盲注
重点说下这里的报错盲注
对0取补码,已经是当前mysql版本的最大数字了,+1会让数据溢出,导致mysql报错,报错了的话,之后的sql语句就不会执行了(其实执行与否都无关紧要,重要的是看是否报错)
报错的情况是500,正常的访问是401,可以利用这两者的差距来判断
if()
这样的条件判断盲注不能用了,可以使用case
,when
配合报错
这个语句有点复杂,我先在本地环境试一下
借此学习了一下mysql,首先发现这个~0+1
是否执行与case
是0(false)
和1(true)
有关(我的mysql不知道为啥用不了utf8mb4_bin
,用utf8mb3_bin
代替一下)
上面的语句是执行了then
后面的~0+1
,下面的语句没有执行then
的语句
我研究了一下case
和when
语句,发现有点像c语言的switch
语句
在执行一个then
的语句之后,后面的then
就不会再执行了
注意:case
,when
,then
的结尾必须加上end
关键词
上面的图当中,当case=1
的时候,若regexp
匹配成功则执行then
之后的语句~0+1
,触发报错和500
当case=0
的时候与之相反
但是事情好像没有这么简单
当我尝试构造一个'1'='0'
的永假式的时候,报错执行成功了,但是当我尝试构造一个'1'='1'
的永真式的时候,它直接无视了我的报错执行
我从中得出了一个结论:这个sql语句用||
好几个部分,其中payload当中的'1'='2
纯粹是为了闭合limit
前面的单引号
其实这样也可以(不过是因为空格被过滤了所以需要多用上一个||
以及一个'1'
来使sql语句本身正确)
接下来确实是爆破出了账号和密码
但是按照上述的脚本跑的话出现了这样的状况
1 | QaY8TeFYzC67aeoO/m52FPlDxYyLB.eIzAr!8gxh. |
理论上来说,密码当中应该没有这2个点,到后面用利用了python中的format
才发现这两个字符是^
和$
1 | import requests |
就是说不用正则表达式了,直接用format
,类似于你在C语言当中想输出一个\(转义字符)
的时候你需要打两个\
才可以
即为^123456$
只能输出123456
,而不能输出^
和$
这两个符号(太坑了)
总结结束!