爬蟲搞崩網站後,程式設計師自制“Zip炸彈”反擊,6刀伺服器成功扛住4.6萬請求

作者:Ibrahim Diallo       
編譯:蘇宓
轉自:CSDN(ID:CSDNnews)
在這個爬蟲橫行的時代,越來越多開發者深受其害:有人怒斥 OpenAI 的爬蟲瘋狂“偷”資料,7 人團隊十年心血的網站一夜崩潰;也有人被爬蟲逼到極限,最後只好封掉整個巴西的訪問才勉強止血。但本文作者卻走了一條完全不同的路——他靠一己之力,用一臺每月僅需 6 美元的小破伺服器,成功扛下了 Hacker News 熱榜帶來的流量海嘯,還順手反制了那些惡意爬蟲,用“zip 炸彈”讓它們原地爆炸。而他是怎麼做到的,我們不妨來看看。
原文:https://idiallo.com/blog/surviving-the-hug-of-death
https://idiallo.com/blog/zipbomb-protection

背景
我是 Ibrahim Diallo,一名住在加州的軟體開發者。從 1992 年起,我就開始“折騰電腦”:破解系統、寫程式碼,一轉眼搞了幾十年,到現在也還沒停下。
平時我會寫點部落格,分享技術觀察和一些個人思考。誰知多年前剛開始寫沒多久,就因為一篇意外“火出圈”的文章,遭遇了前所未有的流量暴擊——直接衝上了 Hacker News 和 Reddit 熱榜。

文章登上 HN 首頁的那一刻,我那臺小伺服器瞬間崩潰了。請求像潮水一樣湧來,Apache 伺服器吃力地運轉,我坐在電腦前一次次重啟它,就像拿個噴水槍去滅森林大火——完全招架不住。這種“被熱度壓垮”的場面,在網際網路圈子裡還有個形象的說法:“死亡之擁”(Hug of Death)。
到底訪問量有多猛、伺服器壓力有多大?說實話,還真挺難用語言形容。
直到今年二月,我又寫了一篇文章,它沒多久後又登上了 Hacker News 第一名。不過這次我有備而來:提前儲存了伺服器的日誌,還專門做了一段視覺化動畫,展示那臺每月只花 6 美元的小伺服器,是怎麼扛住這波流量洪水的。
上面這個動畫裡,每一個網頁請求都用一個小圓點表示,朝伺服器移動。右下角還有圖例說明:
  • 機器人 vs 真實使用者:透過 user-agent 判斷。帶有 “bot” 的一般是正規機器人,其他的就靠一些啟發式規則來判斷。
  • 響應型別:
– 200 OK(是一種 HTTP 狀態碼,表示網頁請求成功了,伺服器已經正常返回了你要的內容):請求成功
– 重定向:用晃動的小圓點表示
– 404 Not Found:紅點會掉出螢幕
– zip Bombs:這個後面再解釋

伺服器配置

隨著文章爆火,儘管現場一度混亂,我那臺每月只花 6 美元的小伺服器卻始終堅挺。它的配置非常簡陋:只有 1GB 記憶體,跑著 Apache 2 和一個簡單的 MySQL 資料庫。沒有云服務、沒有自動擴容、也沒有負載均衡器,全靠輕量化配置和合理的快取策略“硬扛”流量洪峰。
伺服器配置如下:
  • 主機服務商:DigitalOcean(1GB 記憶體)
  • Web 伺服器:Apache 2
  • 系統環境:Ubuntu + PHP
  • 資料庫:MySQL
  • 月費用:$6
我的部落格是基於一個自建框架,大部分頁面內容都提前快取到了 memcached 中。資料庫只在每小時查詢一次頁面,大大減輕了負擔。這套方案在過去幾次流量高峰時也都撐住了。
來看一下這次文章爆火的時間線:
  • 下午 4:43(PST)— 文章提交到 Hacker News
  • 下午 4:53(PST)— 登上首頁,機器人蜂擁而至
  • 下午 5:17(PST)— 成為 Hacker News 第一,洪水般的訪問量湧入
  • 晚上 8:00(PST)— 管理員改了標題(原因不明),流量斷崖式下跌
  • 凌晨 3:56(PST)— 一隻機器人掃描了 300 個 URL,試圖找漏洞
  • 上午 9:00(PST)— 來自 Mastodon 網路的新一波流量暴增
  • 上午 9:32(PST)— 遭遇大規模垃圾攻擊:一分鐘內接收約 4000 個請求,幾乎全是廣告
  • 下午 4:00(PST)— 24 小時內,伺服器共處理 46,000 個請求

關鍵點來了:伺服器從頭到尾都沒宕機,甚至 CPU 使用率連 16% 都沒到!

不過你在動畫中可能注意到一個奇怪現象:明明是 1GB 記憶體的小伺服器,怎麼記憶體始終被佔了一半左右?原因其實很簡單——是 MySQL 在“背鍋”。
當年部落格剛上線時,我曾雄心勃勃地記錄下每一條請求,把它們寫入資料庫日誌表,用來追蹤哪些文章最受歡迎。這招在當時確實挺實用,但 12 年過去,資料越堆越多,想從中查點東西反而變成了高成本操作。
這次流量衝擊之後,我備份了日誌表,然後徹底刪掉它。也是時候跟那段歷史告個別了。
對了,你可能在視覺化動畫裡看到一些“小爆炸”效果,現在就來解釋下那是怎麼回事…
有一天,我偶然發現有個網站在即時偷我部落格的內容:每次有人訪問他們的頁面,他們就立刻爬取我最新的文章,刪掉我的名字和品牌標識,然後假裝是他們自己寫的。
一開始,我試著“手動反擊”——故意喂他們一些假資料,讓他們搬錯內容。但沒過多久,我就覺得這種方式太麻煩,乾脆拿出了我的秘密武器:“zip 炸彈”
這個“炸彈”的工作原理是這樣的:當他們的爬蟲訪問我的網站時,我就返回一個看起來沒什麼問題的小壓縮檔案。他們的伺服器會乖乖下載並嘗試解壓。結果呢?幾 GB 的“垃圾”檔案瞬間釋放,系統直接崩了。
就這樣,遊戲結束。

麼是“zip 炸彈”?為什麼能“反殺”爬蟲?

這些年,“zip 炸彈”成了我對付各種惡意爬蟲(比如偷內容、探測漏洞、發垃圾資訊的自動程式)最有效的工具。
要知道,網際網路上的流量,其實大部分都來自這些機器人。有的是“友好”的,比如搜尋引擎、RSS 訂閱器,或者現在很流行的大模型訓練用爬蟲。但也有很多是惡意的:像傳送廣告、偷文章,甚至入侵網站的指令碼。我曾經在工作中就遇到過:有個爬蟲發現我們 WordPress 的漏洞,在伺服器裡偷偷植入惡意程式碼,讓網站變成駭客控制的攻擊工具;還有一次,我的網站被爬蟲刷滿垃圾內容,結果直接被 Google 搜尋下架。
從那以後我就意識到,必須對這些爬蟲進行“反擊”,zip 炸彈就是我找到的最佳方案之一。
簡單說,zip 炸彈是一種超小的壓縮檔案,解壓之後卻會變成一個巨大的檔案,能讓處理它的系統崩潰。
在網際網路早期,gzip 壓縮是很早就被引入的一個功能。由於當時網速慢、資訊密度高,人們希望儘可能壓縮資料再傳輸。比如一個 50KB 的 HTML 檔案,經過 gzip 壓縮後可能只剩 10KB,能節省 40KB 的傳輸量。在撥號上網時代,這意味著頁面載入時間從 12 秒減少到 3 秒。
這種壓縮技術同樣可以用於傳輸 CSS、JavaScript 甚至影像檔案。Gzip 的優勢在於它快速、簡單,而且能顯著提升瀏覽體驗。當瀏覽器發出請求時,會在請求頭中表明自己支援壓縮。如果伺服器也支援,就會返回經過壓縮的資料版本。
Accept-Encodinggzip, deflate
而爬蟲程式也會這麼做,它們一樣想節省資源。我正是利用這一點“反制”它們

反擊過程:用壓縮包“讓你解壓到崩潰”

在我的部落格上,經常會遇到一些機器人在掃描安全漏洞,通常我不會理會。但當我發現它們試圖注入惡意攻擊,或是在試探伺服器響應時,我就會返回一個看起來正常的響應(比如 200 OK),並提供一個經過 gzip 壓縮的檔案。
我會提供一個從 1MB 到 10MB 不等的檔案,它們也會欣然接受。通常一旦它們接收了這個檔案,我就再也沒見過它們出現。為什麼?因為它們在解壓檔案後直接崩潰了。
Content-Encoding: deflate, gzip
具體發生的事情是這樣的:機器人收到檔案後,會讀取檔案頭,發現這是一份壓縮檔案。於是它們嘗試解壓這個 1MB 的檔案,以查詢其中的內容。但檔案開始不斷擴充套件、擴充套件、再擴充套件,直到記憶體耗盡、伺服器崩潰為止。這個 1MB 的壓縮檔案實際上會被解壓為 1GB,大多數機器人在這一過程中就會崩潰。如果遇到那些死纏爛打的指令碼,我就給它們傳送 10MB 的版本,它會解壓成 10GB,直接“秒殺”。
在告訴你如何製作 zip Bomb 之前,我得提醒一句:這確實可能會導致你自己的裝置崩潰甚至損毀,繼續操作需自擔風險。
下面是建立 zip Bomb 的方法:
dd if=/dev/zero bs=1G count=10 | gzip -c > 10GB.gz
這個命令的作用如下:
  • dd:用於複製或轉換資料;
  • if:指定輸入檔案,這裡是 /dev/zero,一個會不斷生成零位元組資料的特殊裝置;
  • bs=1G:設定塊大小為 1GB,意味著每次處理 1GB 資料;
  • count=10:處理 10 個這樣的塊,總共生成 10GB 的零資料;
  • 然後我們將這些資料透過管道傳給 gzip,生成一個壓縮檔案 10GB.gz,壓縮後大小大約是 10MB。
在我的伺服器上,我寫了一箇中間件程式,用來自動識別哪些請求是惡意的。
我維護了一個黑名單 IP 列表,專門記錄那些反覆掃描整個網站的地址。同時還有一些啟發式策略來檢測垃圾資訊傳送者。很多垃圾指令碼會在發完內容後再次訪問頁面,看內容是否成功插入,我正是利用這個行為模式來識別它們。
當系統判斷出請求是惡意的,就觸發如下邏輯:
if(ipIsBlackListed() || isMalicious()) {header("Content-Encoding: deflate, gzip");header("Content-Length: " . filesize(ZIP_BOMB_FILE_10G)); // 10MBreadfile(ZIP_BOMB_FILE_10G);exit;}
就是這麼簡單。我只需要“送出”一個 10MB 的檔案,就可能讓對方伺服器宕機。如果哪天我的文章突然大火、訪問量激增,我就把炸彈換成 1MB 的“輕量版”,對付低端爬蟲也完全足夠。

Zip Bomb 並不是萬能的
最後再補充一點:zip 炸彈並不是萬能的。
高階一點的爬蟲可以識別壓縮檔案,提前做限制,比如只讀取一部分或禁止解壓,這樣就能繞過炸彈。不過那些“低階無腦亂爬”的指令碼,就非常容易中招了。
而我需要的,就是一個成本低、效果好的防禦手段。對於這種級別的威脅來說,“zip 炸彈”已經綽綽有餘。

相關文章