甲方自研分布式WAF落地全程实录

admin 2020年8月22日18:00:50评论366 views字数 5553阅读18分30秒阅读模式

Web应用防护系统(也称为:网站应用级入侵防御系统。英文:Web Application Firewall,简称:WAF)。利用国际上公认的一种说法:Web应用防火墙是通过执行一系列针对HTTP/HTTPS的安全策略来专门为Web应用提供保护的一款产品。

架构设计

我们的流量第一层先到达高防抗D,做DDOS清洗,然后转发给WAF,由WAF做第二次清洗流控,转发给后端业务LB,整体架构如下,并旁路了分析引擎,弥补了WAF这一块无法做太复杂的计算缺陷,并把分析结果通过接口交给WAF执行。

甲方自研分布式WAF落地全程实录

技术选型

目前,主流的自研WAF实现技术主要是依赖OpenResty技术栈(由中国人章亦春发起),代码部分主要是使用Lua编写,简单的安装如下:

wget https://openresty.org/download/openresty-1.13.6.1.tar.gztar -zxvf openresty-1.13.6.1.tar.gzcd openresty-1.13.6.1/ && ./configure --prefix=/usr/local/openresty --with-pcre-jit --with-http_iconv_module  --with-http_gunzip_module --with-http_auth_request_module  --with-http_stub_status_module   --with-http_gzip_static_module//根据真实需求调整配置项目gmake && gmake install或者make && make install第二步,安装luarocks-3.1.3wget https://luarocks.github.io/luarocks/releases/luarocks-3.1.3.tar.gztar -zxvf luarocks-3.1.3.tar.gzcd luarocks-3.1.3/./configure --prefix=/usr/local/openresty/luajit --with-lua=/usr/local/openresty/luajit/ --lua-suffix=jit  --with-lua-include=/usr/local/openresty/luajit/include/luajit-2.1//根据真实需求调整配置项目make &&make install第三步,安装luasocket/usr/local/openresty/luajit/bin/luarocks install luasocket                                                                                                                                                                                  //根据真实环境调整目录注意:这里有个bug,显示安装成功,其实没有安装成功,通过检查 /usr/local/openresty/luajit/lib/lua/5.1  目录下面,有没有mime  socket 目录来确定是否安装成功,否则再次执行安装步骤三,直到安装成功

动态规则更新

比如,黑白IP的添加,域名URL的拦截封禁,流控CC规则的添加,这些动态的规则要求快速生效,这一块规则是存放在Redis里面的,通过API进行修改添加,WAF定时从Redis里面读取到共享内存中,Lua更新规则部分使用了redis-lua 2.0.5-dev类库和luasocket类库完成, 相关的代码放到init_worker.lua文件中, 如果有什么修改, nginx reload 即可,在 nginx reload 的过程中, master进程不退出,worker 进程陆续退出重启,这里特别注意,不然容易踩坑,比如,init.lua 在 nginx reload 的过后代码不会生效

传统规则引擎

一些安全拦截的规则,主要有GET和POST参数、Header里面的一些字段过滤,文件上传的拦截,是编写在json文件之中,就像下面列子一样,规则在OpenResty启动时候,由init_worker.lua写入共享内存,在 nginx reload 的过程中可完成更新,无缝对接更新规则,拦截过滤部分在access.lua中实现,通过读取共享内存里面的规则,来完成相关恶意参数的正则匹配。

{    "state": "enable",    "rule_id":"scanner_01",    "rule_tag":"scanner",    "rule_name":"scanner_hunter",    "useragent": ["(dirbuster|pangolin|nmap|BBBike|sqlmap|w3af|owasp|Nikto|apachebench)","jios"],    "action": "deny",    "info": "scanner attack"}
local _basedir = config.prod.config_rule_dir_M.rule_table.referer_rule = load_json(_basedir.."referer.json")_M.rule_table.uri_rule = load_json(_basedir.."uri.json")_M.rule_table.header_rule = load_json(_basedir.."header.json")_M.rule_table.useragent_rule = load_json(_basedir.."useragent.json")_M.rule_table.cookie_rule = load_json(_basedir.."cookie.json")_M.rule_table.args_rule = load_json(_basedir.."args.json")_M.rule_table.post_rule = load_json(_basedir.."post.json")rule_dict :safe_set("rule",cjson.encode(_M.rule_table),0)if info then    util.waf_info_log(util.table_to_json(_M.rule_table))    util.waf_info_log(env .. ':loadrule.lua work well')endrule_dict :safe_set("rule_version",1.2,0)

CC算法

CC 模块位于access.lua 文件中,主要逻辑就是,把IP和当前的域名作为一个key写入共享内存,在单位内对该key累加计数,只要超过阀值,就拦截指定时间长度并返回一个拦截的页面,下一次访问的时候就直接拦截。见下面演示代码:

if cc_policy  then        local time = tonumber(util.split_str_table(cc_policy , ",")[1])   -- 单位时间        local times = tonumber(util.split_str_table(cc_policy , ",")[2])  -- 请求次数        local block_time = tonumber(util.split_str_table(cc_policy , ",")[3])  -- 封禁时间        local req, _ = ngx.shared.cc:get("cc_deny_"..host..real_ip)        if req then            _M.log_record("cc_module", 'cc_01', 'cc','cc','cc attack)            util.waf_output(block_template_cc)            end        end        local req_h, _ = ngx.shared.cc:get(host..real_ip)        if req_h then            if req_h >= times then                ngx.shared.cc:set("cc_deny_"..host..real_ip, "1", block_time*60)                _M.log_record("cc_module", 'cc_01', 'cc', 'cc','cc attack')                util.waf_output(block_template_cc)
else ngx.shared.cc:incr(host..real_ip, 1) end else ngx.shared.cc:set(host..real_ip, 1, time) end end

对域名的限流

对域名限流的模块位于access.lua 文件中,主要逻辑就是,把当前的域名作为一个key写入共享内存,在1s内对该key累加计数,把超过阀值的流量用IP标记,拦截指定时间并返回一个拦截的页面,完成流量置换。见下面演示代码:

local flow_max = tonumber(util.split_str_table(flow_rate, ",")[1])    -- qps    local block_time = tonumber(util.split_str_table(flow_rate, ",")[2])  -- 拦截时间    local req = ngx.shared.flow_control:get("flow_deny_"..host..real_ip)    if req then        _M.log_record("flow_module", 'flow_01','flow', 'flow','flow policy')                util.waf_output(block_template_flow)        end    end    local flow_count, _ = ngx.shared.flow_control:get(host)    if flow_count then        if flow_count>= flow_max then            ngx.shared.flow_control:set("flow_deny_"..host..real_ip, "1", block_time*60)            _M.log_record("flow_module", 'flow_01','flow', 'flow','flow policy')
util.waf_output(block_template_flow) else ngx.shared.flow_control:incr(host, 1) end else ngx.shared.flow_control:set(host, 1, 1) endend

对IP的限流

对IP限流的模块位于access.lua 文件中,主要逻辑就是,把IP和当前的域名作为一个key写入共享内存,在1s内对该key累加计数,把超过阀值的流量拦截并返回一个拦截的页面。见下面演示代码:

flow_rate =ngx.shared.flow_ip_rules:get(ip_str_key)   -- 单个IP 1s内最大请求数if flow_rate then    local flow_count, _ = ngx.shared.flow_control:get(host..real_ip)    if flow_count then        if flow_count>= tonumber(flow_rate) then            _M.log_record("flow_module", 'flow_ip_01','flow', 'flow_ip','flow policy')            util.waf_output(block_template_flow)        else            ngx.shared.flow_control:incr(host..real_ip, 1)        end    else        ngx.shared.flow_control:set(host..real_ip, 1, 1)    endend

数据传输

甲方自研分布式WAF落地全程实录

WAF会把拦截记录序列化成json格式,写入log中,而不是直接写入任何数据库,因为这里对性能要求较高,综合考虑采取此方法,然后使用logstash写入kafka再写入es。WAF log输出是用的nginx 的worker 进程执行权限,一般www-data, 保证log输出目录,拥有对应权限,否则无log输出,且不报错。

压测

WAF所在物理机:
14.04.1-Ubuntu IP : 110.110.110.110Kernel Version: 4.2.0-27-genericCPU Type : Intel(R) Xeon(R) CPU E5-2680 v4 @ 2.40GHz * 2Memory Size : 64GNetwork Card : Intel 10-Gigabit X540-AT2 (rev 01) 10G万兆
物理机公网网速:Testing download speed........Download: 588.17 Mbit/sTesting upload speed..........Upload: 332.21 Mbit/s
请求机公网网速:CentOS 6.5 IP:112.112.112.112
Testing download speed...............Download: 500.96 Mbit/sTesting upload speed................Upload: 327.65 Mbit/s

http性能测试工具:wrk

公网测试:

由请求机从公网链路发出请求,贴近真实场景

命令: ./wrk -t8 -c200 -d10s http://110.110.110.110/

这里从压测报告中挑出一个场景,抛砖引玉:

开启waf,upstream转发转发到 server1, server2, server3 ,80端口 (静态页面),黑白ip,各100条,常规域名、常规URL拦截各100条,常规流控100个域名, 常规cc域名100个,其他模块开启(包括get post ua url 拦截模块等)

Requests/sec: 15423.37

Latency:28.59ms

甲方自研分布式WAF落地全程实录

精彩推荐





甲方自研分布式WAF落地全程实录
甲方自研分布式WAF落地全程实录甲方自研分布式WAF落地全程实录

甲方自研分布式WAF落地全程实录甲方自研分布式WAF落地全程实录

甲方自研分布式WAF落地全程实录

甲方自研分布式WAF落地全程实录

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2020年8月22日18:00:50
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   甲方自研分布式WAF落地全程实录http://cn-sec.com/archives/98790.html

发表评论

匿名网友 填写信息