有关 kong 微服务网关的配置参考以下文档
大部分的 WAF 网关都是通过 openresty 加载 lua 语言的方式实现 http 不同加载阶段的过滤。网上比较常见的开源方案有 ModSecurity 和 Naxsi 和国内额 Loveshell。从项目的活跃程度和其它大厂的策略综合来看还是 ModSecurity 比较靠谱。
由于 ModSecuriy 原本是基于 Apache 的功能开发,后续才增加对 Nginx 的支持,因此在性能方面和原生的 Nginx 有较大的差距。网络上有大神将它的策略内容结合 PCRE 的正则表达式重新添加了 openresty 的支持并且性能上做了较大大优化调整。具体可以参考https://github.com/p0pr0ck5/lua-resty-waf/
将 lua-resty-waf 的功能集成到 kong 服务网关的开源项目参考https://github.com/zhenguang/kong-plugin-lua-resty-waf
yum install luarocks gcc gcc-c++ tree elfutils-devel pcre-devel luarocks install lrexlib-PCRE luarocks install kong-plugin-lua-resty-waf |
CentOS 7的版本中,GCC 的版本较低,如果不更新 GCC 版本,后续 Kong 启动 WAF 插件时会提示出错。如下所示:
2020/06/12 14:54:32 [error] 31539#0: *1587 lua coroutine: runtime error: .../plugins/kong-plugin-lua-resty-waf/resty/waf/load_ac.lua:50: /lib64/libstdc++.so.6: version `CXXABI_1.3.9' not found (required by /usr/local/share/lua/5.1/kong/plugins/kong-plugin-lua-resty-waf/resty/waf/../libac.so) stack traceback: coroutine 0: [C]: in function 'load' .../plugins/kong-plugin-lua-resty-waf/resty/waf/load_ac.lua:50: in function 'load_ac_lib' .../plugins/kong-plugin-lua-resty-waf/resty/waf/load_ac.lua:64: in function 'create_ac' ...lugins/kong-plugin-lua-resty-waf/resty/waf/operators.lua:259: in function <...lugins/kong-plugin-lua-resty-waf/resty/waf/operators.lua:252> |
strings /lib64/libstdc++.so.6 |grep 'CXX' wget ftp://ftp.gnu.org/gnu/gcc/gcc-9.2.0/gcc-9.2.0.tar.gz ../configure --enable-checking=release --enable-languages=c,c++ --disable-multilib make |
编译后将新版本的文件替换旧文件即可。
vim /etc/kong/kong.conf plugins = bundled,http301https,kong-plugin-lua-resty-waf kong restart |
在 konga 的管理界面中找到需要应用的站点路由,然后选择添加插件。
默认的配置策略中 WAF 不会输出拦截的日志,修改“/usr/local/share/lua/5.1/kong/plugins/kong-plugin-lua-resty-waf/resty/waf/waf.lua”中的配置选项将日志输出到本地。
touch /usr/local/kong/logs/waf.log vim /usr/local/share/lua/5.1/kong/plugins/kong-plugin-lua-resty-waf/resty/waf/waf.lua |
_event_log_target = "file", _event_log_target_host = nil, _event_log_target_path = "/usr/local/kong/logs/waf.log", |
默认的日期格式使用 ngx.time() 函数按计数的方式显示日期及时间,可以通过 ngx.localtime() 将格式改为更直观的日期格式。
vim /usr/local/share/lua/5.1/kong/plugins/kong-plugin-lua-resty-waf/resty/waf.lua |
在其它的电脑上构建一条有安全风险的访问 URL 测试下安全策略是否生效。其中 cctv.myj.lan 是目标站点,该站点通过 kong 配置为反向代理。
curl -I 'http://cctv.myj.lan?query=<script></script>' tail -f /usr/local/kong/logs/waf.log |
{"timestamp":"2020-06-16 09:00:00","request_headers":{"host":"cctv.myj.lan","accept":"*\/*","user-agent":"curl\/7.29.0"},"id":"d11e0c9c4a82cd67f833","method":"HEAD","uri":"\/","client":"192.168.111.95","uri_args":{"query":"<script><\/script>"},"alerts":[{"logdata":"Matched Data: <script> found within REQUEST_ARGS: MATCHED_VAR","msg":"Restricted SQL Character Anomaly Detection (args): # of special characters exceeded (2)","id":"942432","match":{"0":"<script>","1":"<script>"}},{"msg":"XSS (Cross-Site Scripting)","id":42059,"match":1},{"msg":"XSS (Cross-Site Scripting) - HTML Tag Handler","id":42069,"match":1},{"msg":"XSS (Cross-Site Scripting) - IE Filter","id":42083,"match":1},{"logdata":12,"msg":"Request score greater than score threshold","id":99001,"match":12}]}
系统配置分为两块系统基本配置和规则配置。
系统默认提供了基础的防护规则集,规则集文件都在 rules/ 文件夹下,默认有九个文件:
默认规则集的执行顺序也是从上到下的,注意文件的命名规律,后续添加自己的规则集的时候最好遵循这种规范。
规则配置的加载方式有三种:
1、系统启动时默认加载
waf.init 函数默认会去 package.path 前缀路径下的目录 rules/ 下加载数组 global_rulesets 指定的 .json 规则集配置文件。 global_rulesets 默认就是包括了 rules/ 目录下的所有文件名,也是基本的系统参数之一,可以通过 waf:set_option("global_rulesets", {}) 来指定。
2、使用 load_secrules 加载
可以调用函数 load_secrules 函数从磁盘加载 ModSecurity SecRules 配置文件,参数就是文件所在的绝对路径。需要注意的是还需要调用函数 add_ruleset 将规则集名(文件名称)注册到系统,否则系统是识别不到的。系统内部会按行将 ModSecurity 规则集文件转换(在 translate 包内)成 waf 对应的规则格式(json)。目前支持四种规则指令:
3、使用 add_ruleset_string 加载
add_ruleset_string 可以直接加载规则集字符串(json)。用户可以使用这种方式动态的加载自定义规则集合。
在每个阶段执行对应的规则集之前,都会先合并(merge)规则集,主要就是根据规则集名,当规则集名称一样时,用户自己添加的规则集优先级更高。
waf.new 函数会初始化一个系统基础参数表,这些参数都可以通过函数 waf.set_option(参数名称, value) 来设置。一些比较重要的参数说明:
规则模块是 WAF 项目的核心,包括解析和执行两个部分,为了支持类似 ModSecurity 的防护规则,规则配置比较复杂,解析和执行逻辑就更复杂了。
规则配置是按规则集(规则数组)的形式被读取的,规则集再分为多个阶段 --- access,header_filter 等。所有的规则集在解析时,会按阶段的维度,添加到对应阶段的规则集数组。需要注意的是,规则解析时会计算两个特殊的变量值:
rule.offset_nomatch 数值,当当前规则匹配失败时,规则遍历迭代器接下来要跳转的规则数,即:当前规则序数 + offset_nomatch = 下条规则的序数
rule.offset_match 数值,当当前规则匹配成功时,规则遍历迭代器接下来要跳转的规则数
这两个变量值一般都是 1,即直接进入相邻的下个规则,但是使用 skip 或 CHAIN 都会改变这些值。他们都被放入 table 对象 rule,供规则执行时使用。
规则是在 waf.exec 函数内执行的,每个阶段只会执行当前阶段对应的规则集,及规则集里面的规则。需要注意的是 CHAIN 规则的执行逻辑,这种类型的规则会组成一个规则链,规则链内的规则是 and 关系,即规则匹配失败,就会跳过当前整个规则链。规则链是怎么组成的呢?当遇到非 CHAIN 规则时,就会计算成一个规则链。
下面使用 C 代表 CHAIN 规则,X 代表非 CHAIN 规则,有如下规则集:
C C C X X C X
将生成两条规则链:
规则命中后,都会将命中(匹配成功)规则日志记录到日志输出缓存数组,除了 CHAIN 类型的规则,也就是说规则链命中后只会记录一条日志。规则日志可以在阶段结束时输出,也可以在请求结束时,汇总一起输出。
日志对外输出方式由 _event_log_target 设置,有三种输出方式:
lua-resty-waf 的防护策略源自 modsecurity 的 CRS 内容。https://www.modsecurity.org/CRS/Documentation/exceptions.html。有关策略的应用及配置参照官网文档。lua-resty-waf 内部应用的是 JSON 规则,项目提供了一个工具用于转换策略。
git clone https://github.com/p0pr0ck5/lua-resty-waf.git mv lua-resty-waf/ /usr/local/ yum install perl-JSON perl-Clone perl-List perl-List-MoreUtils perl-Try-Tiny perl-Exporter-Declare cp /usr/local/lua-resty-waf/tools/Modsec2LRW.pm /usr/lib64/perl5/ echo "SecRule REQUEST_URI /XinXiBu/WebResource.axd \"id:11008, phase:1,log,allow,msg:'WhiteList-Different URL Accessed'\"" | /usr/local/lua-resty-waf/tools/modsec2lua-resty-waf.pl |
{"access":[{"actions":{"disrupt":"ACCEPT"},"id":"11008","msg":"WhiteList-Different URL Accessed","operator":"REFIND","pattern":"/XinXiBu/WebResource.axd","vars":[{"type":"REQUEST_URI"}]}],"body_filter":[],"header_filter":[]} |
通过工具将白名单策略加入到白名单规则库中。
1、URL 的白名单
echo "SecRule REQUEST_URI /XinXiBu/WebResource.axd \"id:11008, phase:1,log,allow,msg:'WhiteList-Different URL Accessed'\"" | /usr/local/lua-resty-waf/tools/modsec2lua-resty-waf.pl |
{"access":[{"actions":{"disrupt":"ACCEPT"},"id":"11008","msg":"WhiteList-Different URL Accessed","operator":"REFIND","pattern":"/XinXiBu/WebResource.axd","vars":[{"type":"REQUEST_URI"}]}],"body_filter":[],"header_filter":[]} |
2、IP 地址的白名单
echo "SecRule REMOTE_ADDR \"@ipMatch 192.168.111.95\" "id:11008,phase:1,log,allow"" | /usr/local/lua-resty-waf/tools/modsec2lua-resty-waf.pl -P |