基于ngx_lua模塊的WAF開(kāi)發(fā)實(shí)踐
0x00 常見(jiàn)WAF簡(jiǎn)單分析
WAF主要分為硬件WAF和軟件防火墻,硬件WAF如綠盟的NSFOCUS Web Application Firewall,軟件防火墻比較有名的是ModSecurity,再就是代碼級(jí)別的ngx_lua_waf。下面談?wù)剛€(gè)人對(duì)幾款防火墻的理解:
硬件WAF個(gè)人覺(jué)得只適合在那種訪問(wèn)量較少的網(wǎng)站,比如政府網(wǎng)站,公司的介紹網(wǎng)站等等。硬件WAF的的優(yōu)勢(shì)在于規(guī)則有專門的安全公司維護(hù),管理方便,但也存在一個(gè)致命的弱點(diǎn),使用傳統(tǒng)的方式來(lái)解包到應(yīng)用層對(duì)性能的需求較高,而且當(dāng)訪問(wèn)量很大的時(shí)候延時(shí)比較大,這樣在高并發(fā)訪問(wèn)的情況下要使用硬件WAF就只能使用很多臺(tái)WAF了,這樣成本就非常高了;還有一個(gè)在接觸過(guò)程中發(fā)現(xiàn)的問(wèn)題,就是硬件WAF的規(guī)則雖然多而且有人維護(hù),但是一般公司很難敢直接開(kāi)啟阻難,很多都是只記錄,并不能阻難,這樣WAF的意義就變得小多了。
ModSecurity在網(wǎng)上的評(píng)價(jià)都是很高的,性能高,規(guī)則全。最開(kāi)始我研究的也是這款WAF,但是在實(shí)際使用過(guò)程中發(fā)現(xiàn)問(wèn)題,就是在高并發(fā)的情況下,運(yùn)行一段時(shí)間,會(huì)出現(xiàn)內(nèi)存飆升,而且不下來(lái)的問(wèn)題。這個(gè)問(wèn)題再M(fèi)odSecurity的討論論壇上面也發(fā)現(xiàn)了有人提出這樣的問(wèn)題,但一直未解決(https://github.com/SpiderLabs/ModSecurity/issues/785)。針對(duì)于規(guī)則全的優(yōu)勢(shì),一般使用者也不敢直接開(kāi)啟所有的規(guī)則攔截,畢竟每個(gè)公司的業(yè)務(wù)不同,規(guī)則也不可能直接套用。
基于高性能,低成本的想法,發(fā)現(xiàn)了@loveshell開(kāi)發(fā)的ngx_lua_waf,經(jīng)過(guò)實(shí)際使用下來(lái),確實(shí)性能極好,由于LUA語(yǔ)言的性能是接近于C的,而且ngx_lua_module本身就是基于為nginx開(kāi)發(fā)的高性能的模塊。安全寶的云 WAF,以及cloudflare的新waf也是基于此模塊使用LUA開(kāi)發(fā)的。結(jié)合ModSecurity的思路,參考@loveshell的ngx_lua_waf來(lái)開(kāi)發(fā)適合自己用的WAF,其中使用了很多@loveshell的函數(shù),再此也表示感謝。
0x01 WAF框架設(shè)計(jì)
WAF開(kāi)發(fā)過(guò)程中的主要方向?yàn)椋?/p>
◆主引擎的開(kāi)發(fā),主要關(guān)注主引擎的性能和容錯(cuò)能力 ◆規(guī)則的開(kāi)發(fā),主要關(guān)注規(guī)則的全面可靠,防勿攔截以及防繞過(guò) ◆整體方案能夠適應(yīng)多站點(diǎn),高可用性的環(huán)境
WAF的主要功能為:
◆ip黑白名單 ◆url黑白名單 ◆useragent黑白名單 ◆referer黑白名單 ◆常見(jiàn)web漏洞防護(hù),如xss,sql注入等 ◆cc攻擊防護(hù) ◆掃描器簡(jiǎn)單防護(hù) ◆其他你想要的功能
WAF的總體檢測(cè)思路:
◆當(dāng)用戶訪問(wèn)到nginx時(shí),waf首先獲取用戶的ip,uri,referer,useragent,,cookie,args,post,method,header信息。 ◆將獲取到的信息依次傳給上述功能的函數(shù),如ip規(guī)則,在ip規(guī)則中,循環(huán)到所有的ip規(guī)則,如果匹配到ip則根據(jù)規(guī)則的處理方式來(lái)進(jìn)行處理,匹配到之后不繼續(xù)匹配后續(xù)規(guī)則。 ◆需要開(kāi)啟的功能依次在主函數(shù)中調(diào)用即可,順序也可根據(jù)實(shí)際場(chǎng)景來(lái)確定最合適的順序。
圖示如下:
0x02 規(guī)則格式分析
規(guī)則說(shuō)明:
比如規(guī)則:{"rule00001","rules","args|post|cookie",[[../]],"deny","logon"},
rule00001:規(guī)則編號(hào),隨意寫
rules:規(guī)則名稱,如xssrules,隨意寫
args|post|cookie|header:檢測(cè)位置,|表示或,args,post,cookie,header可多選
../:匹配的正則表達(dá)式,標(biāo)準(zhǔn)PCRE正則
deny:處理方式,可選deny ,allow
logon:日志記錄與否,可選logon,logoff
0x03 cc攻擊防護(hù)代碼示例
- --在nginx.conf的HTTP中加入
- --lua_shared_dict limit 50m; 根據(jù)主機(jī)內(nèi)存調(diào)合適的值
- --lua_shared_dict iplimit 20m;
- --lua_shared_dict blockiplimit 5m;
- -------------------------------------------------------------
- CCDeny="on" --cc攻擊開(kāi)關(guān)
- CCrate="60/60"--基于url的計(jì)數(shù) 次/秒
- ipCCrate="600/60"--基于ip的計(jì)數(shù) 次/秒
- -------------------------------------------------
- ccdenyrules={"ccdeny1","ccdeny","","","","logon"}
- function gethost()
- host = ngx.var.host
- if host == nil or type(host) ~= "string" then
- math.randomseed(os.time())
- host = "nohost"..math.random()
- end
- return host
- end
- function denycc(clientdata)
- if CCDeny=="on" then
- local uri=clientdata[2]
- local host = gethost()
- CCcount=tonumber(string.match(CCrate,'(.*)/'))
- CCseconds=tonumber(string.match(CCrate,'/(.*)'))
- ipCCcount=tonumber(string.match(ipCCrate,'(.*)/'))
- ipCCseconds=tonumber(string.match(ipCCrate,'/(.*)'))
- local token = clientdata[1]..host..uri
- local clientip = clientdata[1]..host
- local limit = ngx.shared.limit
- local iplimit = ngx.shared.iplimit
- local blockiplimit = ngx.shared.blockiplimit
- local req,_=limit:get(token)
- local ipreq,_=iplimit:get(clientip)
- local blockipreq,_=blockiplimit:get(clientip)
- if blockipreq or ipreq then
- if blockipreq or req then
- if blockipreq or req >= CCcount or ipreq >= ipCCcount then
- log(ccdenyrules,clientdata)
- blockiplimit:set(clientip,1,300)
- ngx.exit(403)
- return true
- else
- limit:incr(token,1)
- iplimit:incr(clientip,1)
- end
- else
- limit:set(token,1,CCseconds)
- end
- else
- iplimit:set(clientip,1,ipCCseconds)
- end
- end
- return false
- end
#p#
0x04 優(yōu)勢(shì)舉例
可以很靈活的實(shí)現(xiàn)復(fù)雜的控制
比如我在我的個(gè)人網(wǎng)站上面就使用了這樣一個(gè)功能,后臺(tái)頁(yè)面需要特定useragent才能訪問(wèn)。
代碼如下:
- --特定頁(yè)面容許特定useragent可訪問(wèn)
- function houtai(clientdata)
- if stringmatch(clientdata[2],"wp-admin") then
- if stringmatch(clientdata[4],"hahahaha") then
- return
- else
- ngx.exit(403)
- return
- end
- else
- return
- end
- end
可以測(cè)試http://www.zhangsan.me/wp-admin/
只有在特定的useragent才可以訪問(wèn)此頁(yè)面,否則報(bào)403錯(cuò)誤。
0x05 源碼下載及使用
源碼下載地址為:http://pan.baidu.com/s/18QQya
環(huán)境搭建就參考:http://wiki.nginx.org/HttpLuaModule#Installation
waf使用主要就是配置config.lua
SecRuleEngine = "on" attacklog = "on" logpath = "/home/waflog/"
分別為引擎是否開(kāi)啟 是否記錄日志 日志的存儲(chǔ)路徑 日志的存儲(chǔ)路徑需要給予nginx運(yùn)行用戶的讀寫權(quán)限
0x06 后續(xù)研究方向
1.根據(jù)ModSecurity規(guī)則提取一份較適應(yīng)自己用的規(guī)則
2.根據(jù)最新出現(xiàn)的漏洞維護(hù)規(guī)則
3.在多個(gè)站點(diǎn)的情況下,如果在站點(diǎn)變動(dòng),規(guī)則變動(dòng)的時(shí)候,不影響其他站點(diǎn),實(shí)現(xiàn)高可用性。
寫的很簡(jiǎn)單,大牛勿噴,希望大家多提建議。
0x07 參考資料
1. https://github.com/loveshell/ngx_lua_waf
2. http://wiki.nginx.org/HttpLuaModule
3. http://www.freebuf.com/tools/54221.html
……