經過nginx配置文件抵禦攻擊

囧思九千 · 2013/11/12 12:22nginx

0x00 前言


你們好,咱們是OpenCDN團隊的Twwy。此次咱們來說講如何經過簡單的配置文件來實現nginx防護攻擊的效果。git

其實不少時候,各類防攻擊的思路咱們都明白,好比限制IP啊,過濾攻擊字符串啊,識別攻擊指紋啦。但是要如何去實現它呢?用守護腳本嗎?用PHP在外面包一層過濾?仍是直接加防火牆嗎?這些都是防護手段。不過本文將要介紹的是直接經過nginx的普通模塊和配置文件的組合來達到必定的防護效果。github

0x01 驗證瀏覽器行爲


簡易版

咱們先來作個比喻。web

社區在搞福利,在廣場上給你們派發紅包。而壞人派了一批人形的機器人(沒有語言模塊)來冒領紅包,聰明工做人員須要想出辦法來防止紅包被冒領。shell

因而工做人員在發紅包以前,會給領取者一張紙,上面寫着「紅包拿來」,若是那人能念出紙上的字,那麼就是人,給紅包,若是你不能念出來,那麼請自覺。因而機器人便被識破,灰溜溜地回來了。瀏覽器

是的,在這個比喻中,人就是瀏覽器,機器人就是攻擊器,咱們能夠經過鑑別cookie功能(念紙上的字)的方式來鑑別他們。下面就是nginx的配置文件寫法。服務器

#!shell
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世界中。cookie

固然,這麼簡單就能防住了?固然沒有那麼簡單。session

加強版

仔細的你必定會發現配置文件這樣寫仍是有缺陷。若是攻擊者設置cookie爲say=hbnl(CC攻擊器上就能夠這麼設置),那麼這個防護就形同虛設了。咱們繼續拿剛剛那個比喻來講明問題。框架

壞人發現這個規律後,給每一個機器人安上了揚聲器,一直重複着「紅包拿來,紅包拿來」,浩浩蕩蕩地又來領紅包了。

這時,工做人員的對策是這樣作的,要求領取者出示有本身名字的戶口本,而且念出本身的名字,「我是xxx,紅包拿來」。因而一羣只會嗡嗡叫着「紅包拿來」的機器人又被攆回去了。

固然,爲了配合說明問題,每一個機器人是有戶口本的,被趕回去的緣由是不會念本身的名字,雖然這個有點荒誕,唉。

而後,咱們來看下這種方式的配置文件寫法

#!shell
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模塊來進行實現。

#!shell
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,能夠嗎?能夠的。

#!shell
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模塊來完成。

#!shell
http{
    ...
    limit_req_zone $cookie_token zone=session_limit:3m rate=1r/s;
}
複製代碼

而後咱們只須要在上面的token配置後面中加入

#!shell
limit_req zone=session_limit burst=5;
複製代碼

因而,又是兩行配置便讓nginx在session層解決了請求頻率的限制。不過彷佛仍是有缺陷,由於攻擊者能夠經過一直獲取token來突破請求頻率限制,若是能限制一個IP獲取token的頻率就更完美了。能夠作到嗎?能夠。

#!shell
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;
}
複製代碼


#!shell
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 總結


本文旨在達到拋磚引玉的做用,咱們並不但願你直接單純的複製咱們的這些例子中的配置,而是但願根據你的自身業務須要,寫出適合自身站點的配置文件。

相關文章
相關標籤/搜索