背景
虽然开源的CRS规则集[1]误报率很高,但是ModSecurity引擎还是很强大的,支持非常多的功能,包括丰富的HTTP解析后的字段。
不过虽然ModSecurity官方文档[2]中对于变量字段有说明,但是有一些细节仍然不清晰,比如 PATH_INFO变量是否经过url解码、存在参数污染时ARGS/ARGS_GET/ARGS_POST变量值是什么、为什么有时候REQUEST_BODY变量是空 等问题,不清楚这些细节容易写出存在漏报或者误报的规则。
本文介绍快速搞清变量的方法、几个重要且常用的变量。
搞清楚变量含义最快的方法
ModSecurity支持lua插件,你可以用SecRuleScript指令和lua脚本来做一些调试。
比如添加如下规则可以打印PATH_INFO变量到终端控制台
SecRuleScript "/tmp/1.lua" "id:23333,deny"
/tmp/1.lua 内容如下
function main()
local inspect = require("inspect") -- inspect库需要额外安装 https://github.com/kikito/inspect.lua
m.log(2,inspect(m.getvars("PATH_INFO"))) -- 将 PATH_INFO 变量打印到标准错误中
return nil;
end
ARGS_GET 和 参数污染
参数污染,简单的理解,也就是访问”http://网址/index.php?a=1&a=2&a=3″,有的web容器解析请求后认为a的值是1,另一些web容器解析后认为a的值是3,这种就是存在参数污染问题。
那么ModSecurity解析参数时,会有参数污染问题吗?
结论是没有,测试过程如下
function main()
local inspect = require("inspect")
m.log(2,inspect(m.getvars("ARGS_GET")))
return nil;
end
curl ‘127.0.0.1:8081?a=1&a=2&a=3’ 后,从下面日志中可以看到,ARGS_GET会存储参数a所有的值,这是符合预期的。
127.0.0.1:8081 是我本地安装了modsecurity的apache服务
Message: { {
name = "ARGS_GET:a",
value = "1"
}, {
name = "ARGS_GET:a",
value = "2"
}, {
name = "ARGS_GET:a",
value = "3"
} }
curl ‘127.0.0.1:8081?a=1&a=2&a=3’ ,可以匹配到如下三条规则,也验证了 ARGS_GET 没有参数污染问题。
SecRule ARGS_GET:a "@rx 1" "id:946811,msg:'TEST1',phase:2,block,capture,severity:'CRITICAL',tag:'attack-rce',tag:'paranoia-level/1',t:none,setvar:tx.anomaly_score_pl1=+%{tx.critical_anomaly_score}"
SecRule ARGS_GET:a "@rx 2" "id:946812,msg:'TEST2',phase:2,block,capture,severity:'CRITICAL',tag:'attack-rce',tag:'paranoia-level/1',t:none,setvar:tx.anomaly_score_pl1=+%{tx.critical_anomaly_score}"
SecRule ARGS_GET:a "@rx 3" "id:946813,msg:'TEST3',phase:2,block,capture,severity:'CRITICAL',tag:'attack-rce',tag:'paranoia-level/1',t:none,setvar:tx.anomaly_score_pl1=+%{tx.critical_anomaly_score}"
PATH_INFO 是否经过url解码等处理?
你觉得下面的自定义规则(禁止外部用户访问 security.xxx.com/login 接口),可以怎么绕过?
规则含义如下:
-
Host 完全匹配 security.xxx.com -
URI 正则匹配 ^/login
上面的问题就不直接给出答案了。和路径相关的变量,你永远需要关注它有没有 url解码、../ 、./、/// 等处理逻辑:
-
/a/../b 会不会变成 /b -
/a/./b 会不会变成 /a/b -
////a 会不会变成 /a
在ModSecurity中 REQUEST_URI、REQUEST_URI_RAW、PATH_INFO 三个常用的变量都和路径有关,v2版本测试结果如下
结论:
-
PATH_INFO 和 REQUEST_URI 会url解码、会处理../ 、./、///等字符 -
REQUEST_URI_RAW不做任何处理
REQUEST_BODY
根据文档所说,默认情况下,只有请求content-type是application/x-www-form-urlencoded时,REQUEST_BODY才会有值。
你也可以用forceRequestBodyVariable强制给REQUEST_BODY赋值,crs规则也是这么干的,如下
文档说REQUEST_BODY存储的是原始的请求body,那遇到chunked请求时,它存的是解码后还是解码前的呢?
Apache、ModSecurity-V2测试结论:是解码后的
总结
变量的细节影响规则质量,而ModSecurity的文档并不一定准确,我们可以用lua插件打印变量来测试。
参考资料
开源的CRS规则集: https://github.com/coreruleset/coreruleset
[2]ModSecurity官方文档: https://github.com/SpiderLabs/ModSecurity/wiki/Reference-Manual-%28v3.x%29
原文始发于微信公众号(leveryd):开源WAF规则运营入门