2015年5月17日 星期日

遭 ssh 暴力攻擊的防禦實戰記錄

最近半個月我的主機遭 ssh 暴力攻擊狀況 如果你有用 ssh 連線在管理某些主機, 請一定要不時查看一下 /var/log/auth.log。 粗心散漫的貴哥管理系上教學主機已經十幾年, 竟然直到最近才注意到有人持續用暴力嘗試所有密碼的方式, 企圖入侵我的主機的 root 帳號, 有時每小時的攻擊嘗試高達兩千多次! 還好我的 root 密碼夠亂, 不然早就毀了。 不論你的主機有沒有遭到攻擊、 沒空研究的讀者請至少照著第一節做。

[2016/4/4: 改推薦 fail2ban]

一、 ssh 的基本防護

防護 ssh 的基本方式有好幾招。

強力推薦第一招: 安裝 denyhosts 套件。 光是下 apt-get install denyhosts 這個指令, 就算完全沒有後續設定, 你的伺服器風險馬上就已降低百倍! 它會定時檢查 /var/log/auth.log 檔, 只要來自某 IP 的登入次數符合以下任一條件 (以下為預設值)

  1. 1 次 root 登入失敗 (DENY_THRESHOLD_ROOT=1)
  2. 5 次企圖使用不存在的帳戶登入 (DENY_THRESHOLD_INVALID=5)
  3. (5 天之內 AGE_RESET_VALID=5d) 發生 10 次一般正常用戶登入失敗 (DENY_THRESHOLD_VALID=10)

就會把那個 IP 加到 /etc/hosts.deny 去, 也就是封鎖了那個 IP。 更多選項詳見設定檔 /etc/denyhosts.conf 的註解。 另外, 我還偏好設定 PURGE_DENY = 5w 每五週清除舊的 IP。 這有兩個效果: (1) /etc/hosts.deny 不會無限長大; 它也會把太舊的 IP 清掉。 (2) 在 /etc/hosts.deny 裡面會出現註解, 標示每個 IP 被阻擋的日期時間。 更多設定詳見 正體中文簡體中文官網。 還可以考慮打開同步功能, 跟全球的 denyhosts 用戶一起交換 「惡意入侵 IP」 清單。 要記得重新啟動服務才會生效: service denyhosts restart

第二招是禁止 root 登入。 先以 「非 root 帳號」 登入 (重要! 否則等一下可能會被自己鎖在外面), 再用 su 或 sudo bash 變身成 root。 編輯 /etc/ssh/sshd_config 找到 PermitRootLogin yes 那一句, 改成 PermitRootLogin no 最後重新啟動 ssh 服務: service ssh restart 。 詳見 這一篇

二、 封鎖整個網段

這一節的措施是我一開始的驚嚇反應, 需要手動設黑名單; 其實它的實用價值稍微低一些。 因為已發生的攻擊量很大, 當時又還不知道要採用哪種簡單有效的防護措施, 手邊最熟的工具就只有 regexp, 所以那時第一反應就是拿它來分析 auth.log。

先從原始資料當中只抓出登入失敗記錄的時間及來源 IP, 存在 fail.log 裡: perl -ne 'print "$1/$2:$3 $4\n" if m#^(\w+)\s+(\d+)\s+(\d+):.*failed.*from\s+(\S+)\s#i' auth.log.4 auth.log.3 auth.log.2 auth.log.1 auth.log > fail.log (檔案的長像請參考: 從原始 auth.log 裡面抓出來的 一小部分 auth.log 以及它所產生的 一小部分 fail.log)

再從 fail.log 產生統計報表: 以 256 個 IP 為單位, 每個網段各有多少個 IP 參與攻擊? 列出至少 5 個 IP 以上參與攻擊的網段: perl -pe 's#.* ##' fail.log | sort | uniq | perl -pe 's/\.\d+$/.0/' | uniq -c | perl -ne 'print unless /^\s*[1-4]\s/' | sort -nr > evil-domains.txt (這是我的主機遭受攻擊, 從原始 log 檔產生的真實 evil-domains.txt。) 看來某些網段似乎幾近完全被暗黑勢力控制, 有不只兩三個 IP 都參與 ssh 暴力攻擊。

於是用 iptables 封殺這些網段: for d in $(perl -pe 's/.* //' evil-domains.txt) ; do iptables -A INPUT -s $d/24 -j DROP ; done 最後, 記得要用 iptables-persistent 把新的 iptables 存起來, 以免重開機就消失。 詳見 「iptables 過濾封包」

跟 denyhosts 搭配使用, 這個措施有助於避免 /etc/hosts.deny 長太大。

三、 動態應變的 iptables 規則

其實 iptables 有一個 recent 模組也可以做到有點類似 denyhosts 的功能。 它跟 denyhosts 搭配使用, 有點雙重防護的效果。

iptables -N SSH_BRUTE
iptables -A SSH_BRUTE -j LOG --log-prefix "ssh brute force: "
iptables -A SSH_BRUTE -j DROP
iptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 300 --hitcount 6 -j SSH_BRUTE
iptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set

以上建立一個新的 chain 命名為 SSH_BRUTE, 凡是進入這條 chain 的封包都會被記錄, 然後丟掉。 (據說是寫在 /var/log/kern.log 裡面; 但可能因為我的 log level 沒設好, 所以 log 檔裡面找不到 「ssh brute force: 」 識別字串。) 什麼樣的封包會進入這條 chain 呢? 凡是在五分鐘 (300 秒) 之內企圖連到 port 22 (也就是 ssh) 高達 6 次的 IP, 未來它的封包都會被導向 SSH_BRUTE。 上面的 -I 跟先前的 -A 都是新增規則, 但 -I 新增到規則清單的最前面去。

用 iptables 的 「recent」 功能阻擋 ssh 暴力攻擊的優缺點包含:

  1. 在很早就把封包擋住, 所以耗費系統資源較少。
  2. 無法分辨成功與失敗的登入, 所以如果短時間內連續成功登入也會被封鎖。
  3. 一個 IP 如果超過一定時間沒試著登入 (例如五分鐘) 就不被封鎖了。 當然, 如果又連續登入, 就又被封鎖。
  4. 可以設定 多層次封鎖, 例如 「每 20 秒達 3 次, 或每 200 秒達 15 次, 或每 2000 秒達 80 次, 或每 20000 秒達 400 次, 一律封鎖。」
  5. 在 container 內無法使用。

目前有哪些 IP 被列入觀察名單? 這些資訊放在 /proc/net/xt_recent/ 目錄下的某個檔案。 以本節而言, 規則都加到 DEFAULT 這個 table 裡面, 所以你可以用 less /proc/net/xt_recent/DEFAULT 查看 kernel 記錄的原始資料, 或用 ./pr_xt_recent /proc/net/xt_recent/DEFAULT 查看每個 IP 最近一次登入的時間。 那個 pr_xt_recent 是我根據 這一頁的問答 改以 perl 重寫的程式。

如果不小心把自己鎖住, 可以從另一個 IP 登入 (希望你還有另一個 IP 可用...) 然後下: echo -12.34.56.78 > /proc/net/xt_recent/DEFAULT 就可把 12.34.56.78 從封鎖清單當中移除, 或是下: echo / > /proc/net/xt_recent/DEFAULT 就可暫時把最近的封鎖名單清空。 詳見 這一頁問答 或是查手冊: man iptables 並在手冊內搜尋 「xt_recent」。

如果需要放行某些 IP, 那就要用 I (insert) 而不是 A (append) 因為這些必須放在 iptables 的最前面: iptables -I INPUT -s 987.654.321.0/24 -j ACCEPT

關於 iptables 的 recent 模組, 請參考 1 2 3 更多文章。

四、 補充

更多關於如何抵擋 ssh 暴力攻擊的文章和工具:

  1. Preventing Brute Force SSH Attacks
  2. sshguard

附錄、 繪圖指令

從 fail.log 可以產生 「時間 次數」 攻擊流量統計文字檔: perl -pe 's/ .*//' fail.log | uniq -c > traffic.txt。 這是我的主機遭到攻擊的 實際流量檔 traffic.txt。 然後跟 「遭收割機騷擾」 這篇一樣, 把 traffic.gpt 裡面的 set xrange ... 那一句改成你的實際資料範圍, 就可以進 gnuplotload traffic.gpt 指令繪出本文插圖。

3 則留言:

  1. 其實有很簡單的fail2ban就可以有效擋住大部分的嘗試

    回覆刪除
  2. 其實有很簡單的fail2ban就可以有效擋住大部分的嘗試

    回覆刪除