zoukankan      html  css  js  c++  java
  • ngx_lua模块学习示例之waf

    转自:http://www.tuicool.com/articles/FbQ3ymB

    WAF的主要功能为:

    • ip黑白名单
    • url黑白名单
    • useragent黑白名单
    • referer黑白名单
    • 常见web漏洞防护,如xss,sql注入等
    • cc攻击防护
    • 扫描器简单防护
    • 其他你想要的功能

    WAF的总体检测思路:

    • 当用户访问到nginx时,waf首先获取用户的ip,uri,referer,useragent,,cookie,args,post,method,header信息。
    • 将获取到的信息依次传给上述功能的函数,如ip规则,在ip规则中,循环到所有的ip规则,如果匹配到ip则根据规则的处理方式来进行处理,匹配到之后不继续匹配后续规则。
    • 需要开启的功能依次在主函数中调用即可,顺序也可根据实际场景来确定最合适的顺序。

    config.lua

    RulePath = "/data/mypro/ngx_lua/waf/wafconf/"
    attacklog = "on"
    logdir = "/usr/local/openresty/nginx/logs"
    UrlDeny="on"
    Redirect="on"
    CookieMatch="on"
    postMatch="on" 
    whiteModule="on" 
    ipWhitelist={}
    ipBlocklist={"1.0.0.1"}
    CCDeny="off"
    CCrate="100/60"
    html=[[Please go away~~ ]]

    init.lua

    require 'config'
    local match = string.match
    local ngxmatch=ngx.re.match
    local unescape=ngx.unescape_uri
    local get_headers = ngx.req.get_headers
    local optionIsOn = function (options) return options == "on" and true or false end
    logpath = logdir 
    rulepath = RulePath
    UrlDeny = optionIsOn(UrlDeny)
    PostCheck = optionIsOn(postMatch)
    CookieCheck = optionIsOn(cookieMatch)
    WhiteCheck = optionIsOn(whiteModule)
    PathInfoFix = optionIsOn(PathInfoFix)
    attacklog = optionIsOn(attacklog)
    CCDeny = optionIsOn(CCDeny)
    Redirect=optionIsOn(Redirect)
    function getClientIp()
            IP = ngx.req.get_headers()["X-Real-IP"]
            if IP == nil then
                    IP  = ngx.var.remote_addr 
            end
            if IP == nil then
                    IP  = "unknown"
            end
            return IP
    end
    function write(logfile,msg)
        local fd = io.open(logfile,"ab")
        if fd == nil then return end
        fd:write(msg)
        fd:flush()
        fd:close()
    end
    function log(method,url,data,ruletag)
        if attacklog then
            local realIp = getClientIp()
            local ua = ngx.var.http_user_agent
            local servername=ngx.var.server_name
            local time=ngx.localtime()
            if ua  then
                line = realIp.." ["..time.."] ""..method.." "..servername..url.."" ""..data..""  ""..ua.."" ""..ruletag..""
    "
            else
                line = realIp.." ["..time.."] ""..method.." "..servername..url.."" ""..data.."" - ""..ruletag..""
    "
            end
            local filename = logpath..'/'..servername.."_"..ngx.today().."_sec.log"
            write(filename,line)
        end
    end
    ------------------------------------规则读取函数-------------------------------------------------------------------
    function read_rule(var)
        file = io.open(rulepath..'/'..var,"r")
        if file==nil then
            return
        end
        t = {}
        for line in file:lines() do
            table.insert(t,line)
        end
        file:close()
        return(t)
    end

      local urlrules=read_rule('url')
      local argsrules=read_rule('args')
      local uarules=read_rule('user-agent')
      local wturlrules=read_rule('whiteurl')
      local postrules=read_rule('post')
      local ckrules=read_rule('cookie')

    function say_html()
        if Redirect then
            ngx.header.content_type = "text/html"
            ngx.say(html)
            ngx.exit(200)
        end
    end
    
    function whiteurl()
        if WhiteCheck then
            if wturlrules ~=nil then
                for _,rule in pairs(wturlrules) do
                    if ngxmatch(ngx.var.request_uri,rule,"imjo") then
                        return true 
                     end
                end
            end
        end
        return false
    end
    
    function args()
        for _,rule in pairs(argsrules) do
            local args = ngx.req.get_uri_args()
            for key, val in pairs(args) do
                if type(val)=='table' then
                    if val == false then
                        data=table.concat(val, " ")
                    end
                else
                    data=val
                end
                if data and type(data) ~= "boolean" and rule ~="" and ngxmatch(unescape(data),rule,"imjo") then
                    log('GET',ngx.var.request_uri,"-",rule)
                    say_html()
                    return true
                end
            end
        end
        return false
    end
    
    
    function url()
        if UrlDeny then
            for _,rule in pairs(urlrules) do
                if rule ~="" and ngxmatch(ngx.var.request_uri,rule,"imjo") then
                    log('GET',ngx.var.request_uri,"-",rule)
                    say_html()
                    return true
                end
            end
        end
        return false
    end
    
    function ua()
        local ua = ngx.var.http_user_agent
        if ua ~= nil then
            for _,rule in pairs(uarules) do
                if rule ~="" and ngxmatch(ua,rule,"imjo") then
                    log('UA',ngx.var.request_uri,"-",rule)
                    say_html()
                return true
                end
            end
        end
        return false
    end
    function body(data)
        for _,rule in pairs(postrules) do
            if rule ~="" and data~="" and ngxmatch(unescape(data),rule,"imjo") then
                log('POST',ngx.var.request_uri,data,rule)
                say_html()
                return true
            end
        end
        return false
    end
    function cookie()
        local ck = ngx.var.http_cookie
        if CookieCheck and ck then
            for _,rule in pairs(ckrules) do
                if rule ~="" and ngxmatch(ck,rule,"imjo") then
                    log('Cookie',ngx.var.request_uri,"-",rule)
                    say_html()
                return true
                end
            end
        end
        return false
    end
    
    function denycc()
        if CCDeny then
            local uri=ngx.var.uri
            CCcount=tonumber(string.match(CCrate,'(.*)/'))
            CCseconds=tonumber(string.match(CCrate,'/(.*)'))
            local token = getClientIp()..uri
            local limit = ngx.shared.limit
            local req,_=limit:get(token)
            if req then
                if req > CCcount then
                     ngx.exit(503)
                    return true
                else
                     limit:incr(token,1)
                end
            else
                limit:set(token,1,CCseconds)
            end
        end
        return false
    end
    
    function get_boundary()
        local header = get_headers()["content-type"]
        if not header then
            return nil
        end
    
        if type(header) == "table" then
            header = header[1]
        end
    
        local m = match(header, ";%s*boundary="([^"]+)"")
        if m then
            return m
        end
    
        return match(header, ";%s*boundary=([^",;]+)")
    end
    
    function whiteip()
        if next(ipWhitelist) ~= nil then
            for _,ip in pairs(ipWhitelist) do
                if getClientIp()==ip then
                    return true
                end
            end
        end
            return false
    end
    
    function blockip()
         if next(ipBlocklist) ~= nil then
             for _,ip in pairs(ipBlocklist) do
                 if getClientIp()==ip then
                     ngx.exit(403)
                     return true
                 end
             end
         end
             return false
    end

    waf.lua

    local content_length=tonumber(ngx.req.get_headers()['content-length'])
    local method=ngx.req.get_method()
    if whiteip() then
    elseif blockip() then
    elseif denycc() then
    elseif ngx.var.http_Acunetix_Aspect then
        ngx.exit(444)
    elseif ngx.var.http_X_Scan_Memo then
        ngx.exit(444)
    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 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

    在nginx.conf的http段添加

        lua_package_path "/usr/local/nginx/conf/waf/?.lua";
        lua_shared_dict limit 10m;
        init_by_lua_file  /usr/local/nginx/conf/waf/init.lua; 
        access_by_lua_file /usr/local/nginx/conf/waf/waf.lua;

    重启nginx

    部署完毕可以尝试如下命令:

    curl http://xxxx/test.php?id=../etc/passwd
    返回"Please go away~~"字样,说明规则生效。
  • 相关阅读:
    3.25Java常量
    3.26Java逻辑运算符
    3.26Java关系运算符
    Java标识符
    3.27Java位运算符
    3.26Java运算符(operator)
    3.26Java字符型(char)变量、常量
    3.26Java布尔类型(boolean)变量、常量
    《算法导论》第9章 顺序统计学 (1)最小值和最大值
    《算法导论》第8章 线性时间排序 (1)计数排序
  • 原文地址:https://www.cnblogs.com/wangxusummer/p/4332648.html
Copyright © 2011-2022 走看看