转自: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.."\"\n" else line = realIp.." ["..time.."] \""..method.." "..servername..url.."\" \""..data.."\" - \""..ruletag.."\"\n" 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~~"字样,说明规则生效。
时间: 2024-10-02 07:10:40