0x00 前言
通常来说,在进行字符型的SQL注入时,都需要先将前面的引号等(以单引号为例)进行闭合才能执行我们构造的SQL语句,那么如果单引号被过滤了,是否还能够成功的SQL注入呢?
答案是可以,当你在判断登录时使用的是如下SQL语句:
1 | select user from user where user='$_POST[username]' and password='$_POST[password]'; |
那么即使在过滤的时候将单引号过滤掉了,还是可以进行一定程度上的注入的,下面通过两道CTF题来进行介绍。
0x01 [BJDCTF 2020]简单注入
进入题目后就是一个登录框,名字也提示了要注入:
扫描目录发现hint.txt中的提示:

告诉了我们登录的sql语句:
1 | select * from users where username='$_POST["username"]' and password='$_POST["password"]'; |
稍微fuzz一下可以发现过滤了'、"、=、-、and、select等关键词,其中关键就是这个单引号,由hint已知这里是需要单引号进行闭合的,而单引号又被过滤了。
可以发现注释符#和反斜杠\并没有被过滤,既然注释符没有过滤,那么我们就可以注释掉最后一个单引号,这样就只剩下3个单引号需要处理。
而如果我们输入的用户名以反斜杠\结尾,例如POST:username=admin\&password=123456#,那么拼接进去后,\就可以将第2个单引号转义掉,如下:
1 | select * from users where username='admin\' and password='123456#'; |
这样第1个单引号就会找第3个单引号进行闭合,后台接收到的username实际上是admin\' and password=这个整体,而这个用户名显然不存在,所以还是登录失败。
但是别忘了我们还有一个password变量可控,实际上我们已经解决了单引号的闭合问题了,下面的就是常规的思路,比如我们构造password为 or 2>1#
那么拼接后的sql语句就为:
1 | select * from users where username='admin\' and password=' or 2>1#'; |
很显然上面的语句会返回为真,通过这样的思路,我们就可以进行bool盲注:


最终脚本如下:
1 | import requests |
运行脚本得到admin的密码:OhyOuFOuNdit,登录即可得到flag。
0x02 某道CTF入群题
同样也是一到登陆题,在check.php中给出了源码如下:
1 |
|
可以看到这一题的sql语句同样是下面这样:
1 | select user from user where user='$_POST[username]' and passwd='$_POST[passwd]' |
只不过过滤了更多的东西,下面思考如果进行绕过:
or可以用||进行绕过。- 用
%00绕过注释符#、--的过滤
(虽然%00在过滤列表中,但由于浏览器在传给php过程中会经过一次urldecode(),所以php接收到的并不是%00) - 用
/**/绕过空格的过滤。 - 如果我们只需要得到
user表下password字段的值,因为是在同一条语句中,所以select等查询语句的过滤对我们没有影响。 - 反斜杠
\没被过滤,思路还是利用转义单引号来构造闭合,然后再在password字段构造盲注语句进行注入。
现在就是如何构造盲注语句,常用的=、>、<、like等逻辑运算都被过滤掉了,这时候就需要用到REGEXP正则注入。
在MySQL中除了可以使用LIKE ..%进行模糊匹配,同样也支持正则表达式的匹配,其使用REGEXP 操作符来进行正则表达式匹配。
正则表达式的规则就类似于PHP 或 Perl,下表中的正则模式可应用于 REGEXP 操作符中:
查找以Le开头的用户名:
查找以ck结尾的用户名:
回到题目中来,我们尝试将按照题目的sql逻辑构造如下语句:、

发现是能够进行真假判断的,如果第一个字母猜对了,我们只需要接着判断前两位的开头是否正确即可,然后以此类推,直到猜出全部字段的值:

在题目中进行一下验证,利用Intruder模块爆破一下:

可以看到在以d和D开头时都返回了真,是因为使用regexp正则匹配时是不区分大小写,通常来说只需要加上binary即可解决这个问题,如||binart/**/passwd/**/regexp/**/"^a";%00,但是这一题过滤了in,所以目前我还没有想到好的方法进行大小写匹配。
但实际上这一题的密码都是由小写组成的,否则就只能去遍历所有情况进行爆破了。
最终的脚本如下:
1 | import requests |
运行脚本得到密码:d0itr1ght
得到密码后,后面的就比较简单了,admin用adm/**/in绕过,然后用Referer利用file://localhost/..伪造本地读文件即可:
源码中得到flag: