市面上有一些比较知名的开源waf,如雷池 WAF 社区版(https://github.com/chaitin/SafeLine)、ModSecurity(https://github.com/owasp-modsecurity/ModSecurity)、南墙 WAF(https://github.com/Safe3/uuWAF)、宝塔 WAF(https://github.com/aaPanel/BT-WAF)
waf功能
以长亭的雷池WAF为例,在服务器上运行如下命令即可安装
bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release/latest/setup.sh)"
然后访问https://127.0.0.1:9443/(安装的命令行中带有用户名和密码)。想要开启WAF需要先配置防护站点(包括域名或ip,要监听的端口)。WAF相当于一个反向代理,用户访问ip:port,WAF判断这是一个正常的请求还是一个恶意请求,如果是正常请求就将其转发到对应的服务器上。
WAF本身内置了防御的规则和引擎。雷池WAF的一个优势是采用词法和语法分析引擎,这部分之前是一个单独的组件,名为SQLChop。参考官方文章如下。
早期WAF都是采用规则的方式来运行,但是规则容易被绕过,且误报概率也高一些。所以长亭改用了词法和语法分析引擎。该引擎的核心四个模块:1.递归解码 2.词法分析 3.语法分析 4. 综合打分。具体的可以去读一下SQLChop官方的内容。当然,这个只是针对于SQL的,后续发展涵盖了各种Web漏洞类型,然后SQLChop就作为WAF的一部分被合入了。现在源码不再公开,安装WAF时都是拉取远程的docker,但是SQLChop还可以在官网试用:SQLChop
也可以让用户添加自定义规则
用sql攻击的payload访问雷池防护的站点,在雷池waf“防护日志”模块中显示如下。一旦发现是恶意攻击就会将请求拦截。
waf效果测试
上面这个测试是用blazehttp执行的。blazehttp是由长亭开发的WAF效果自动化测试框架,包含3w+的黑白样本。地址如下:GitHub - chaitin/blazehttp: BlazeHTTP 是一款简单易用的 WAF 防护效果测试工具。BlazeHTTP stands as a user-friendly WAF protection efficacy evaluation tool.
blazehttp运行机制
blazehttp是一个WAF自动化测试框架,采用Go语言开发。运行机制如下
1. 判段WAF是否存在
blazehttp运行开始后先对两个路由发送请求,其中一个是正常请求,另一个包含恶意sql。如果目标系统存在waf,会拦截恶意sql,返回403或500等自定义状态码。通过判断两次请求状态码是否一致,如果不一致则认为存在WAF。认为存在WAF后再进行后续操作。
normalStatusCode, err = getNormalStatusCode(target+`/abcdefg/hijklmn/a.html`, mHost)
blockStatusCode, err = getNormalStatusCode(target+`/abcdefg/hijklmn/a.html?1%20AND%201=1%20UNION%20ALL%20SELECT%201,NULL,%27<script>alert("XSS")</script>%27,table_name%20FROM%20information_schema.tables%20WHERE%202>1--/**/;%20EXEC%20xp_cmdshell(%27cat%20../../../etc/passwd%27)#`, mHost)
if normalStatusCode != blockStatusCode {
isWaf = true
}
2. 从目标文件中读取test case
blazehttp允许传入一个g参数,来传入自定义的test case测试用例。如果没有传入,则采用默认的测试用例—位于testcases文件夹下。包含.white和.black两类文件,即白流量和黑流量。根据官方文档,默认的测试用例数量达到3w+
3. 采用多线程的方式读取testcase并发包测试
WAF源码解析
WAF本身也是一种反向代理,可以看一下关于反向代理的前置知识。
https://www.youtube.com/watch?v=MiqrArNSxSM
ngx_lua_waf
项目地址:https://github.com/loveshell/ngx_lua_waf
这是十年前一个早期的开源web应用防火墙的demo,基于OpenResty开发。OpenResty是在Nginx(开源 Web 服务器和反向代理服务器)的基础上集成了Lua。这个逻辑比较简单,比较适合学习入门,整体防御采用正则的规则来匹配。
跟进主文件waf.lua看一下核心实现逻辑。首先获取请求的长度和方法类型(如GET、POST)。然后进行一些黑白名单的判断,包含对IP、URL、UA、参数、Cookie 等检查。如果发现了请求是来源自扫描器,立即终止请求,返回状态码 444(Nginx 用于关闭连接的特殊状态码)。
if whiteip() then -- 127.0.0.1
elseif blockip() then -- 1.0.0.1
elseif denycc() then -- 判断Challenge Collapsar(CC攻击)防护是否生效
elseif ngx.var.http_Acunetix_Aspect then -- 检查了请求中是否包含特定的扫描器标识头(如 Acunetix_Aspect 和 X_Scan_Memo)
ngx.exit(444)
elseif ngx.var.http_X_Scan_Memo then
ngx.exit(444)
elseif whiteurl() then
elseif ua() then
elseif url() then
elseif args() then
elseif cookie() then
elseif PostCheck then
针对上面这些url、args、cookie等判断,ngx_lua_waf内置了相应的wafconf规则。例如url规则如下
\.(svn|htaccess|bash_history)
\.(bak|inc|old|mdb|sql|backup|java|class)$
(vhost|bbs|host|wwwroot|www|site|root|hytop|flashfxp).*\.rar
(phpmyadmin|jmx-console|jmxinvokerservlet)
java\.lang
/(attachments|upimg|images|css|uploadfiles|html|uploads|templets|static|template|data|inc|forumdata|upload|includes|cache|avatar)/(\\w+).(php|jsp)
args规则如下。一旦匹配到这些字段,则认为存在恶意攻击。
\.\./
\:\$
\$\{
select.+(from|limit)
(?:(union(.*?)select))
having|rongjitest
sleep\((\s*)(\d*)(\s*)\)
benchmark\((.*)\,(.*)\)
base64_decode\(
(?:from\W+information_schema\W)
(?:(?:current_)user|database|schema|connection_id)\s*\(
(?:etc\/\W*passwd)
into(\s+)+(?:dump|out)file\s*
group\s+by.+\(
xwork.MethodAccessor
(?:define|eval|file_get_contents|include|require|require_once|shell_exec|phpinfo|system|passthru|preg_\w+|execute|echo|print|print_r|var_dump|(fp)open|alert|showmodaldialog)\(
xwork\.MethodAccessor
(gopher|doc|php|glob|file|phar|zlib|ftp|ldap|dict|ogg|data)\:\/
java\.lang
\$_(GET|post|cookie|files|session|env|phplib|GLOBALS|SERVER)\[
\<(iframe|script|body|img|layer|div|meta|style|base|object|input)
(onmouseover|onerror|onload)\=
恶意文件上传判断逻辑。首先判断头部的content-type中是否包含boundary。PS:在 multipart/form-data
请求中,boundary
用于区分每个表单字段(如文件、文本等),如果包含就获取请求内容。对请求体内容进行正则判断,是否包含Content-Disposition,如果包含获取filename的文件扩展名,并检查扩展名是否为php或jsp。
black_fileExt={"php","jsp"}
local m = ngxmatch(data,[[Content-Disposition: form-data;(.+)filename="(.+)\\.(.*)"]],'ijo')
if m then
fileExtCheck(m[3])
filetranslate = true
这个项目没有去实现页面、日志分析等展示功能,也有一些二开项目基于这个进行了扩展。
有一些WAF采用Snort规则,Snort规则和引擎后面有机会再写。