安装
直接到github下载
[root@localhost tmp]# cd /tmp [root@localhost tmp]# git clone https://github.com/jx-sec/jxwaf.git [root@localhost tmp]# cd jxwaf/ [root@localhost jxwaf]# chmod +x install_waf.sh [root@localhost jxwaf]# ./install_waf.sh
最后安装完成会显示成这样。
下载靶机测试
docker pull docker.io/citizenstig/nowasp
下载jxwaf配置中心
docker pull jxwaf/jxwaf-server:latest
配置
首先先把配置中心启动起来
[root@localhost jxwaf]# docker run -d -p 8000:80 jxwaf/jxwaf-server:latest 7666b1d2a235c57f13bb243309d7a30afd03684dcdd1365be0dca95b70588936
浏览器打开8000端口进去
点击注册,输入邮箱,
邮箱验证码随便填注册账号之后,登录进去
来到全局配置这里,复制api key和api password
由于我没有DNS解析域名,就本地hosts一下
[root@localhost jxwaf]# cat /etc/hosts 192.168.0.4 test.waf.com
默认配置文件,需要修改前面四个地方,waf_api_key,waf_api_password配置中心里面有,waf_update_website,waf_monitor_website,不连上官网,修改本地地址
[root@localhost jxwaf]# cat /opt/jxwaf/nginx/conf/jxwaf/jxwaf_config.json { "waf_api_key": "5fc335bc-d778-4d90-ab3f-ece36bad4a24", "waf_api_password": "6bace0ac-ddca-412f-b768-81407044ea0c", "waf_update_website": "http://update2.jxwaf.com/waf_update", "waf_monitor_website": "http://update2.jxwaf.com/waf_monitor", "waf_local":"false", "server_info":"::1|localhost.localdomain", "waf_node_monitor":"true", "aes_enc_key":"9f935f193570023e", "aes_enc_iv":"91ef2b87d7f8403a", "aliyun_access_id":"", "aliyun_access_secret":"" }
变成如下
{ "waf_api_key": "af1c085c-015a-4604-b627-964db6bcaaa5", "waf_api_password": "50eb815c-2eab-4aa7-a2ab-634760cb97a1", "waf_update_website": "http://192.168.0.4:8000/waf_update", "waf_monitor_website": "http://192.168.0.4:8000/waf_monitor", "waf_local":"false", "server_info":"::1|localhost.localdomain", "waf_node_monitor":"true", "aes_enc_key":"9f935f193570023e", "aes_enc_iv":"91ef2b87d7f8403a", "aliyun_access_id":"", "aliyun_access_secret":"" }
启动靶机
[root@localhost jxwaf]# docker run -d -p 8080:80 citizenstig/nowasp 0981bbeae7fc094917872b274867d737fa4c33e1465ac32f9c501cd22693d
重置靶机
http配置
回到网站配置,添加域名信息,后端端口就是靶机的端口
启动WAF
[root@localhost jxwaf]# /opt/jxwaf/nginx/sbin/nginx -t nginx: the configuration file /opt/jxwaf/nginx/conf/nginx.conf syntax is ok nginx: configuration file /opt/jxwaf/nginx/conf/nginx.conf test is successful [root@localhost jxwaf]# /opt/jxwaf/nginx/sbin/nginx nginx: [alert] [lua] waf.lua:580: init(): jxwaf init success,waf node uuid is e0bd7f61-75e9-4102-a4a5-44f9d480c9b9
在浏览器打开http://test.waf.com/,说明已经配置完成了
https配置
私钥文件、公钥文件
[root@localhost ~]# cat server.key
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAseoZimF8BJOFbyrKaZ8hrD+1L6kiYHcvwKG/ItBEdfFAbYD8
IzwXtsli9Qzz3X5Vl/FFMAow3hWSE6UhaFUVqT3GDOhK6hG53DQHI1pjHv4QSOA1
3GUM2JXyjG6WuJArcIhqzoweiJtU2xBvSJTZ/+SfEnIb2FJCspjzpD06eOGgwZMM
igdN6n72lWV7015B/gsUEeIXjq49Tb+LIXuj3A3bw/cM7CfoGziBAExEuSI80ZCZ
fOUJXbJJm8QCMUNPyfr5nCtf0CH+aU4gMEPdgzRcl1RVR1r4GPLDx4jawXrdLveL
6T5wSlg/RcXZFlUeQ+h93sCcafNX7g1nNBSlUQIDAQABAoIBAG3k/Qu19WXaPYSS
ON8O9TyxSVh8L4jIdg2VmzuEy5TShQpert+QwdEdCev1qTh6TaKB3Eu1L8QuLMHH
sSAB1lRonMniPkvg0R4MYRBcR3egVSy+mWZeYJXz4RMPSDgOjVaAXQDiGgYldD+w
Ih0CHLnsXLmHFF4FSb+JrI0ZaOG67Lm/wK5Y42hsV0zrgV7zaSMFasEmWacoqaQW
AEyFR144231AlvnwvH0gNf7TMkTxAAI/LEOLsSC9Cctbx9rY0WZEZMJR1dSolFwc
xMBPtAuqXxrc4narAmP6COk7bXiORUanbOGFs8RQUEZjRTW0ZMxTYQDn68q5W1Ft
0YC/KXECgYEA3c/FBfd3u4wk4OfnbqlqEl+U2ndMjz8Yq09ZouFY5Fv3XtrBV/1D
rNsEi6T5QnWS39glpYhhnUYvPYqpvD6VjQHyNdkqhGEOqFZktA1o32kU9DDjMCCU
jNK1AaJ4/TsfAdWaOwdXhQQnAeDb07D25L4IkfXo3AcfTtEFLAzmu4UCgYEAzVY9
IoR2DMELjJq2IwEB8+SQz/Vm2avxkO00nAzGdpnYCM1vOl8f45BPdeWvUaqmwxrG
huM+MIyRNztK29wSm38WtxWCGu35yY676OakZuZ46YVsT8I/GuJlXbMgocuR3uwu
7sRAa441gtbkxlVqTN6klo2m3oc7/Q4TPDSgTl0CgYA8dFJYq/gAL9QlUE9tg9Mb
Kt3hJT7ClAnfNwNRN2YI51/mhGzJ1IdLZ243uUEOcgkT5U9tbFxehzB873wPiGcu
RWeEcan65pEeJF3SDQ2WRoelfmWNSnPyZcNbrLKZIjHzSAp/KCMcZ+NRyb1gVw0T
jw+66HEM9wv7aVCljuacGQKBgGJcp1h7n5koeIHYMtu9xdOxb/VOlwA6r7M/De6a
6A80TxqYXmnV247FOGs/paY3Wz8m+mbvQIE9NOsCSi/b0kYOsTDu6q4/xWJaL4W3
xpVMXitvMJ1cbaJRRUGHZ2BaBfyFo03ZUQq0yslsa5ben9dG6Az+uirrGT91mJ1E
kG45AoGBAI4yrvdFFKcoNjpptk8jhmiVls7rTT7aue5YhSBEyw8UU/OO9oy3PxC2
4ZHokewhxF0vZj2pqTHJML26HB4kxSPJjfghLZK4U5uB5NxJbzAM2olDF1q0MZtL
xZLYbyNuwDFyE7y8pg2Dl6bi5mnnt1ruJcbuGxtDUGN7r99B5zWV
-----END RSA PRIVATE KEY-----
#公钥 [root@localhost ~]# cat server.pem -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAseoZimF8BJOFbyrKaZ8h rD+1L6kiYHcvwKG/ItBEdfFAbYD8IzwXtsli9Qzz3X5Vl/FFMAow3hWSE6UhaFUV qT3GDOhK6hG53DQHI1pjHv4QSOA13GUM2JXyjG6WuJArcIhqzoweiJtU2xBvSJTZ /+SfEnIb2FJCspjzpD06eOGgwZMMigdN6n72lWV7015B/gsUEeIXjq49Tb+LIXuj 3A3bw/cM7CfoGziBAExEuSI80ZCZfOUJXbJJm8QCMUNPyfr5nCtf0CH+aU4gMEPd gzRcl1RVR1r4GPLDx4jawXrdLveL6T5wSlg/RcXZFlUeQ+h93sCcafNX7g1nNBSl UQIDAQAB -----END PUBLIC KEY-----
在配置中心进行配置
高级设置的aes加密不关注,因为配置中心已经放到本地,不是放在云上,公钥私钥都不会被外界知道。
后端服务器协议是指后端服务器是http协议还是https协议,一般来说都是http协议,除非有特殊要求。
日志配置
jxwaf支持三种日志方式
- 本地记录
- 远程日志
- 阿里云日志
本地日志,就是error.log,作者使用ngx.err记录访问日志
远程日志,支持TCP和UDP协议
建议使用UDP协议,我使用logstash output file+tcp的时候,因为logstash和nginx会建立长连接,不中断的话,数据写不进去,但是作者说他可以,你们也可以试试。而且UDP+logstash output file可能会抽风,日志没有写到本地文件,所以我的配置文件是直接导出到本地和sls那里。推荐使用logstash转发,WAF将日志发送到logstash,我这边保存到本地,有需要的朋友,可以转发到ES或者阿里云上面。
vim /etc/logstash/conf.d/logstash.conf input {
#这里udp可以变成tcp udp { type => "waf" port => "5555" codec => "json" } } output {
file { path => "/data/jxwaf.json" } }
转发到阿里云日志服务上面的话,可以使用logstash output。
直接无法安装插件,需要修改gem源
#查看源,将默认外网源干掉,变成中国源
gem sources -l gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/
#安装bundler,配置中国源
gem install bundler
bundle config mirror.https://rubygems.org https://gems.ruby-china.com/
vim /usr/share/logstash/Gemfile
找到source变量修改成 https://gems.ruby-china.com/
#安装插件
/usr/share/logstash/bin/logstash-plugin install logstash-output-logservice
复制一下sls配置,endpoint在这里
output { logservice { codec => "json" endpoint => "***" project => "***" logstore => "***" topic => "" source => "" access_key_id => "***" access_key_secret => "***" max_send_retry => 10 } }
配置sls索引,需要添加owasp_type这个字段,默认是没有的,不然后续做不了攻击类型统计
就可以在sls看到
阿里云日志服务(强烈不推荐)
为什么不推荐这种呢?实测直接导致性能损失4-5倍,性能直接就炸了。
使用
说用说明
所有的防护配置都在这里面
防护配置有很多种,有以下这些,我这边一个一个说
Web应用攻击防护,默认防护owasp top10的攻击,关于虚拟补丁防护是防护一些经典漏洞payload,webshell防护、敏感文件防泄漏,字面意思。
下面日志配置就是发送的日志类型,异常请求日志(后续会机器学习用到),攻击请求日志是我们关注的。
CC攻击智能防护,实际测试发现传统CC防护模式,作者默认设置太低,会导致拦截,需要结合自己本身业务进行确认。而智能人机识别是弹出一段图形验证码,确认是真人访问。
自定义规则防护,可以匹配相关内容,做出对应操作。
IP黑白名单,字面意思。
地区封禁,字面意思。
拦截页面自定义,配置返回拦截页面的信息。
攻击测试
注入拦截
xss拦截
遍历拦截
其他就差不多,不展示了。
传统CC拦截,开启后记得重启nginx
不断刷新浏览器就会出现拦截
清除黑名单记录,有两种,nginx -s stop和官方API调用
智能CC
如果要用智能的话,智能的qps要低于传统,不然会优先执行传统,如上情况。所以为了演示,强制智能
一直刷新浏览器,就会出现
自定义规则
执行动作可以拦截和忽略,具体自己看
匹配上了,直接拦截
IP黑白名单
连接被重置拦截
地区封禁
自己选
拦截页面自定义自学习防护
作者没有放出来,pass
性能测试
WAF配置(双核8G),使用locust进行跑,测试效果反而是上了jxwaf之后,性能上升了(经过多次测试)。后面更高并发后jxwaf性能会稍微下降一点,损耗不多。
场景 | WAF性能损耗对比 | |||||
---|---|---|---|---|---|---|
性能指标 测试目标 |
并发 | 压测slave个数 | QPS |
请求成功率 (%) |
平均响应时间 (ms) |
最大响应时间 (ms) |
单台WAF->四台服务器 | 100 | 30 |
205.51 |
100.0 | 383 | 5305 |
四台服务器 | 100 | 30 |
201.66 |
99.999 | 392 | 5335 |
二次开发
一开始没有发现sls有日志脱敏功能,所以基于jxwaf的架构,二次开发了一个日志脱敏功能
日志处理阶段文件:/opt/jxwaf/lualib/resty/jxwaf/log.lua
正常来说,为了数据安全,不记录body,所以我们只关注waf本身记录body这个地方,只需将传进来body参数解析脱敏就返回来。
代码如下,desensitization函数,dv就是代表要脱敏的参数,后续作者会做成自定义规则,只不过他懒没空写。。我就自己写了
local cjson = require "cjson.safe" local function split(str, dv) local resultStrList = {} local ok, e = pcall(function() string.gsub(str, '[^&]+', function(w) table.insert(resultStrList, w) end) end) if not ok then return str end local rs = {} for _k, _v in pairs(resultStrList) do local i b = string.gsub(_v, '[^=]+', function(w) if i == nil then rs[w] = nil else rs[i] = w end i = w end) end local r = {} for _key, _value in pairs(rs) do for _, _d in pairs(dv) do if _key == _d then _value = "****" end r[_key] = _value end end local _t = "" for _k, _v in pairs(r) do _t = _t .. _k .. "=" .. _v .. "&" end if _t == "" then return str end return _t end function serialize(obj) local lua = "" local t = type(obj) if t == "number" then lua = lua .. obj elseif t == "boolean" then lua = lua .. tostring(obj) elseif t == "string" then lua = lua .. string.format("%q", obj) elseif t == "table" then lua = lua .. "{" for k, v in pairs(obj) do lua = lua .. serialize(k) .. ":" .. serialize(v) .. "," end local metatable = getmetatable(obj) if metatable ~= nil and type(metatable.__index) == "table" then for k, v in pairs(metatable.__index) do lua = lua .. serialize(k) .. ":" .. serialize(v) .. "," end end lua = lua .. "}" elseif t == "nil" then return nil else error("can not serialize a " .. t .. " type.") end return lua end local function decodetable(t, dv) local _t = t or {} for _k, _v in pairs(t) do for _key, _value in pairs(dv) do if _value == _k then _v = "***" end end if type(_v) ~= "table" then _t[_k] = _v return _t else decodetable(_v, dv) end end end local function desensitization(body) local dv = { "password" ,"order_id"} local rs = rs or {} local json_body, err = cjson.decode(body) if json_body ~= nil then for _k, _v in pairs(json_body) do for _, value in pairs(dv) do if value == _k then _v = "***" elseif type(_v) == 'table' then dt = decodetable(_v, dv) rs[_k] = dt end rs[_k] = _v end end _tmp = serialize(rs) return _tmp else _t = split(body, dv) return _t end end
本地测试
我之前在微信公众号写了,阿里云仪表盘的制作SQL,欢迎看看
如果关注的朋友多的话,说不定会发一些干货上去(叉腰)
来源:freebuf.com 2020-07-07 23:05:45 by: 陌度
请登录后发表评论
注册