經過nginx配置文件抵禦攻擊
0x00 前言
你們好,咱們是OpenCDN團隊的Twwy。此次咱們來說講如何經過簡單的配置文件來實現nginx防護攻擊的效果。javascript
其實不少時候,各類防攻擊的思路咱們都明白,好比限制IP啊,過濾攻擊字符串啊,識別攻擊指紋啦。但是要如何去實現它呢?用守護腳本嗎?用PHP在外面包一層過濾?仍是直接加防火牆嗎?這些都是防護手段。不過本文將要介紹的是直接經過nginx的普通模塊和配置文件的組合來達到必定的防護效果。php
0x01 驗證瀏覽器行爲
簡易版
咱們先來作個比喻。css
社區在搞福利,在廣場上給你們派發紅包。而壞人派了一批人形的機器人(沒有語言模塊)來冒領紅包,聰明工做人員須要想出辦法來防止紅包被冒領。html
因而工做人員在發紅包以前,會給領取者一張紙,上面寫着「紅包拿來」,若是那人能念出紙上的字,那麼就是人,給紅包,若是你不能念出來,那麼請自覺。因而機器人便被識破,灰溜溜地回來了。java
是的,在這個比喻中,人就是瀏覽器,機器人就是攻擊器,咱們能夠經過鑑別cookie功能(念紙上的字)的方式來鑑別他們。下面就是nginx的配置文件寫法。linux
1
2
3
4
|
if
($cookie_say !=
"hbnl"
){
add_header Set-Cookie
"say=hbnl"
;
rewrite .*
"$scheme://$host$uri"
redirect;
}
|
讓咱們看下這幾行的意思,當cookie中say爲空時,給一個設置cookie say爲hbnl的302重定向包,若是訪問者可以在第二個包中攜帶上cookie值,那麼就能正常訪問網站了,若是不能的話,那他永遠活在了302中。你也能夠測試一下,用CC攻擊器或者webbench或者直接curl發包作測試,他們都活在了302世界中。nginx
固然,這麼簡單就能防住了?固然沒有那麼簡單。git
加強版
仔細的你必定會發現配置文件這樣寫仍是有缺陷。若是攻擊者設置cookie爲say=hbnl(CC攻擊器上就能夠這麼設置),那麼這個防護就形同虛設了。咱們繼續拿剛剛那個比喻來講明問題。github
壞人發現這個規律後,給每一個機器人安上了揚聲器,一直重複着「紅包拿來,紅包拿來」,浩浩蕩蕩地又來領紅包了。web
這時,工做人員的對策是這樣作的,要求領取者出示有本身名字的戶口本,而且念出本身的名字,「我是xxx,紅包拿來」。因而一羣只會嗡嗡叫着「紅包拿來」的機器人又被攆回去了。
固然,爲了配合說明問題,每一個機器人是有戶口本的,被趕回去的緣由是不會念本身的名字,雖然這個有點荒誕,唉。
而後,咱們來看下這種方式的配置文件寫法
1
2
3
4
|
if
($cookie_say !=
"hbnl$remote_addr"
){
add_header Set-Cookie
"say=hbnl$remote_addr"
;
rewrite .*
"$scheme://$host$uri"
redirect;
}
|
這樣的寫法和前面的區別是,不一樣IP的請求cookie值是不同的,好比IP是1.2.3.4,那麼須要設置的cookie是say=hbnl1.2.3.4。因而攻擊者便沒法經過設置同樣的cookie(好比CC攻擊器)來繞過這種限制。你能夠繼續用CC攻擊器來測試下,你會發現CC攻擊器打出的流量已經所有進入302世界中。
不過你們也能感受到,這彷佛也不是一個萬全之計,由於攻擊者若是研究了網站的機制以後,總有辦法測出並預先僞造cookie值的設置方法。由於咱們作差別化的數據源正是他們自己的一些信息(IP、user agent等)。攻擊者花點時間也是能夠作出專門針對網站的攻擊腳本的。
完美版
那麼要如何根據他們自身的信息得出他們又得出他們算不出的數值?
我想,聰明的你必定已經猜到了,用salt加散列。好比md5("opencdn$remote_addr"),雖然攻擊者知道能夠本身IP,可是他沒法得知如何用他的IP來計算出這個散列,由於他是逆不出這個散列的。固然,若是你不放心的話,怕cmd5.com萬一能查出來的話,能夠加一些特殊字符,而後多散幾回。
很惋惜,nginx默認是沒法進行字符串散列的,因而咱們藉助nginx_lua模塊來進行實現。
1
2
3
4
5
6
7
|
rewrite_by_lua '
local
say = ngx.md5(
"opencdn"
.. ngx.var.remote_addr)
if
(ngx.var.cookie_say ~= say)
then
ngx.header[
"Set-Cookie"
] =
"say="
.. say
return
ngx.redirect(ngx.var.scheme ..
"://"
.. ngx.var.host .. ngx.var.uri)
end
';
|
經過這樣的配置,攻擊者便沒法事先計算這個cookie中的say值,因而攻擊流量(代理型CC和低級發包型CC)便在302地獄沒法自拔了。
你們能夠看到,除了借用了md5這個函數外,其餘的邏輯和上面的寫法是如出一轍的。所以若是能夠的話,你徹底能夠安裝一個nginx的計算散列的第三方模塊來完成,可能效率會更高一些。
這段配置是能夠被放在任意的location裏面,若是你的網站有對外提供API功能的話,建議API必定不能加入這段,由於API的調用也是沒有瀏覽器行爲的,會被當作攻擊流量處理。而且,有些弱一點爬蟲也會陷在302之中,這個須要注意。
同時,若是你以爲set-cookie這個動做彷佛攻擊者也有可能經過解析字符串模擬出來的話,你能夠把上述的經過header來設置cookie的操做,變成經過高端大氣的js完成,發回一個含有doument.cookie=...的文本便可。
那麼,攻擊是否是徹底被擋住了呢?只能說那些低級的攻擊已經被擋住而來,若是攻擊者必須花很大代價給每一個攻擊器加上webkit模塊來解析js和執行set-cookie才行,那麼他也是能夠逃脫302地獄的,在nginx看來,確實攻擊流量和普通瀏覽流量是同樣的。那麼如何防護呢?下節會告訴你答案。
0x02 請求頻率限制
不得不說,不少防CC的措施是直接在請求頻率上作限制來實現的,可是,不少都存在着必定的問題。
那麼是哪些問題呢?
首先,若是經過IP來限制請求頻率,容易致使一些誤殺,好比我一個地方出口IP就那麼幾個,而訪問者一多的話,請求頻率很容易到上限,那麼那個地方的用戶就都訪問不了你的網站了。
因而你會說,我用SESSION來限制就有這個問題了。嗯,你的SESSION爲攻擊者敞開了一道大門。爲何呢?看了上文的你可能已經大體知道了,由於就像那個「紅包拿來」的揚聲器同樣,不少語言或者框架中的SESSION是可以僞造的。以php爲例,你能夠在瀏覽器中的cookie看到PHPSESSIONID,這個ID不一樣的話,session也就不一樣了,而後若是你杜撰一個PHPSESSIONID過去的話,你會發現,服務器也承認了這個ID,爲這個ID初始化了一個會話。那麼,攻擊者只須要每次發完包就構造一個新的SESSIONID就能夠很輕鬆地躲過這種在session上的請求次數限制。
那麼咱們要如何來作這個請求頻率的限制呢?
首先,咱們先要一個攻擊者沒法杜撰的sessionID,一種方式是用個池子記錄下每次給出的ID,而後在請求來的時候進行查詢,若是沒有的話,就拒絕請求。這種方式咱們不推薦,首先一個網站已經有了session池,這樣再作個無疑有些浪費,並且還須要進行池中的遍歷比較查詢,太消耗性能。咱們但願的是一種能夠無狀態性的sessionID,能夠嗎?能夠的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
rewrite_by_lua '
local
random = ngx.var.cookie_random
if
(random == nil)
then
random = math.random(999999)
end
local
token = ngx.md5(
"opencdn"
.. ngx.var.remote_addr .. random)
if
(ngx.var.cookie_token ~= token)
then
ngx.header[
"Set-Cookie"
] = {
"token="
.. token,
"random="
.. random}
return
ngx.redirect(ngx.var.scheme ..
"://"
.. ngx.var.host .. ngx.var.uri)
end
';
|
你們是否是以爲好像有些眼熟?是的,這個就是上節的完美版的配置再加個隨機數,爲的是讓同一個IP的用戶也能有不一樣的token。一樣的,只要有nginx的第三方模塊提供散列和隨機數功能,這個配置也能夠不用lua直接用純配置文件完成。
有了這個token以後,至關於每一個訪客有一個沒法僞造的而且獨一無二的token,這種狀況下,進行請求限制纔有意義。
因爲有了token作鋪墊,咱們能夠不作什麼白名單、黑名單,直接經過limit模塊來完成。
1
2
3
4
|
http{
...
limit_req_zone $cookie_token zone=session_limit:3m rate=1r
/s
;
}
|
而後咱們只須要在上面的token配置後面中加入
1
|
limit_req zone=session_limit burst=5;
|
因而,又是兩行配置便讓nginx在session層解決了請求頻率的限制。不過彷佛仍是有缺陷,由於攻擊者能夠經過一直獲取token來突破請求頻率限制,若是能限制一個IP獲取token的頻率就更完美了。能夠作到嗎?能夠。
1
2
3
4
5
|
http{
...
limit_req_zone $cookie_token zone=session_limit:3m rate=1r
/s
;
limit_req_zone $binary_remote_addr $uri zone=auth_limit:3m rate=1r
/m
;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
location /{
limit_req zone=session_limit burst=5;
rewrite_by_lua '
local
random = ngx.var.cookie_random
if
(random == nil)
then
return
ngx.redirect(
"/auth?url="
.. ngx.var.request_uri)
end
local
token = ngx.md5(
"opencdn"
.. ngx.var.remote_addr .. random)
if
(ngx.var.cookie_token ~= token)
then
return
ngx.redirect(
"/auth?url="
.. ngx.var.request_uri)
end
';
}
location
/auth
{
limit_req zone=auth_limit burst=1;
if
($arg_url =
""
) {
return
403;
}
access_by_lua '
local
random = math.random(9999)
local
token = ngx.md5(
"opencdn"
.. ngx.var.remote_addr .. random)
if
(ngx.var.cookie_token ~= token)
then
ngx.header[
"Set-Cookie"
] = {
"token="
.. token,
"random="
.. random}
return
ngx.redirect(ngx.var.arg_url)
end
';
}
|
我想你們也應該已經猜到,這段配置文件的原理就是:把原本的發token的功能分離到一個auth頁面,而後用limit對這個auth頁面進行頻率限制便可。這邊的頻率是1個IP每分鐘受權1個token。固然,這個數量能夠根據業務須要進行調整。
須要注意的是,這個auth部分我lua採用的是access_by_lua,緣由在於limit模塊是在rewrite階段後執行的,若是在rewrite階段302的話,limit將會失效。所以,這段lua配置我不能保證能夠用原生的配置文件實現,由於不知道如何用配置文件在rewrite階段後進行302跳轉,也求大牛可以指點一下啊。
固然,你若是還不知足於這種限制的話,想要作到某個IP若是一天到達上限超過幾回以後就直接封IP的話,也是能夠的,你能夠用相似的思路再作個錯誤頁面,而後到達上限以後不返回503而是跳轉到那個錯誤頁面,而後錯誤頁面也作個請求次數限制,好比天天只能訪問100次,那麼當超過報錯超過100次(請求錯誤頁面100次)以後,那天這個IP就不能再訪問這個網站了。
因而,經過這些配置咱們便實現了一個網站訪問頻率限制。不過,這樣的配置也不是說能夠徹底防止了攻擊,只能說讓攻擊者的成本變高,讓網站的扛攻擊能力變強,固然,前提是nginx可以扛得住這些流量,而後帶寬不被堵死。若是你家門被堵了,你還想開門營業,那真心沒有辦法了。
而後,作完流量上的防禦,讓咱們來看看對於掃描器之類的攻擊的防護。
0x03 防掃描
ngx_lua_waf模塊
這個是一個不錯的waf模塊,這塊咱們也就再也不重複造輪子了。能夠直接用這個模塊來作防禦,固然也徹底能夠再配合limit模塊,用上文的思路來作到一個封IP或者封session的效果。
0x04 總結
本文旨在達到拋磚引玉的做用,咱們並不但願你直接單純的複製咱們的這些例子中的配置,而是但願根據你的自身業務須要,寫出適合自身站點的配置文件。
文章來源:http://drops.wooyun.org/tips/734
如何打造一款可靠的WAF(Web應用防火牆)
以前寫了一篇《WAF防護能力評測及工具》,是站在安全運維人員選型WAF產品的角度來考慮的(優先從測試角度考慮是前職業病,畢竟當過3年遊戲測試?!)。本篇文章從WAF產品研發的角度來YY如何實現一款可靠的WAF,靈感來自ModSecurity等,感謝開源。
本片文章包括三個主題
(1) WAF實現
WAF包括哪些組件,這些組件如何交互來實現WAF防護功能
(2)WAF規則(策略)維護
規則(策略)如何維護,包括獲取渠道,規則測試方法以及上線效果評測
(3) WAF支撐
WAF產品的完善須要哪些信息庫的支撐
1、WAF實現
WAF一句話描述,就是解析HTTP請求(協議解析模塊),規則檢測(規則模塊),作不一樣的防護動做(動做模塊),並將防護過程(日誌模塊)記錄下來。無論硬件款,軟件款,雲款,核心都是這個,而接下來圍繞這句話來YY WAF的實現。WAF的實現由五個模塊(配置模塊、協議解析模塊、規則模塊、動做模塊、錯誤處理模塊)組成
1. 配置模塊
設置WAF的檢測粒度,按需開啓,如圖所示

2. 協議解析模塊(重點)
協議解析的輸出就是下一個模塊規則檢測時的操做對象,解析的粒度直接影響WAF防護效果。對於將WAF模塊寄生於web 服務器的雲WAF模式,通常依賴於web 服務器的解析能力。

3. 規則模塊(重點)
重點來了,這塊是WAF的核心,我將這塊又細分爲三個子模塊。
(1) 規則配置模塊
IP黑白名單配置、 URL黑白名單配置、以及挑選合適的規則套餐。

(2)規則解析模塊
主要做用是解析具體的規則文件,規則最好採用統一的規則描述語言,便於提供給第三方定製規則,ModSecurity這方面作得很是優秀。
規則文件由四部分組成,分爲變量部分、操做符部分,事務函數部分與動做部分。


(3)規則檢測模塊
上一步咱們設置了各類變量,接下來就是按照必定的邏輯來作加減乘除了。

4. 動做模塊(重點)
經過規則檢測模塊,咱們識別了請求的好惡,接下來就是作出響應,量刑處理,不只僅是攔截。

5. 日誌模塊(重點)
日誌處理,很是重要,也很是火熱,內容豐富到徹底能夠從WAF獨立出來造成單獨的安全產品(e.g.日誌寶)而採用提供接口的方式來支撐WAF。對於數據量巨大的雲WAF,都會有單獨的大數據團隊來支撐架構這一塊,包括數據存儲(e.g. hdfs) ,數據傳輸(kafka),數據離線分析(Hadoop/Spark),數據實時分析(storm),數據關聯分析(elasticsearch)等等,之後另開一篇單獨說明。

6. 錯誤處理模塊
以上模塊運行錯誤時的異常處理
2、WAF規則(策略)維護
WAF須要修煉一圖以蔽之

3、WAF支撐信息庫
WAF須要修煉一圖以蔽之

以上支撐庫幾乎全部的安全人員都在重複地作,而資源沒有共享的緣由,一是內部不可說;二是沒有采起統一的描述語言沒法匯合,唉,安全從業人員的巴別塔。
4、補充知識(包括文章與代碼)
想一想寫了這麼多文章,自我感受萌萌噠!
WAF相關
WAF防護能力評測及工具
ssdeep檢測webshell
ModSecurity相關文章(我就是ModSecurity的死忠粉)
[科普文]ubuntu上安裝Apache2+ModSecurity及自定義WAF規則
ModSecurity SecRule cheatsheets
ModSecurity CRS 筆記、WAF防護checklist,及WAF架構的一些想法
ModSecurity 晉級-如何調用lua腳本進行防護快速入門
ModSecurity 白名單設置
指紋識別
Web應用指紋識別
FingerPrint
IP相關
使用免費的本地IP地理庫來定位IP地理位置-GeoIP lookup
得到IP的地理位置信IP Geolocation及IP位置可視化
IP地理信息離線獲取腳本
IP地理信息在線獲取腳本
識別搜索引擎腳本
判斷使用哪家CDN腳本
代理類型判斷腳本 Proxy探測腳本與HTTP基本認證暴力破解腳本
CDN架構
網站負載均衡技術讀書筆記與站長產品的一點想法
正則優化
NFA引擎正則優化TIPS、Perl正則技巧及正則性能評測方法
HTTP發包工具
HTTP.pl——經過HTTP發包工具瞭解HTTP協議
HTTP發包工具 -HTTPie
WAF實現的思惟導圖
參考:
《ModSecurity Handbook》
第8、9、十,十一我是反覆看,每次都有新的靈感,第1四、15章是當成新華字典看的,以避免遺忘。
《Web Application Defenders Cookbook Battling Hackers and Protecting Users》 (紅寶書,還在看)
來源:http://www.freebuf.com/sectool/54221.html
基於ngx_lua模塊的waf開發實踐
0x00 常見WAF簡單分析
WAF主要分爲硬件WAF和軟件防火牆,硬件WAF如綠盟的NSFOCUS Web Application Firewall,軟件防火牆比較有名的是ModSecurity,再就是代碼級別的ngx_lua_waf。下面談談我的對幾款防火牆的理解:
硬件WAF我的以爲只適合在那種訪問量較少的網站,好比政府網站,公司的介紹網站等等。硬件WAF的的優點在於規則有專門的安全公司維護,管理方便,但也存在一個致命的弱點,使用傳統的方式來解包到應用層對性能的需求較高,並且當訪問量很大的時候延時比較大,這樣在高併發訪問的狀況下要使用硬件WAF就只能使用不少臺WAF了,這樣成本就很是高了;還有一個在接觸過程當中發現的問題,就是硬件WAF的規則雖然多並且有人維護,可是通常公司很難敢直接開啓阻難,不少都是隻記錄,並不能阻難,這樣WAF的意義就變得小多了。
ModSecurity在網上的評價都是很高的,性能高,規則全。最開始我研究的也是這款WAF,可是在實際使用過程當中發現問題,就是在高併發的狀況下,運行一段時間,會出現內存飆升,並且不下來的問題。這個問題再ModSecurity的討論論壇上面也發現了有人提出這樣的問題,但一直未解決(https://github.com/SpiderLabs/ModSecurity/issues/785)。針對於規則全的優點,通常使用者也不敢直接開啓全部的規則攔截,畢竟每一個公司的業務不一樣,規則也不可能直接套用。
基於高性能,低成本的想法,發現了@loveshell開發的ngx_lua_waf,通過實際使用下來,確實性能極好,因爲LUA語言的性能是接近於C的,並且ngx_lua_module自己就是基於爲nginx開發的高性能的模塊。安全寶的雲 WAF,以及cloudflare的新waf也是基於此模塊使用LUA開發的。結合ModSecurity的思路,參考@loveshell的ngx_lua_waf來開發適合本身用的WAF,其中使用了不少@loveshell的函數,再此也表示感謝。
0x01 WAF框架設計
WAF開發過程當中的主要方向爲:
- 主引擎的開發,主要關注主引擎的性能和容錯能力
- 規則的開發,主要關注規則的全面可靠,防勿攔截以及防繞過
- 總體方案可以適應多站點,高可用性的環境
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則根據規則的處理方式來進行處理,匹配到以後不繼續匹配後續規則。
- 須要開啓的功能依次在主函數中調用便可,順序也可根據實際場景來肯定最合適的順序。
圖示以下:

0x02 規則格式分析
規則說明:
好比規則:{"rule00001","rules","args|post|cookie",[[../]],"deny","logon"},
rule00001:規則編號,隨意寫
rules:規則名稱,如xssrules,隨意寫
args|post|cookie|header:檢測位置,|表示或,args,post,cookie,header可多選
../:匹配的正則表達式,標準PCRE正則
deny:處理方式,可選deny ,allow
logon:日誌記錄與否,可選logon,logoff
0x03 cc攻擊防禦代碼示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
--在nginx.conf的HTTP中加入
--lua_shared_dict limit 50m; 根據主機內存調合適的值
--lua_shared_dict iplimit 20m;
--lua_shared_dict blockiplimit 5m;
-------------------------------------------------------------
CCDeny=
"on"
--cc攻擊開關
CCrate=
"60/60"
--基於url的計數 次/秒
ipCCrate=
"600/60"
--基於ip的計數 次/秒
-------------------------------------------------
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
|
0x04 優點舉例
能夠很靈活的實現複雜的控制
好比我在個人我的網站上面就使用了這樣一個功能,後臺頁面須要特定useragent才能訪問。
代碼以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
--特定頁面允許特定useragent可訪問
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
|
能夠測試http://www.zhangsan.me/wp-admin/
只有在特定的useragent才能夠訪問此頁面,不然報403錯誤。
0x05 源碼下載及使用
源碼下載地址爲:http://pan.baidu.com/s/18QQya
環境搭建就參考:http://wiki.nginx.org/HttpLuaModule#Installation
waf使用主要就是配置config.lua
SecRuleEngine = "on" attacklog = "on" logpath = "/home/waflog/"
分別爲引擎是否開啓 是否記錄日誌 日誌的存儲路徑 日誌的存儲路徑須要給予nginx運行用戶的讀寫權限
0x06 後續研究方向
- 1.根據ModSecurity規則提取一份較適應本身用的規則
- 2.根據最新出現的漏洞維護規則
- 3.在多個站點的狀況下,若是在站點變更,規則變更的時候,不影響其餘站點,實現高可用性。
寫的很簡單,大牛勿噴,但願你們多提建議。
0x07 參考資料
1. https://github.com/loveshell/ngx_lua_waf
2. http://wiki.nginx.org/HttpLuaModule
3. http://www.freebuf.com/tools/54221.html
……
文章來源:http://drops.wooyun.org/tips/5136
ngx_lua_waf - 一個基於 lua-nginx-module 的 Web 應用防火牆
ngx_lua_waf
ngx_lua_waf是我剛入職趣遊時候開發的一個基於ngx_lua的web應用防火牆。
代碼很簡單,開發初衷主要是使用簡單,高性能和輕量級。
如今開源出來,聽從MIT許可協議。其中包含咱們的過濾規則。若是你們有什麼建議和想fa,歡迎和我一塊兒完善。
用途:
防止sql注入,本地包含,部分溢出,fuzzing測試,xss,SSRF等web攻擊
防止svn/備份之類文件泄漏
防止ApacheBench之類壓力測試工具的攻擊
屏蔽常見的掃描黑客工具,掃描器
屏蔽異常的網絡請求
屏蔽圖片附件類目錄php執行權限
防止webshell上傳
推薦安裝:
推薦使用lujit2.1作lua支持
ngx_lua若是是0.9.2以上版本,建議正則過濾函數改成ngx.re.find,匹配效率會提升三倍左右。
使用說明:
nginx安裝路徑假設爲:/usr/local/nginx/conf/
把ngx_lua_waf下載到conf目錄下,解壓命名爲waf
在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;
配置config.lua裏的waf規則目錄(通常在waf/conf/目錄下)
RulePath = "/usr/local/nginx/conf/waf/wafconf/"
絕對路徑若有變更,需對應修改
而後重啓nginx便可
配置文件詳細說明:
RulePath = "/usr/local/nginx/conf/waf/wafconf/"
--規則存放目錄
attacklog = "off"
--是否開啓攻擊信息記錄,須要配置logdir
logdir = "/usr/local/nginx/logs/hack/"
--log存儲目錄,該目錄須要用戶本身新建,切須要nginx用戶的可寫權限
UrlDeny="on"
--是否攔截url訪問
Redirect="on"
--是否攔截後重定向
CookieMatch = "on"
--是否攔截cookie攻擊
postMatch = "on"
--是否攔截post攻擊
whiteModule = "on"
--是否開啓URL白名單
black_fileExt={"php","jsp"}
--填寫不容許上傳文件後綴類型
ipWhitelist={"127.0.0.1"}
--ip白名單,多個ip用逗號分隔
ipBlocklist={"1.0.0.1"}
--ip黑名單,多個ip用逗號分隔
CCDeny="on"
--是否開啓攔截cc攻擊(須要nginx.conf的http段增長lua_shared_dict limit 10m;)
CCrate = "100/60"
--設置cc攻擊頻率,單位爲秒.
--默認1分鐘同一個IP只能請求同一個地址100次
html=[[Please go away~~]]
--警告內容,可在中括號內自定義
備註:不要亂動雙引號,區分大小寫
檢查規則是否生效
部署完畢能夠嘗試以下命令:
curl http://xxxx/test.php?id=../etc/passwd
返回"Please go away~~"字樣,說明規則生效。
注意:默認,本機在白名單不過濾,可自行調整config.lua配置
效果圖以下:


規則更新:
考慮到正則的緩存問題,動態規則會影響性能,因此暫沒用共享內存字典和Redis之類東西作動態管理。
規則更新能夠把規則文件放置到其餘服務器,經過crontab任務定時下載來更新規則,nginx reload便可生效。以保障ngx lua waf的高性能。
只記錄過濾日誌,不開啓過濾,在代碼裏在check前面加上--註釋便可,若是須要過濾,反之
一些說明:
過濾規則在wafconf下,可根據需求自行調整,每條規則需換行,或者用|分割
args裏面的規則get參數進行過濾的
url是隻在get請求url過濾的規則
post是隻在post請求過濾的規則
whitelist是白名單,裏面的url匹配到不作過濾
user-agent是對user-agent的過濾規則
默認開啓了get和post過濾,須要開啓cookie過濾的,編輯waf.lua取消部分--註釋便可
日誌文件名稱格式以下:虛擬主機名_sec.log
Copyright
感謝ngx_lua模塊的開發者@agentzh,春哥是我所接觸過開源精神最好的人
來源:https://github.com/loveshell/ngx_lua_waf
ngx_lua_waf針對性改寫
當初選擇ngx_lua_waf做爲本身的WAF,主要緣由就是由於其可擴展性與性能上有一個很好的平衡。
lua語言的靈活性與效率是不少腳本層WAF無可匹及的。
ngx_lua_waf自身是比較簡單的,並且存在不少誤報、漏報、繞過的現象,我整理以下,來改進本身的waf。
1.debug函數
預備一個debug函數,方便之後調試。由於waf運行在後臺,因此看不到輸出,最好以日誌的形式寫到文件中。
2 |
local file = io.open("/tmp/debug.log","a") |
2.waf能夠用hpp進行繞過
做爲做者一處筆誤(我認爲的),我提交到烏雲了:
http://wooyun.org/bugs/wooyun-2010-0104525
等公開了,能夠用裏面的方法修改。
3.利用白名單繞過
wafconf/whiteurl中,白名單URL直接是/123/
而後在函數whiteurl中
03 |
if wturlrules ~=nil then |
04 |
for _,rule in pairs(wturlrules) do |
05 |
if ngxmatch(ngx.var.request_uri,rule,"ijom") then |
用的是ngx.var.request_uri和這個"/123/"進行比較,只要uri中存在/123/就做爲白名單不進行檢測,這樣咱們能夠經過/waf.php?a=/123/&b=../etc/passwd 繞過防護規則。
因此,將/123/改爲^/123/
這樣只有以/123/開頭的uri才能進入白名單。
4.正則是m仍是s
WAF繞的多的人必定知道正則裏「.」表明什麼意義。
正常狀況下,.匹配的是「不含換行」的全部字符。因此有些WAF用這樣的正則:
union.*select
來攔截注入。咱們就能夠經過union%0aselect,中間一個換行來繞過。
因此,如今通常的WAF都會用s來修飾正則。s的意思就是single,也就是單行模式。
說白了,加了s修飾,則「.」就會匹配換行了。
而咱們的ngx_lua_waf中,全部的正則都用的m來修飾的,m的意思是multiple,多行的意思,也就是默認的.不匹配換行。 (注:這樣理解是錯的,詳見評論。)
而咱們的ngx_lua_waf中,並無使用i修飾正則,因此默認.是匹配多行的,也就是默認的.不匹配換行。
好比對GET變量的攔截:
02 |
for _,rule in pairs(argsrules) do |
03 |
local args = ngx.req.get_uri_args() |
04 |
for key, val in pairs(args) do |
05 |
if type(val)=='table' then |
07 |
data=table.concat(val, " ") |
12 |
if data and type(data) ~= "boolean" and rule ~="" and ngxmatch(unescape(data),rule,"imjo") then |
13 |
log('GET',ngx.var.request_uri,"-",rule) |
可見ngxmatch(unescape(data),rule,"imjo"),用的是imjo來修飾。咱們用union%0aselect就能繞過WAF:

5.誤殺誤殺!上傳文件的誤殺。
對HTTP協議瞭解的同窗必定內心清楚,POST的類型是分兩種的:application/x-www-form-urlencoded和multipart/form-data
前一種是默認POST數據的時候使用的,服務器獲取了數據後會進行url解碼。後一種通常是上傳的時候纔會使用,服務器獲取數據後不會進行url解碼,因此咱們能直接上傳二進制文件。
php在上傳過程當中,上傳文件的表單會放進$_FILES變量,其餘POST表單會放進$_POST變量,和直接application/x-www-form-urlencoded的效果同樣。
這部分POST變量在lua中須要特殊處理,原ngx_lua_waf的做者也考慮了,具體攔截代碼可見waf.lua。
但做者處理的太草率,直接把整個數據包,一點一點丟進body函數裏檢測。這樣形成了兩個問題:
①. 數據包一部分一部分發過來,他就一部分一部分丟進body裏檢測。那麼若是union、select兩個連在一塊兒的關鍵詞正好從中間某位置分開,好比"unio"和"n select",這兩個包分別檢測都是正常的。但實際發送到php裏的時候是連在一塊兒的,致使繞過WAF。
②. 文件裏的特殊字符也被攔截了,所謂的誤殺。有時候咱們要上傳一些文件,文件裏可能會有html標籤,或SQL語句,這裏他將上傳表單的內容也放入body檢測了,致使不少文件上傳不了。
我對上述問題作了修改與處理,不過代碼太多我就不寫在文章裏了。思路就是這樣:
首先將完整的數據包獲取下來,並用boundary將他們分割成數組。遍歷數組,只對進入POST變量的值進行攔截,不攔截FILE內容。但須要攔截FILE表單中的"filename=xxx"的部分。
6.人性化提示信息
雖然個人WAF攔截的80%是攻擊者,但也可能有正常訪客。這時候我就須要告訴訪客,你輸入了哪些東西不合理被我攔截(誤殺)了,你能夠換個方式輸入或通知我。
我在init.lua靠前的位置加入以下代碼:
1 |
local fd = io.open(file403,"r") |
file403是我本身寫的403頁面,讀取之。並將say_html函數改爲這個:
01 |
function say_html(reason) |
04 |
ngx.header.content_type = "text/html" |
05 |
nowhtml = string.gsub(nowhtml, "{ip}", ngx.var.remote_addr) |
06 |
nowhtml = string.gsub(nowhtml, "{host}", ngx.var.host) |
07 |
nowhtml = string.gsub(nowhtml, "{reason}", reason) |