[轉]Web應用防火牆WAF詳解

 

經過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的檢測粒度,按需開啓,如圖所示

WAF的實現 - 碳基體 - 碳基體

2. 協議解析模塊(重點)

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

WAF的實現 - 碳基體 - 碳基體

3. 規則模塊(重點)

重點來了,這塊是WAF的核心,我將這塊又細分爲三個子模塊。

(1) 規則配置模塊

IP黑白名單配置、 URL黑白名單配置、以及挑選合適的規則套餐。

WAF的實現 - 碳基體 - 碳基體

(2)規則解析模塊

主要做用是解析具體的規則文件,規則最好採用統一的規則描述語言,便於提供給第三方定製規則,ModSecurity這方面作得很是優秀。

規則文件由四部分組成,分爲變量部分、操做符部分,事務函數部分與動做部分。

WAF的實現 - 碳基體 - 碳基體

WAF的實現 - 碳基體 - 碳基體

(3)規則檢測模塊

上一步咱們設置了各類變量,接下來就是按照必定的邏輯來作加減乘除了。

WAF的實現 - 碳基體 - 碳基體

4. 動做模塊(重點)

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

WAF的實現 - 碳基體 - 碳基體

5. 日誌模塊(重點)

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

WAF的實現 - 碳基體 - 碳基體

6. 錯誤處理模塊

以上模塊運行錯誤時的異常處理

2、WAF規則(策略)維護

WAF須要修煉一圖以蔽之

WAF的實現 - 碳基體 - 碳基體

3、WAF支撐信息庫

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則根據規則的處理方式來進行處理,匹配到以後不繼續匹配後續規則。
  • 須要開啓的功能依次在主函數中調用便可,順序也可根據實際場景來肯定最合適的順序。

圖示以下:

enter image description here

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配置

效果圖以下:

sec

sec

規則更新:

考慮到正則的緩存問題,動態規則會影響性能,因此暫沒用共享內存字典和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

Weibo 神奇的魔法師
Forum http://bbs.linuxtone.org/
Copyright Copyright (c) 2013- loveshell
License MIT License

感謝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運行在後臺,因此看不到輸出,最好以日誌的形式寫到文件中。
1 function debug(info)
2     local file = io.open("/tmp/debug.log","a")
3     file:write(info.."\n")
4     file:close()
5 end

2.waf能夠用hpp進行繞過
    做爲做者一處筆誤(我認爲的),我提交到烏雲了: http://wooyun.org/bugs/wooyun-2010-0104525
    等公開了,能夠用裏面的方法修改。

3.利用白名單繞過
    wafconf/whiteurl中,白名單URL直接是/123/
    而後在函數whiteurl中
01 function whiteurl()
02    if WhiteCheck then
03        if wturlrules ~=nil then
04            for _,rule in pairs(wturlrules) do
05                if ngxmatch(ngx.var.request_uri,rule,"ijom") then
06                    return true
07                 end
08            end
09        end
10    end
11    return false
12 end
    用的是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變量的攔截:
01 function args()
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
06                 if val == false then
07                     data=table.concat(val, " ")
08                 end
09             else
10                 data=val
11             end
12             if data and type(data) ~= "boolean" and rule ~="" and ngxmatch(unescape(data),rule,"imjo") then
13                 log('GET',ngx.var.request_uri,"-",rule)
14                 say_html()
15                 return true
16             end
17         end
18     end
19     return false
20 end

    可見ngxmatch(unescape(data),rule,"imjo"),用的是imjo來修飾。咱們用union%0aselect就能繞過WAF:

    QQ20150329-5@2x.png


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")
2 if fd == nil then
3     html = [[403 error!!]]
4 else
5     html = fd:read("*a")
6     fd:close()
7 end
file403是我本身寫的403頁面,讀取之。並將say_html函數改爲這個:
01 function say_html(reason)
02     if Redirect then
03         local nowhtml = html
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)
08
相關文章
相關標籤/搜索