freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Jxwaf安装部署使用方案
2020-07-07 23:05:45
所属地 广东省

安装

直接到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,欢迎看看

如果关注的朋友多的话,说不定会发一些干货上去(叉腰)

# waf # JXWAF
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者