美文网首页
Openresty 之 waf 功能

Openresty 之 waf 功能

作者: 跑腿打杂临时工 | 来源:发表于2021-12-17 17:44 被阅读0次

    需求描述

    公司业务调整,需要对个别新域名进行简单的 CC 安全防护,需要测试 openresty 中的 waf 模块做 CC 防护。

    部署测试 waf

    安装 openresty

    首先通过有 yum 来安装 openresty

    wget https://openresty.org/package/centos/openresty.repo
    mv openresty.repo /etc/yum.repos.d/
    yum check-update
    yum install -y openresty
    

    安装完成后,在 nginx 中添加一个 url 测试页面

            location /hello {
                default_type text/html;
                content_by_lua_block {
                    ngx.say("<p>hello, world</p>")
                }
            }
    

    验证测试服务是否正常

    /usr/local/openresty/nginx/sbin/nginx -t
    /usr/local/openresty/nginx/sbin/nginx -s reload
    

    部署 waf 功能模块

    从 git 上下载相关文件

    git clone https://github.com/unixhot/waf.git
    cp -a ./waf/waf /usr/local/openresty/nginx/conf/
    ln -s /usr/local/openresty/lualib/resty/ /usr/local/openresty/nginx/conf/waf/resty
    /usr/local/openresty/nginx/sbin/nginx -t
    /usr/local/openresty/nginx/sbin/nginx -s reload
    

    查看文件目录

    ./waf/
    ├── access.lua
    ├── config.lua   # 配置文件
    ├── init.lua   # 规则函数
    ├── lib.lua
    ├── resty -> /usr/local/openresty/lualib/resty/
    └── rule-config
        ├── args.rule
        ├── blackip.rule   # IP黑名单
        ├── cookie.rule   # cookie请求过滤规则
        ├── post.rule   # post请求过滤规则
        ├── url.rule   # url请求过滤规则
        ├── useragent.rule   # useragent过滤规则
        ├── whiteip.rule   # IP请求白名单
        └── whiteurl.rule   # url请求白名单
    

    以上到此,可以基本实现一些常见防护攻击,但是我们这边的需求是需要根据特定的域名加上 uri 来进行限制,这个功能就无法做到,我查阅了一些资料之后根据其他文档简单的改写了一些 lua 来进行判断,如果有类似的需求可以根据下面的文档来进行操作。

    waf 功能模块调整改写

    这里的前提条件是需要 openresty 和 lua 等相关功能模块已经就绪可以正常使用。
    我这里参考了 https://github.com/sosojustdo/tengine_waf 的文献来进行微调实现的,需要注意的是不管使用 tengine 或者是 openresty 都是可以的,我两个都测试过,只要lua的结构和函数对应进行调整即可。
    首先我们来看一下这个文件目录

    ./conf/
    └── waf
        ├── access.lua  # 条件配置文件
        ├── args
        ├── blockip
        ├── config.lua  # 配置文件
        ├── cookie
        ├── denycc  # 基于 ip,ip+uri 等多种方式,具体可以参考上面 git 的内容介绍
        ├── denyhost  # 域名+uri 配置文件,这个是我新增的一个文件
        ├── init.lua  # 规则函数文件
        ├── post
        ├── resty -> /usr/local/openresty/lualib/resty/
        ├── url
        ├── user-agent
        ├── whiteip
        └── whiteurl
    

    这里我只介绍需要调整相关的文件内容
    nginx.conf 文件

    # 开启日志方便调试
    error_log  logs/error.log  info;
    # 在 http 段中增加
        # WAF config
        lua_shared_dict limit 50m;
        lua_shared_dict blockiplimit 10m;
        lua_package_path "/usr/local/openresty/nginx/conf/waf/?.lua";
        init_by_lua_file "/usr/local/openresty/nginx/conf/waf/init.lua";
        access_by_lua_file "/usr/local/openresty/nginx/conf/waf/access.lua";
    

    access.lua 文件,在这个文件当中我新增了一个函数配置

    local content_length=tonumber(ngx.req.get_headers()['content-length'])
    local method=ngx.req.get_method()
    local ngxmatch=ngx.re.match
    
    if whiteip() then
    elseif blockip() then
    elseif denyhost() then  -- 这里是我新写的一个关于域名+uri 方法的一个判断条件
    elseif denycc() then
    elseif ngx.var.http_Acunetix_Aspect then
        say_html()
    elseif whiteurl() then
    elseif ua() then
    elseif url() then
    elseif args() then
    elseif cookie() then
    elseif PostCheck then
        if method=="POST" then   
                local boundary = get_boundary()
                if boundary then
                local len = string.len
                local sock, err = ngx.req.socket()
                if not sock then
                                            return
                end
                ngx.req.init_body(128 * 1024)
                sock:settimeout(0)
                local content_length = nil
                content_length=tonumber(ngx.req.get_headers()['content-length'])
                local chunk_size = 4096
                if content_length < chunk_size then
                                            chunk_size = content_length
                end
                local size = 0
                while size < content_length do
                    local data, err, partial = sock:receive(chunk_size)
                    data = data or partial
                    if not data then
                            return
                    end
                    ngx.req.append_body(data)
                    if body(data) then
                            return true
                    end
                    size = size + len(data)
                    local m = ngxmatch(data,'Content-Disposition: form-data;(.+)filename="(.+)\\.(.*)"','ijo')
                    if m then
                            fileExtCheck(m[3])
                            filetranslate = true
                    else
                            if ngxmatch(data,"Content-Disposition:",'isjo') then
                                    filetranslate = false
                            end
                            if filetranslate==false then
                                    if body(data) then
                                            return true
                                    end
                            end
                    end
                    local less = content_length - size
                    if less < chunk_size then
                            chunk_size = less
                    end
             end
             ngx.req.finish_body()
        else
                            ngx.req.read_body()
                            local args = ngx.req.get_post_args()
                            if not args then
                                    return
                            end
                            for key, val in pairs(args) do
                                    if type(val) == "table" or val == false then
                                            data=table.concat(val, ", ")
                                    else
                                            data=val
                                    end
                                    if data and type(data) ~= "boolean" and body(data) then
                      return true
                                    end
                            end
                    end
        end
    else
        return
    end
    

    config.lua 文件

    RulePath = "/usr/local/openresty/nginx/conf/waf/"
    logdir = "/usr/local/openresty/nginx/logs/"
    black_fileExt={"php","jsp"}
    
    OnlyCheck="off"
    
    urlMatch="on"
    cookieMatch="on"
    postMatch="on" 
    whiteurlMatch="on" 
    whiteipMatch="on"
    blackipMatch="on"
    denyhost="on"  -- 这里是新增功能的开启按钮
    denycc="on"
    
    -- 这里定义被拒的提示页
    html=[[
    <html xmlns="http://www.w3.org/1999/xhtml"><head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>网站防火墙</title>
    <style>
    p {
            line-height:20px;
    }
    ul{ list-style-type:none;}
    li{ list-style-type:none;}
    </style>
    </head>
    
    <body style=" padding:0; margin:0; font:14px/1.5 Microsoft Yahei, 宋体,sans-serif; color:#555;">
    
     <div style="margin: 0 auto; width:1000px; padding-top:70px; overflow:hidden;">
      
      
      <div style="width:600px; float:left;">
        <div style=" height:40px; line-height:40px; color:#fff; font-size:16px; overflow:hidden; background:#6bb3f6; padding-left:20px;">网站防火墙 </div>
        <div style="border:1px dashed #cdcece; border-top:none; font-size:14px; background:#fff; color:#555; line-height:24px; height:220px; padding:20px 20px 0 20px; overflow-y:auto;background:#f3f7f9;">
          <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600; color:#fc4f03;">您的请求带有不合法参数,已被网站管理员设置拦截!</span></p>
    <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">可能原因:您提交的内容包含危险的攻击请求或者请求次数过多</p>
    <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;">如何解决:</p>
    <ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">1)检查提交内容;</li>
    <li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">2)请联系某某部 abc </li>
    <li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">3)请联系公司信息安全部;</li></ul>
        </div>
      </div>
    </div>
    </body></html>
    ]]
    

    init.lua 文件,这个是自定义的规则函数,有不同的需求都可以在这个文件当中自己写代码去定义,这里我只把我自己新增的函数内容展示出来,原有的函数保持不变

    -- 这里新定义个获取域名+uri 的 token 函数
    function gettokenhost(data)
        if data ~= nil then
            return data
        end
    end
    
    -- 这里定义需要屏蔽内容函数
    function denyhost()
        if HostDeny then
        for _,rule in pairs(denyhostrules) do
            if rule ~="" and string.sub(rule,1,1) ~= "#" then
                local clientip=getClientIp()
                local data = string.match(rule,'(.*)%s+%d+/(.*)%s+%d+')
                local CCrate = string.match(rule,'.*%s+(%d+/%d+)%s+%d+')
                local bantime = tonumber(string.match(rule,'.*%s+.*%s+(%d+)'))
                if data ~= nil and CCrate ~=nil and bantime ~=nil then
                    local token=gettokenhost(data)
                    if token ~=nil and token == (ngx.var.host..ngx.var.request_uri) then
                        local CCcount=tonumber(string.match(CCrate,'(.*)/'))
                        local CCseconds=tonumber(string.match(CCrate,'/(.*)'))
                        local limit = ngx.shared.limit
                        local blockiplimit = ngx.shared.blockiplimit
                        local blockipreq,_=blockiplimit:get(clientip..data)
                        if  blockipreq then     
                            say_html()  
                            return true 
                        end
                        local req,_=limit:get(token)
                        if req then
                            if req >= CCcount then
                                log('denyhost',token,"-",rule)
                                blockiplimit:set(clientip..data,1,bantime) 
                                say_html()
                                return true
                            else
                                limit:incr(token,1)
                            end
                        else
                            limit:set(token,1,CCseconds)
                        end
                    end
                end
            end
        end
        return false
        end
    end
    

    定义完以上内容之后,可以去进行验证测试,我在 denyhost 文件中的定义如下

    #domain/uri 10/60 60
    # 这里有两个规则
    # 限制 hi.test.com/hi 这个域名,在1分钟内访问超过5次之后,拒绝访问1分钟
    # 限制 hi.test.com/shiyongzhi 这个域名,在1分钟内访问超过10次之后,拒绝访问1分钟
    # 其他域名和 uri 则不受时间和次数限制
    hi.test.com/hi 5/60 60
    hi.test.com/shiyongzhi 10/60 60
    

    相关文章

      网友评论

          本文标题:Openresty 之 waf 功能

          本文链接:https://www.haomeiwen.com/subject/zlcztrtx.html