一次穿透 iptables 防火牆的 UDP ***報文真實案例分析,揭示了一些在使用 iptables 時鮮爲人知的安全隱患,希望能給大家帶來幫助!
原文鏈接 http://blog.chinaunix.net/uid-1728743-id-3457803.html
這類***確實少見,也是一次好的排錯過程,所以轉載以總結下來.
現象
一次偶然的機會,發現一臺服務器出現了極不正常的情況:CPU 消耗巨大
通過用 top 觀察,發現 snmpd 進程持續走高
抓包
重啓 snmpd 進程,在正常了幾分鐘後突然又重現異常,此時感覺是有特殊數據包進來造成的。
爲了驗證猜測,對 UDP/161 端口進行了抓包,發現了些端倪。
通過抓包發現,原來真的有***,從抓包看,***是突發性質的,當有***時:
頻率:≈ 10Hz
週期:≈ 0.1s
類型:請求 snmpd 的根信息 "Linux C"
原理:通過請求比較高級別的根內容,使系統處於高負載狀態;由於頻率較高,高負載狀態很難消除。
惡意性:爲了突破某些無狀態防火牆,來源端口 sport 特意也是用了與 snmpd 服務相同的 UDP/161,很陰險。
規則檢查
不過奇怪的問題來了,我的防火牆是基於狀態檢測的,可以輕易區分進來的報文是主動請求進來的,還是我主動發出後的回包。
既然這樣,爲何 snmpd 報文還是會被系統收到並處理呢?是不是 iptables 規則使用存在問題?
經查看,規則很簡單,大體如下:
iptables -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p udp --dport 161 -s IP1 -j ACCEPT
iptables -A INPUT -p udp --dport 161 -s IP2 -j ACCEPT
iptables -A INPUT -p udp --dport 161 -s IP3 -j ACCEPT
iptables -A INPUT -j DROP
通過觀察,未發現異常,針對 snmpd 報文,系統只允許了 3 個特殊 IP 訪問,那麼就奇怪了,這個***報文是怎麼進來的呢?
conntrack 特性分析:UDP 與 TCP 的差異
衆所周知,iptables 只是 userspace 的 controller,真正的主體是 netfilter,而 conntrack 則是 netfilter 的一個重要組成部分。
conntrack 的核心價值,就是爲 Linux 實現了基於狀態的防火牆。
在 Linux 中,當啓用了 conntrack 後,即使是 UDP 報文,也是有“新建連接”(NEW)和“已連接”(ESTABLISHED)概念的。
不過與 TCP 不同的是,TCP 是有三次握手的,通過分析三次握手時數據報文頭部的合規性可以知道當前數據是否有效。
但是,UDP 則不同,在 Linux 中,只有“去”和“回”兩個概念,當系統第一次看到 UDP 包,視爲 NEW,看到該包的回包後,視爲 ESTABLISHED。
推理
知道了這個,大概可以猜出問題所在了,一定和 conntrack 有關。
猜測失控過程:
1、系統未啓動 conntrack 時,已經有***報文 request 進來了
2、當系統啓動 conntrack 機制時,恰好有系統回 response 包,被系統 conntrack 當做了主動發出數據,是 NEW 狀態
3、系統的 iptables 規則最後有阻斷功能,這個包雖然被攔掉
但後續***報文用同樣的 SIP/SPORT 訪問同樣的 DIP/DPORT,導致 hash 後認爲是同一條連接
此次的***報文被無當做上一次 NEW 狀態的回包,此次該連接變成了 ESTABLISHED 狀態
4、今後所有 SIP/SPORT 到 DIP/DPORT 的數據均通行無阻,導致最開始看到的場面
驗證
爲了證明推理是正確的,刻意寫了一個 shell,在第一行 -m state --state RELATED,ESTABLISHED -j ACCEPT 後加入 sleep 以增加重現概率
實驗表明,推理完全正確,UDP ***報文的回包被當做了 NEW,而真正的***則成了 ESTABLISHED。
第一句的位置,也是導致問題出現的主要原因。
嘗試解決
爲了杜絕這種情況的發生,思考,將 state 匹配放到最後是否可行?
答案是“不可以”,因爲一樣可能發生前面說的時間差導致的狀態誤判情況發生。
怎樣做纔是最穩妥的辦法呢?
想了個辦法:
1、先 service iptables stop
2、先寫第一行規則,無條件 -j DROP 任何東西
3、寫其他規則,該怎麼寫怎麼寫
4、當規則生效後等一段時間(保證此時間超過 snmpd 報文打進來後的系統響應時間),再刪掉第一行規則,讓數據涌進來(像開閘放水)
可惜,通過實測,***報文仍然可以進來,這個太奇葩了!
/etc/init.d/iptables stop 行爲分析
看到上述疑點,不得不懷疑 iptables 腳本中 stop 功能的實現了。
理論上 iptables 在 stop 的時候,不僅會清除所有規則,也會刪掉所有 iptables 所用到的模塊,這其中也包含了 conntrack。
推理:如果 iptables 的 stop 功能工作異常,那麼就無法正確卸載 conntrack,那麼上述方法就無濟於事了。
紅色部分是清除 conntrack 的關鍵語句。繼續跟蹤:
IPTABLES=iptables
IPV=${IPTABLES%tables} # ip for ipv4 | ip6 for ipv6
腳本最初有上述定義,也就是說,最後 IPV 的取值是 "ip",卸載模塊時卸載的是 ip_conntrrack。
手動驗證一下:
[root@platinum-PT ~]# modprobe ip_conntrack
[root@platinum-PT ~]# iptables -A INPUT
[root@platinum-PT ~]# /etc/init.d/iptables stop
Flushing firewall rules: [ OK ]
Setting chains to policy ACCEPT: filter [ OK ]
Unloading iptables modules: [ OK ]
[root@platinum-PT ~]# lsmod |grep conntrack
nf_conntrack_ipv4 22028 0
nf_conntrack 67784 1 nf_conntrack_ipv4
[root@platinum-PT ~]#
通過觀察發現,conntrack 確實沒有被卸載,但注意到了一點,模塊名字不是 ip_conntrack 而是 nf_conntrack。
似乎明白了,驗證一下:
[root@platinum-PT ~]# cat /etc/redhat-release
Red Hat Enterprise Linux AS release 4 (Nahant Update 2)
[root@platinum-PT ~]# uname -r
2.6.24.4-7
[root@platinum-PT ~]#
知道原因了:
- 系統是 RHEL 4U2,默認內核應該是 2.6.9,那時的 netfilter 使用的 conntrack 模塊叫 ip_conntrack
- 系統的當前內核是 2.6.24.4,此時的 netfilter 所使用的 conntrack 模塊叫 nf_conntrack 和 nf_conntrack_ipv4
- 由於 RHEL 4U2 的 /etc/init.d/iptables 腳本是爲 2.6.9 內核寫的,因此在 stop 時不能正確卸載 2.6.24.4 內核的 conntrack
卸載 conntrack 解決方案
修改
rmmod_r ${IPV}_conntrack
改爲
rmmod_r nf_conntrack
因爲是定製系統,未做詳細的判斷和動態自適應等工作。
總結
- 特殊時機生效的 ACL 導致 conntrack 誤認爲***報文的回包是 NEW
- ***報文惡意使用固定 sport,使 conntrack 誤認爲是同一個 connection
- iptables 規則的邏輯問題致使穿透***有可能發生(雖然是小概率事件)
- System script 與 kernel 的不匹配導致不能正確卸載 conntrack,使問題依然存在