前言

延时注入无论是在实战中还是在CTF中还是比较常见的,下面我总结一下延时注入的一些套路

利用方法

sleep

最简单直接准确的方式是用sleep

1
2
3
4
5
6
7
mysql> select sleep(5);
+----------+
| sleep(5) |
+----------+
| 0 |
+----------+
1 row in set (5.00 sec)

benchmark

通过执行多次表达式,以达到延时的效果。

1
2
3
4
5
6
7
mysql> select benchmark(10000000,md5(1));
+----------------------------+
| benchmark(10000000,md5(1)) |
+----------------------------+
| 0 |
+----------------------------+
1 row in set (2.20 sec)

reDos

利用正则表达式的回溯机制

正则表达式的两种引擎

正则表达式的常见引擎可以大致分为两种:

  1. DFA(确定型有穷自动机)
  2. NFA(非确定型有穷自动机)

特点

1
2
DFA的特点是只对字符串扫描一次,并且会保证返回一个目标字符串里面的最长匹配,不支持字符串进行回溯
NFA最大的特点是回溯机制,会对字符串进行多次扫描匹配

他们匹配的过程为:

  • DFA:用字符串去匹配正表达式,从开始状态一个一个去匹配字符,如果字符匹配正确就会吞入这字符,如果匹配不上则会吐出这个字符,然后继续往下匹配,直到正则表达式全部匹配完成或者匹配失败
  • NFA:用正则表达式去匹配字符,每吞入一个字符就会和正则表达式做比较,如果匹配正确就会继续往下匹配,如果匹配失败,则会进行回溯,尝试其它状态,直到正则表达式全部匹配完成或者匹配失败

由于NFA存在回溯机制,所以NFA效率会低于DFA,但是NFA的功能却比DFA的多,所以现在大多数编程语言中都是使用NFA作为正则引擎。

例子

我们用一个这样子的正则表达式<a>(.*)</a>去匹配<a>testtesttest</a>,这个正表达式一上来就匹配到了<a>,到后面由于(.*)(贪婪模式)的存在,正则引擎会着急地吞掉后面的所有字符,不过由于该正则表达式还有要匹配的内容即</a>,所以正则表达式会尝试匹配<,它在字符串末尾匹配不成功,所以它每次回溯一个字符,继续尝试匹配<,直到匹配到</a><,然后它再继续匹配/,以此类推下去。

图为正则表达式的执行过程,可以明显发现该正则表达式回溯了4次。

如果正则表达式的回溯次数过多时候,就会占用服务器大量的资源从而造成reDos的情况。

小试一波

———————————————————–分割线——————————————————————————————–

2019/6/28更新,我之前一直以为mysql的reDos是因为它的正则引擎是nfa,但是通过查了一波资料和本地操作,发现并不是这样子的,mysql并没有回溯,而是由于填充了大量的字符和大量的pattern,导致mysql消耗大量的资源从而出现延时的情况,我们用phpmysql对比一下就知道了,用同样的正则表达式,php延时了,而mysql并没有延时。

image.png
image.png

image.png
image.png

———————————————————–分割线——————————————————————————————–
那我们在mysql中尝试一波吧。

1
2
3
4
5
6
7
mysql> select rpad('a',10000000,'a') regexp concat(repeat('(a.*)+',1000),'b');
+-----------------------------------------------------------------+
| rpad('a',10000000,'a') regexp concat(repeat('(a.*)+',1000),'b') |
+-----------------------------------------------------------------+
| NULL |
+-----------------------------------------------------------------+
1 row in set, 1 warning (0.00 sec)

但是我们发现并没有延时,细心的同学可能会发现那个有一个warning,我们把警告打开一下看看是哪里出问题了。

1
2
3
4
5
6
7
8
9
10
11
mysql> \W
Show warnings enabled.
mysql> select rpad('a',10000000,'a') regexp concat(repeat('(a.*)+',1000),'b');
+-----------------------------------------------------------------+
| rpad('a',10000000,'a') regexp concat(repeat('(a.*)+',1000),'b') |
+-----------------------------------------------------------------+
| NULL |
+-----------------------------------------------------------------+
1 row in set, 1 warning (0.00 sec)

Warning (Code 1301): Result of rpad() was larger than max_allowed_packet (1048576) - truncated

发现是mysql设置允许接受的最大数据包大小是1M,而我们上面填充的字符明显超过了1M,所以会出现一个warning

1
2
3
4
5
6
7
mysql> show VARIABLES like 'max_allowed_packet';
+--------------------+---------+
| Variable_name | Value |
+--------------------+---------+
| max_allowed_packet | 1048576 |
+--------------------+---------+
1 row in set, 1 warning (0.00 sec)

然后我们我要匹配把字符改小一点尝试一波,可以明显地发现延时了3.29秒

1
2
3
4
5
6
7
mysql> select rpad('a',100000,'a') regexp concat(repeat('(a.*)+',1000),'b');
+---------------------------------------------------------------+
| rpad('a',100000,'a') regexp concat(repeat('(a.*)+',1000),'b') |
+---------------------------------------------------------------+
| 0 |
+---------------------------------------------------------------+
1 row in set (3.29 sec)

或者我们也可以把max_allowed_packet改大一点

1
2
3
4
5
6
7
8
9
10
mysql> set global max_allowed_packet = 20*1024*1024;
Query OK, 0 rows affected (0.00 sec)
...中间省略...
mysql> select rpad('a',1000000,'a') regexp concat(repeat('(a.*)+',1000),'b');
+----------------------------------------------------------------+
| rpad('a',1000000,'a') regexp concat(repeat('(a.*)+',1000),'b') |
+----------------------------------------------------------------+
| 0 |
+----------------------------------------------------------------+
1 row in set (31.99 sec)

成功延时了31秒。

笛卡尔积

前置知识

笛卡儿积(来自百度百科)

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

举个例子

1
假设集合A={a, b},集合B={0, 1, 2},那么这两个集合的笛卡尔积为{(a, 0), (a, 1), (a, 2), (b, 0), (b, 1), (b, 2)}。

在mysql中如果多表查询使用不恰当时就会出现笛卡儿积现象,在多表查询中如果两张或者多张表的数据量都比较大的话,就会占用很大空间资源从而出现延时的情况。

小试一波

既然我们已经了解笛卡尔积会造成占用大量资源的情况,那我们现在mysql来尝试一波吧。

1
2
3
4
5
6
7
mysql> select count(*) from information_schema.tables A, information_schema.tables B,information_schema.tables C;
+----------+
| count(*) |
+----------+
| 80621568 |
+----------+
1 row in set (3.39 sec)

可以很明显地看到延时了3s,我们还可以延时更久

1
2
3
4
5
6
7
mysql> select count(*) from information_schema.tables A, information_schema.tables B,information_schema.columns C;
+-----------+
| count(*) |
+-----------+
| 891689472 |
+-----------+
1 row in set (36.99 sec)

利用mysql的共享锁

get_lock

先来看一波get_lock的语法

1
get_lock(key, timeout)

get_lock会按照key来加锁,当其它的客户端(session)再以同样的key加锁时就会处于等待状态,如果在等待期间(timeout)内获取到锁,则返回1;如果在timeout时间后获取不到锁,则返回0;如果发生错误,则返回一个NULL

mysql_pconnect

不过这种方法比较局限,在php中它必须用mysql_pconnect()函数连接数据库才可以

mysql_pconnect的定义(来自w3cschool

1
mysql_pconnect() 函数打开一个到 MySQL 服务器的持久连接。

mysql_pconnect()mysql_connect()非常相似,但有两个主要区别:

1
2
1. 当连接的时候本函数将先尝试寻找一个在同一个主机上用同样的用户名和密码已经打开的(持久)连接,如果找到,则返回此连接标识而不打开新连接。
2. 其次,当脚本执行完毕后到 SQL 服务器的连接不会被关闭,此连接将保持打开以备以后使用(mysql_close() 不会关闭由 mysql_pconnect() 建立的连接)。

小试一波

session A

1
2
3
4
5
6
7
mysql> select get_lock("test",5);
+--------------------+
| get_lock("test",5) |
+--------------------+
| 1 |
+--------------------+
1 row in set (0.00 sec)

session B

1
2
3
4
5
6
7
mysql> select get_lock("test",5);
+--------------------+
| get_lock("test",5) |
+--------------------+
| 0 |
+--------------------+
1 row in set (5.00 sec)

image.png
image.png

可以明显地看到另外一个session延时了5秒。

Reference

https://www.jianshu.com/p/458c246d79d4
http://www.w3school.com.cn/php/func_mysql_pconnect.asp
https://www.cnblogs.com/cobbliu/p/3817370.html