Rails 如何用 Rack::Attack 對付駭客 hacking

紅寶鐵軌客
來關注...
關注/停止關注:紅寶鐵軌客
關注有什麼好處?:當作者有新文章發佈時,「思書」就會自動通知您,讓您更容易與作者互動。
現在就加入《思書》,你就可以關注本作者了!
《思書》是一個每個人的寫作與論壇平台,特有的隱私管理,讓你寫作不再受限,討論更深入真實,而且免費。 趕快來試試!
還未加入《思書》? 現在就登錄! 已經加入《思書》── 登入
寫程式中、折磨中、享受中 ......
1.32k   0  
·
2019/02/18
·
15分鐘


如果你有看過你的 web server log,你一定會很驚訝,怎麼會有那麼多的奇怪要求,他們有時會來要求一些不存在的網頁,有時十秒內要求同一個網頁幾十次甚至上百次,還有很多要來讀 /adm 的,這些在我看來,都應該是要被認為是 hacker 的行為,他們很討厭,想要用這些笨方法來試出你網頁的漏洞,看你是不是沒做好基本的保護,目的應該不外乎炫耀、偷網站的資料,或是,植入木馬,不管他們有沒有成功,這些「夭壽鬼」至少佔用了你付錢買的頻寬及讓你的 server 負擔加重,至少讓你的網站反應變慢,真的很討厭,我們有沒有辦法對付他們呢?

先來看看有多少夭壽鬼

最簡單的方法就是看你的 rails server log,它通常就在你的 /log 裡,只是你要一條一條的看,很累啊,當你發現一些可疑的網站時,你可以用:

grep -B 3 -A 2 "夭壽鬼ip" 你的server.log

來更簡單的查出到底這個  ip 來做了什麼,加一個 -c,你就可以知道這個夭壽鬼來了幾次,你一定會很驚訝,怎麼那麼多啊!

查 log 總是麻煩,我覺得一定要裝的工具就是:logwatch,這是個老工具了,如果你還沒裝,強烈建議,網站上很多參考,如果你是用最流行了 ubuntu,以下的這篇文章寫得不錯:

How to install and use Logwatch on Ubuntu 14.04 — When you know about the normal state of your network and servers, then you can easily spot the abnormal by just monitoring its logon activity. So, In this tutorial we will guide you on the complete setup of a tool that will analyze your logs and reports back about every activity on your servers. Logwatch is an VEXXHOST

執行了 logwatch 後,你就可以在報告的 httpd 那部分,看到到底有那些來搞亂的 http 要求,你可能不看還好,看了以後一定昏倒,怎麼有那麼多的夭壽鬼啊,如果你還順便看了一下 ssh,你會更昏倒,這世界怎麼有那麼多夭壽鬼,真希望有個方法對付他們,可惜的是,沒有一個一勞永逸的方法,但是 Rails 有一個還不錯的 gem 可以幫你只擋一部分,就是 Rack:Attack,我們接下來就來看看,怎麼用 Rack::Attack 來做一些基本的防禦。

Rack::Attack

這個 gem 使用很簡單,下面就是它的官網連結:

kickstarter/rack-attack — Rack middleware for blocking & throttling. Contribute to kickstarter/rack-attack development by creating an account on GitHub.
GitHub

各位一定乖乖的好好的看官方正式說明啦,不能只依靠這篇文章,我可不要付那麼大的責任啊,我這篇只是「輔助」,雖然,這是我認我我寫的比較好懂,貼近使用目的⋯⋯

要對付夭壽鬼駭客,當然就要先認識那些是夭壽鬼,Rack::Attack 基本上只能

  1. 擋掉你認得出來的夭壽鬼,
  2. 擋掉來太多次的夭壽鬼

而且,你要擋得很小心,不要把你可愛的顧客給擋掉了,也不要擋掉你可能很需要的 google 機器人,那麼,我們來看看有那些是有可能的夭壽鬼呢?

  1. 密集進來的:例如,同一個 ip,在 10 秒鐘內來讀取 10 次以上的:在正常清況下,不可能有顧客 10 秒鐘內來讀你網站 10 吧,我能想到的唯一「合法」有可能的是搜尋引擎機器人,不過機會不大啦,我的觀察,google 機器人來的都很含蓄,很少有一分鐘兩三次的。
  2. 一直來試管理類網址的:你在 log watch 報告中,會看到很多奇怪的網址要求,明明我們的網站就是用 rails 寫的,你來要 php?還有來要找 phpMyAdmin的,更可惡的是,要來找 admin 或是 adm 的,這不是夭壽鬼誰才是!
  3. 登入失敗又一直再試的:這可能不一定是夭壽鬼,有可能是用戶忘記密碼了,但是如果一分鐘內試 60 次的,就很可能一定是,畢竟,那有「人」可以打密碼那麼快的,所以,也要設個時限內最多可試的登入數目,防止夭壽鬼!

這是我目前有想到的,也許還有其他要擋的,也歡迎大家提出分享。 接下來,我們就來安裝 Rack::Attack 吧。

安裝 Rack::Attack

加入 gem: 就跟其他的 gem 一樣,在 gem file 中加入 gem 'rack-attack',再執行:bundle,你就可以看到 Installing rack-attack x.x.x 的訊息,然後你要在 config/application.rb 中加入一行:config.middleware.use Rack::Attack,這樣,你就裝好 Rack::Attack 了。

使用 Rack::Attack

要使用 Rack::Attack,只要在 config/initializers,加入 Rack::Attak 的設定檔,檔案的名稱是 rack_attack.rb,如同我一向的慣例,放碼!畢竟,碼說話最清楚了!

class Rack::Attack

  ### Configure Cache ###

  # 我建議用 file store

  ### 1. 限制:討厭鬼密集進來的次數 ###
  # 十分鐘內來50次的,就不給他再試
  throttle('max_req/ip', limit: 50, period: 10.minutes) do |req|
    req.ip unless req.path.start_with?('/assets')
  end

  ### 2. 限制:一直來試管理類網址的討厭鬼 ###
  # 十分鐘內試2次的,就給他關監牢三十分鐘
  Rack::Attack.blocklist('fail2ban pentesters') do |req|
    Rack::Attack::Fail2Ban.filter("pentesters-#{req.ip}", maxretry: 2, findtime: 10.minutes, bantime: 30.minutes) do
      CGI.unescape(req.query_string) =~ %r{/etc/passwd} ||
      req.path.include?('/etc/passwd') ||
      req.path.include?('wp-admin') ||
      req.path.include?('wp-login') ||
      req.path.include?('/adm/') ||
      req.path.include?('.php') ||
      req.path.include?('.asp')
    end
  end

  ### 3. 限制:登入失敗又一直再試的 ###
  # 兩分鐘內登入10次的,就給他關監牢六十分鐘
  Rack::Attack.blocklist('allow2ban login scrapers') do |req|
    Rack::Attack::Allow2Ban.filter(req.ip, maxretry: 10, findtime: 2.minutes, bantime: 60.minutes) do
      req.path == '/login' and req.post?
    end
  end

  ### 客製化回應 ###
  # 客製化黑名單回應,
  Rack::Attack.blocklisted_response = lambda do |env|
    # 不設定的話是 403: forbidden,你也可以選其他的,如503: Service Unavailable,這樣可以讓討厭鬼以為他成功了
    [ 503, {}, ['Blocked']]
  end
  # 客製化流量回應,
  Rack::Attack.throttled_response = lambda do |env|
    # 不設定的話是 429: Too Many Requests,你也可以選其他的,如503: Service Unavailable,這樣就跟前面一樣,讓討厭鬼以為他成功了
    [ 403, {}, ['Blocked! Too Many Requests']]
  end

end

我已經把說明加上了,所以程式碼應該很容易讀了,但是也有一些要注意的:

cache 一定要設好

Rack::Attack 的 Throttle,allow2ban 以及 fail2ban 這些功能是要使用 Rails cache 的,所以 cache 一定要設定好,我如同已經介紹過的 Rails Cache 文章,單一 server 的網站就用 file store 就好啦,簡單又快速,很多台 server 的大網站,也應該不用看這篇了,防止駭客要做的工作,絕不只是裝這一個 gem,這只能算是基本啦,還沒有看過我的 Rails Cache 文章的人,如果還不熟 rails cache ,不嫌棄可以看看:

Rails 的 cache 介紹一:cache stores — 在 Rails ,最讓其他平台使用者攻擊的就是網站執行效率,效率這件事,有很多影響因素,像是 Ruby 的慢就是其中一個重要因素,但是...
Scrivinor 思書: 紅寶鐵軌客

Rails 的 cache 介紹二:網頁 caching — 前一篇文章介紹 cache store,如果你還未看,我建議先看,很多設定與選擇要做: Rails 的 cache 介紹一:cache...
Scrivinor 思書: 紅寶鐵軌客

再來,我們沒有介紹:safelisting 跟 track,這兩個都是不錯的功能,可以設定在 rack_attack.rb 中,寫在越上面,就越有優先權,所以你如果有一些 ip 是永遠都不想被擋的,可以用 saftlisting 加在最前面,另外, 你也可以用 Rack::Attack 的 track 功能來追蹤一些用戶的使用狀況,這些設定都很簡單,我這裡就不說明了,你可以看一下官網,或是以下這篇文章(也是 Rack::Attack 的作者寫的):

Rack::Attack: protection from abusive clients – Kickstarter Engineering — I’m excited to introduce Rack::Attack, a ruby rack middleware for throttling abusive requests. We depend on it to keep Kickstarter fast and reliable. If you’ve looked at web server logs, you know… Kickstarter Engineering

還有是有關登入限制的設定,如何管制登入失敗又一直再試的討厭鬼這部分,我們使用的是 blocklist + allow2ban,你如果覺得太麻煩了,可以只用 blocklist 或是 throttle,官網上都有範例,只是,只用 blocklist 或是 throttle 的話,你會發現,被擋下來的 ip 只要在執行幾個正常的網頁,或是等一下,就又可以繼續搞怪了,我不喜歡,但也許合乎你的要求,就看你的需求了。

不能在 Proxy 後運作:如果你的網站是架在 proxy 後(如 Cloudflare),你可以參考以下這篇文章,這篇文章也介紹了更進一步有效防止 DDoS 的方法。

How to mitigate DDoS using Rack::Attack | BigBinary Blog — Throttle requests based on IP with Rack::Attack
BigBinary Blog

再來,就是關於如何設定客製化回應中的 HTTP 狀態碼,這是一個有趣的問題, 基本上,四字頭都是客戶端有問題,五字頭都是伺服器端有問題,看你要回什麼,回四字頭就是告訴討厭鬼,你被抓到了,回五字頭就是裝不知道,看你自己的選擇,我喜歡四字頭就是了。

把討厭鬼留下紀錄

你當然可以不留下任何記錄,不過我是認為這樣太大膽了,如果你有任何設定錯誤,把不該擋下來的也擋下來,那就有可能會造成用戶抱怨或是流量流失,要把紀錄那些討厭鬼被擋下來也很簡單,Rack::Attack 是使用 ActiveSupport::Notifications,如果你對它不熟悉,以下這篇教學真的很棒,我相信你一看就懂:#249 Notifications in Rails 3 - RailsCasts: Short Ruby on Rails screencasts containing tips, tricks and tutorials. Great for both novice and experienced web developers.

你可以將紀錄寫到資料庫中,但是我覺得先記在 server log 中就好,畢竟,寫資料到資料庫中,還是要花資源,還是一樣,以下是如何將討厭鬼在 server log 中紀錄,放碼出來:

# 紀錄討厭鬼
ActiveSupport::Notifications.subscribe('rack.attack') do |name, start, finish, request_id, payload|
  Rails.logger.info "[Rack::Attack] " <<
                      "blocked by: \"#{payload.env["rack.attack.matched"]}\", " <<
                      "ip: \"#{payload.ip}\", " <<
                      "env client ip: \"#{payload.get_header("HTTP_CLIENT_IP")}\", " <<
                      "env forwarded for: \"#{payload.get_header("HTTP_X_FORWARDED_FOR")}\", " <<
                      "env via: \"#{payload.get_header("HTTP_VIA")}\", " <<
                      "env remote addr: \"#{payload.get_header("REMOTE_ADDR")}\", " <<
                      "scheme: \"#{payload.get_header("rack.url_scheme")}\", " <<
                      "method: \"#{payload.env["REQUEST_METHOD"]}\", " <<
                      "params: \"#{payload.params.inspect}\", " <<
                      "URI: \"#{payload.env["REQUEST_URI"]}\", " <<
                      "agent: \"#{payload.env["HTTP_USER_AGENT"]}\" "
end

Rack::Attack 顧名思義,就是是一個 Rack 上的服務,Rack::Attack 會將 Rack 的 Request 包起來再加上以下這些資訊:

 # env['rack.attack.matched'] # 擋下來的名稱
# env['rack.attack.match_type'] # 型態
# env['rack.attack.match_data'] # 資料
# env['rack.attack.match_discriminator'] # 網頁回應

玩一下,應該就很清楚什麼是什麼了。 Rack 的 Request 說明在這:Class: Rack::Request — Documentation for rack

有一點比較怪的是,當用 Rack::Attack.blocklist() 來擋特定的 IP 時,payload 會是空的,也就是說 ip, env, header 等等都不會傳進來,所以你有用到 blocklist(),你在必須要在紀錄時,先檢查一下 method 是不是 undefined,可以用 payload.try(:env).present?  

我們把討厭鬼記錄下來的目的,我認為最主要的就是防止誤判,最簡的的檢查方法就是用 grep 來掃出我們要的 log 資料:

grep -c "pentesters" web_server.log
grep -B 3 -A 2 "pentesters" puma.log

在 server 上,打入以上第 01 行就可以知道到底有幾個要求被擋下來,如果有,再用第 02 行去細看,如果你是 copy paste 我的碼,你就可以把 pentesters 換成 allow2ban 或是 max_req,這樣就知道各個被擋的數字跟是誰了,想看個幾天應該就知道有沒有誤判了。 下面這篇有更多簡單的 log 讀取方法:


Analyzing Linux Logs - — There’s a great deal of information waiting for you within your logs, although it’s not always as easy as you’d like to extract it. In this section, we will cover some examples of basic analysis you can do with your logs right away (just search what’s there). We’ll also cover more advanced analysis that may take some upfront effort to set up properly, but will save you time on the... The Ultimate Guide to Logging

對抗 DDoS 的 404 Not Found

DDoS 就是討厭鬼開始亂讀你的伺服器網址,這包含了存在與不存在的網址,如果你有注意到你的 log 中出現一大堆的 Routing Error,很有可能就是 DDoS 的徵兆,討厭鬼開始亂讀不存在的網址了,在它被 Rack::Attack 擋下來前(也可能討厭鬼沒讀超過門檻,沒擋到),我們要怎麼辦呢?這你就要想想了,看你的需求,一般有以下幾個方法:

  1. 不理它,就讓網頁就顯示「錯誤」
  2. 通通把不存在的網址導引導主頁,或是一個特定頁面,一般稱為:Catch all route
  3. 照標準,就回應 404:網頁不存在「Not Found」

這三種裡面我覺得第三個回應 404 最好,第一個不理它絕不是一個正確的答案,反正也不用做任何事,所以我們就不討論了,以下我們來看看二跟三的做法:

把不存在的網址導引到一個特定頁面:

這寫法很簡單,就在你的 routes.rb 最底下加上以下這行:

match "*nopath", to: "application#nopath_action", via: :all

這是用到了 route 的鬼牌功能,因為這個 match 是在 routes.rb 的最下面,所以就是前面所有的路徑都沒有配對到,這時,*nopath 前面的*鬼牌就會擋下任何路徑要求,轉到 applicatoin controller 的 nopath_action,還會把路徑經由 params[:nopath] 傳來,接下來要做什麼就看你了,可以顯示一個討厭鬼的畫面,也可以連到主頁,不過,這有個實務上的小問題,對搜尋引擎來說,這並不正確,他應該是 404 不存在! Rails guide 的說明:Rails Routing from the Outside In — Ruby on Rails Guides:

照標準,就回應 404:網頁不存在「Not Found」

我覺得這個方法最好,因為事實上就是「找不到」,所以最合網站規矩與標準,上面也提到搜尋引擎的考量,搜尋引擎,特別是 google,很喜歡來找不存在的網址,google 也建議,找不到就回應 404 ,有興趣的可以讀讀:

Google's John Mueller Explains Why Google Crawls Non-Existent Pages - Search Engine Journal — Google's John Mueller offers good news for web publishers concerned about why Google crawls 404 and 410 pages. Search Engine Journal

你當然可以用前面導引的方法來寫,但是以下這個寫法更簡單,就在你的 routes.rb 最底下加上以下這行:

match '*path', to: proc { [404,  {}, ["NOT found"]]}, via: :all

這個寫法很酷,其中的[404, {}, ["xxx"]] 其實就是 Rack response 的 [status, {headers}, [body]],所以你就可以很簡單快速的寫一個文字或網頁回應,上面的例子就是一個簡單的文字回應,你也可以寫成:[404, {'Content-Type' => 'text/html; charset=utf-8'}, ["<div><b>Not Found....</b></div>"]],這樣就是一個 html 網頁了。

結語:

實務上,你會很驚訝的發現,真的有很多討厭鬼,我裝上的第一個 12 小時,就攔截成功上千個討厭鬼,感覺真不錯,我相信,應該有很多網站,每天的討厭鬼數目都是以千來記的,上萬也不稀奇,所以,裝 Rack::Attack 是一個很有價值的投資,想想你可以省下多少頻寬與提高伺服器效率,真是一個簡單好用很棒的寶石。

為什麼不用 Linux 的 fail2ban 就好?

看到這,如果你對 Linux 的 fail2ban 熟悉,我想你一定會問,那為什麼不用 fail2ban 就好?其實我一開始有一樣的想法,只是後來我發現我不大會用 fail2ban,fail2ban 的觀念很簡單,但是在設定上,對我來說,真的有些難搞,不過我相信,如果能用 fail2ban,效率應該會比用 Rack::Attack 好,畢竟它是在 app server 前,但是,後來我還是決定用 Rack::Attack 的主要原因有:

  1. Rack::Attack 對 Rails 來說,可以有更細的判斷,例如,login 成功與否,user ID 等,也許用到的機會不多,不過、總或許會有機會用到。
  2. Rack::Attack 是一個 Rack 的 service,架在 Rack 上,不是寫在 Rails 內,效能也不會比 fail2ban 差多少,所以光就效率而言,應該不是一個主要考量點。
  3. Fail2ban 對我來說,很難搞... 我喜歡 Rack::Attack,好啦,這點不算。

不過,就算用了 Rack::Attack,你還是應該要裝好 Fail2ban,除了 httpd 那部分可以靠 Rack::Attack,其他都要好好的保護好,不然,光 web server 保護了,討厭鬼從 SSH 進來,死得更慘。  下面這篇就是怎麼用 Fail2Ban 的一篇不錯的文章:用 Fail2Ban 防範暴力破解 (SSH、vsftp、dovecot、sendmail): Fail2Ban 可以用來防護 Linux Server 上的 SSH、vsftp、dovecot…等服務…

如果還是想用 Fail2Ban,這兩篇不錯: 如何配置 fail2ban 来保护 Apache 服务器How To Protect an Apache Server with Fail2Ban on Ubuntu 14.04

防止討厭鬼是一條不歸路,還有很多 tools (例如:mod_security 等),也希望這是拋磚引玉,大家可以分享一些好用的工具或服務。

就寫到這了,希望大家使用愉快。


喜歡作者的文章嗎?馬上按「關注」,當作者發佈新文章時,思書™就會 email 通知您。

思書是公開的寫作平台,創新的多筆名寫作方式,能用不同的筆名探索不同的寫作內容,無限寫作創意,如果您喜歡寫作分享,一定要來試試! 《 加入思書》

思書™是自由寫作平台,本文為作者之個人意見。


文章資訊

本文摘自:
Categories:
Tags:
Date:
Published: 2019/02/18 - Updated: 2020/07/27
Total: 4237 words


分享這篇文章:
關於作者

很久以前就是個「寫程式的」,其實,什麼程式都不熟⋯⋯
就,這會一點點,那會一點點⋯⋯




參與討論!
現在就加入《思書》,馬上參與討論!
《思書》是一個每個人的寫作與論壇平台,特有的隱私管理,用筆名來區隔你討論內容,讓你的討論更深入,而且免費。 趕快來試試!
還未加入《思書》? 現在就登錄! 已經加入《思書》── 登入


看看作者的其他文章


看看思書的其他文章



×
登入
申請帳號

需要幫助
關於思書

暗黑模式?
字體大小
成人內容未過濾
更改語言版本?