第三種攻擊:不偷讀、不偽造,只是把你淹掉
本階到目前為止守護的,都是 CIA 三元組裡的兩個字母:機密性(讓祕密保持祕密)以及完整性加上真實性(證明誰送了什麼、且未被竄改)。竊聽威脅前者;欺騙與中間人威脅後者。阻斷服務(denial-of-service)攻擊瞄準的是第三個字母,那個我們幾乎沒提過的:可用性。攻擊者並不想讀你的訊息、也不想假冒你。他們只是想讓你的服務連不上——一間大門被卡死的店。
為什麼可用性這麼好攻擊?因為每一種共享資源都是有限的。一台伺服器只有那麼多記憶體能存半開連線;一條鏈路只有那麼多頻寬;一顆 CPU 每秒只能雜湊那麼多個請求。阻斷服務攻擊本質上是一場耗竭的競賽:逼受害者把稀缺資源花在垃圾上,直到真正的使用者一點都分不到。最便宜的攻擊會找到某種資源,讓攻擊者送出的「一個封包」逼受害者付出遠超過「一個封包」的代價。這種不對稱就是整場遊戲的核心,我們會看它出現三次。
多出來的那個 D——DDoS,分散式阻斷服務——改變的是規模,不是想法。流量不再來自一台攻擊機器,而是來自數以千計:一個由被綁架的家用路由器、攝影機、個人電腦組成的殭屍網路(botnet),每一台只送出涓涓細流,加起來卻成洪水。分散讓攻擊很難靠來源直接封鎖(根本沒有單一來源),也讓一大群小上行頻寬得以壓垮一條大的。同樣是耗竭,只是水龍頭多了很多個。
攻擊一:SYN 洪水與半開連線表
回想一下,每條 TCP 連線開頭的三向交握:SYN、SYN-ACK、ACK——「你聽得到我嗎?——聽得到,你呢?——聽得到。」這裡有個被攻擊者濫用的細節。當你的伺服器收到一個 SYN,它還不能完成連線;它得在等待最後那個 ACK 的同時,記住這條半成形的連線。於是它在「半開連線表」裡配置一小塊記憶體,並回送自己的 SYN-ACK。那塊記憶體是在用戶端證明任何事之前就承諾出去的——交握是序號的同步,不是身分鑑別。
SYN 洪水把那份承諾變成武器。攻擊者射出一陣 SYN 封包,通常帶著偽造的來源位址,然後乾脆永遠不送第三個 ACK。每個 SYN 都逼伺服器保留一個表格欄位並等待。由於來源是假的,那些 SYN-ACK 飛向了從沒請求過任何東西、也永不回覆的位址。表格被一條條幽靈般的半開連線塞滿,一旦滿了,伺服器就只能拒絕下一個 SYN——包括來自真正客人的那一個。沒有資料被偷;門只是被一群敲完就走的人卡死了。
Normal handshake: SYN flood (spoofed sources):
client --- SYN ----> srv attacker - SYN(fake A) -> srv [slot 1 held]
client <- SYN-ACK -- srv attacker - SYN(fake B) -> srv [slot 2 held]
client --- ACK ----> srv attacker - SYN(fake C) -> srv [slot 3 held]
[connection open] srv -> SYN-ACK -> A,B,C (no one home)
...thousands more... TABLE FULL
real client - SYN -> srv [REJECTED]聰明的防禦叫 SYN cookies。伺服器不在收到第一個 SYN 時就配置記憶體,而是拒絕記住任何東西:它把本來要儲存的連線狀態,用一個祕密算進自己 SYN-ACK 的初始序號裡。如果是真正的用戶端把 ACK 送回來,那個被送回的號碼就能讓伺服器當場重建狀態——從頭到尾沒佔過任何欄位。於是一陣永遠產不出有效 ACK 的 SYN 洪水,對伺服器一點記憶體都不花。這招很漂亮:讓用戶端揹著狀態,攻擊者廉價的 SYN 就再也買不到昂貴的伺服器記憶體了。
攻擊二:DNS 放大,一個封包變成五十個
SYN 洪水耗的是記憶體。DNS 放大瞄準的是純粹的頻寬,而且它狠狠地倚賴我們先前點出的那種不對稱。它一次利用兩件事:網際網路的電話簿——DNS——跑在無連線的 UDP 上,沒有交握去查證是誰在問;以及一個極小的查詢能引出一個巨大的回答。一個像「把你知道關於這個網域的全部給我」的小請求可能只有 60 個位元組,而回覆——一堆紀錄、簽章、林林總總——可能有 3000 個位元組以上。那大約是 50 比 1 的放大。
現在再加上來源欺騙。攻擊者把那些極小的查詢送往數以千計的開放 DNS 伺服器,卻把受害者的位址寫成來源。因為 UDP 從不查證到底是誰問的,每台伺服器都盡責地把肥大的回覆送出去——不是送回攻擊者,而是送給受害者。攻擊者花掉自己上行的 60 個位元組,卻把 3000 個位元組逼上受害者的下行——五十倍的拳力——再加上一個殭屍網路從無數個反射器並行這麼做,受害者的頻寬就被活埋了。那些 DNS 伺服器是無辜的中間人,叫做反射器(reflector);這招有時稱為反射攻擊。
攻擊三:ARP 欺騙,淹掉單一區域網路
前兩種攻擊來自網際網路的另一端。ARP 欺騙是內部作案,它顯示阻斷服務也可能是更深層入侵的副作用。在一個區域網路上,一台知道另一台 IP 位址的主機,必須先學到它的硬體 MAC 位址,才能把東西封裝成框並送達。它會大喊一個 ARP 請求——「誰擁有 192.168.1.1?告訴我你的 MAC」——然後相信回來的任何答案。沒有任何鑑別:ARP 就是直接相信那個回覆。那份信任就是漏洞。
在同一個區域網路上的攻擊者送出偽造的 ARP 回覆:「192.168.1.1 在我的 MAC 位址。」受害者更新自己的表格,開始把每一個本要送往閘道的封包交給攻擊者。作為中間人,這能達成竊聽;但作為阻斷服務,它甚至更簡單。攻擊者可以把閘道的 IP 指向一個根本不存在的 MAC 位址,於是每一個要往外面世界去的封包都被送進黑洞。整個子網路失去離開網路的路徑——徹底的本地斷線——而且沒有一個位元組需要跨越網際網路。
防禦手段配合這個本地情境。交換器可以開一個常稱為「動態 ARP 檢查」的功能,把 ARP 回覆對照交換器已學到的可信 IP 對 MAC 綁定(例如靠觀察 DHCP 發放位址而學到的)逐一核對,並丟棄謊言。對關鍵主機設靜態 ARP 條目,以及把網路切成多個 VLAN 讓被入侵的裝置只能毒害較少的鄰居,兩者都能縮小波及範圍。誠實的總結:ARP 當初是為一個小而互信的房間設計的,安全只能在交換器上額外加裝,因為這個協定本身永遠不會說不。
把水舀出去:防禦真正怎麼運作
沒有哪個單一開關能把 DDoS 關掉,而最深的原因讓人不太舒服:一場塞滿你入站鏈路的洪水,在你的設備碰到它之前就已經造成傷害了,因為瓶頸是那條你控制不了的管子。所以防禦是分層的。最靠內的,一道防火牆與速率限制器會丟掉明顯的垃圾、並限制任一來源能多快地敲門。SYN cookies 專門化解 SYN 洪水。但面對一場真正巨大的洪水,你無法在自己家門口取勝——你必須把戰場往上游移。
上游才是規模所在之處。大型防護服務商與內容網路靠著「又巨大又分散」來吸收洪水。關鍵手法用到 任播:同一個被防護的 IP 位址,從世界各地數十個資料中心同時宣告出去,於是路由系統自然會把每個攻擊者的封包送到最近的中心。一場本足以壓垮一台機器的洪水,被稀釋到整個星球上,每個站點只吞下自己消化得了的一口。接著清洗中心(scrubbing center)坐在路徑上,把攻擊流量從真實流量裡篩出來,只把乾淨的那一股往前轉——在水淹到你的船之前先把它舀掉。
退一步看,整個本階的脈絡就接上了。機密性、完整性、真實性、可用性是四個各自獨立的承諾,每一個都需要自己的一套機制:前三者靠密碼學,最後一個靠容量與驗證。也請注意,這裡每一種攻擊都長自一個「預設就信任」的協定——TCP 相信一個 SYN 代表真正的用戶端,UDP 相信來源位址,ARP 相信那個回覆。網際網路是在朋友之間建起來的;網路安全是一項漫長而耐心的工作,把查證一點一點地加裝到一個當初假設「人人都說真話」的系統上。