自從我開通這個部落格幾個月後,流量就空前高漲。我寫了一篇文章,分別投在 Hacker News 和 Reddit 都“爆紅”了。
它一登上內容頭條,我的小伺服器就要徹底崩潰了。請求像海嘯一樣湧入,Apache 苦苦掙扎,我只能無助地坐著,一遍又一遍地重啟機器——就像消防員用水槍滅火一樣。
這要拿網際網路術語來說,這叫做“死亡擁抱”,挺嚇人的。
當時,我很難形容當時收到的請求有多麼密集,以及給我帶來的壓力有多大。
就在今年二月,我又寫了一篇貼文,幾分鐘內就登上了 Hacker News 的榜單第一名。這一回,我可做好了充分的準備。我先保存了一份伺服器日誌,然後製作了一個視覺化圖表,準確展現我每月 6 美元的小伺服器究竟經歷了什麼。
Web 請求視覺化
伺服器的每個 Web 請求都用一個向伺服器移動的圓圈表示。查看右下角的圖例:
- 機器人 vs. 真實使用者:基於使用者代理偵測。合法機器人的名稱中通常包含“bot”,而其他機器人則透過啟發式識別。
-回應類型如下:
- ✅ 200 OK:請求成功
- 🔄 重定向:表示為擺動的點
- ❌ 404 Not Found:螢幕上掉落的紅點
- 💥 Zip 炸彈:稍後向各位詳細介紹
我的伺服器規格
儘管一片混亂,我那台每月 6 美元的小伺服器,只配了 1GB 記憶體、Apache 2 + PHP以及一個基本的 MySQL 資料庫,卻依然屹立不倒。
沒有花俏的雲端自動擴展,也沒有負載平衡器。只有我精簡的配置和良好的快取使用。
主機:DigitalOcean(1GB RAM)
Web伺服器:Apache 2
環境:Ubuntu + PHP
資料庫:MySQL
價格:6美元/月
我的部落格運行在一個自定義PHP框架上。大多數頁面都快取在memcached中,因此每個頁面每小時僅查詢一次資料庫。這種高效的配置在過去曾處理過數百萬次請求,包括我關於被機器學習解僱和登上 BBC 的熱門故事。
事件時間表
🕓 下午 4:43 (太平洋標準時間) — 貼文已提交至 Hacker News。
🕓 下午 4:53 (太平洋標準時間) — 進入主頁。一大群機器人蜂擁而至。
🕔 下午 5:17 (太平洋標準時間) — Hacker News 排名第一。閘門打開了。
🕗 晚上 8:00(太平洋標準時間)——版主重命名了該條目(原因不明)。流量驟降。
🕓 凌晨 3:56 (太平洋標準時間) — 一個機器人掃描 300 個 URL 以尋找漏洞。
🕘 上午 9:00 (太平洋標準時間) — 流量再次激增,主要來自 Mastodon 網路。
🕤 上午 9:32 (太平洋標準時間) — 大規模垃圾郵件攻擊:一分鐘內約有 4,000 個請求,大部分是廣告暗網市場。
🕓 下午 4:00(太平洋標準時間)——在 24 小時內,我的伺服器處理了 46,000 個請求。
房間裡的大象
伺服器從未崩潰過。事實上,CPU 使用率從未超過 16%。
但是你可能已經注意到視覺化中有些奇怪的事情:我的 1GB RAM 伺服器記憶體使用率一直保持在 50%。為什麼?因為 MySQL。
當我開始寫這個部落格時,我雄心勃勃地將每一個請求都記錄到資料庫中。這對於追蹤貼文的受歡迎程度很有用。但 12年 後,資料庫規模膨脹。為了進行簡單的分析而對 數百萬 行資料進行排序變成了一項成本高昂的操作。
病毒式傳播之後,我備份了資料,並刪除了表。現在是時候了。
製造ZIP炸彈方法之一
網路上的大部分流量來自機器人。這些機器人大多用於發現新內容。例如 RSS 訂閱閱讀器、抓取內容的搜尋引擎,或者如今為 LLM 提供內容支援的人工智慧機器人。
但也存在不少惡意機器人。這些機器人來自垃圾郵件發送者、內容抓取者或駭客。在我的前雇主那裡,一個機器人發現了 WordPress 的一個漏洞,並在我們的伺服器中插入了一個惡意腳本。然後,它將伺服器變成了一個用於 DDOS 攻擊的殭屍網路。我的早期網站之一就因為機器人產生垃圾郵件而完全從谷歌搜尋結果中下架。後來,我不得不想法保護自己免受這些機器人的侵害。就在那時,我開始使用 zip 炸彈。
Zip 炸彈是一種相對較小的壓縮檔案,但它可以擴展為非常大的檔案,從而壓垮機器。
早期在網路上開發的一個功能是使用 gzip 進行壓縮。由於網際網路速度慢且資訊密集,其理念是在傳輸資料之前盡可能地壓縮資料。因此,一個由文字組成的 50 KB HTML 檔案可以壓縮到 10 KB,從而節省 40 KB 的傳輸空間。在撥號上網的情況下,這意味著下載頁面只需 3 秒,而不是 12 秒。
同樣的壓縮技術也適用於 CSS、JavaScript 甚至圖片。Gzip 快速、簡單,並能顯著提升瀏覽體驗。
瀏覽器發出 Web 請求時,會包含標頭,告知目標伺服器其支援壓縮。如果伺服器也支援壓縮,則會返回預期資料的壓縮版本。
Accept-Encoding: gzip, deflate
爬取網路的機器人也支援此功能。由於它們的任務是從網路各處獲取資料,因此它們會使用壓縮來最大化頻寬。我們可以充分利用此功能。
在這個部落格上,我經常會遇到掃描安全漏洞的機器人,而我通常都會忽略它們。但當我偵測到它們試圖注入惡意攻擊或探測回應時,我會返回 200 OK 回應,並向它們發送 gzip 壓縮包。我接收的檔案大小從 1MB 到 10MB 不等,它們很樂意接收。
大多數情況下,即使它們接收了,我也再也沒有收到任何消息。為什麼?因為它們在接收檔案後就崩潰了。
Content-Encoding: deflate, gzip
實際情況是,他們收到檔案後,讀取檔案頭,發現這是一個壓縮檔案。於是他們嘗試解壓這個 1MB 的檔案,尋找所需的內容。但檔案不斷膨脹,直到記憶體耗盡,伺服器崩潰。1MB 的檔案解壓後變成了 1GB。這足以摧毀大多數機器人。
不過,對於那些不停歇的煩人腳本,我會給他們提供 10MB 的檔案。這個檔案解壓後變成了 10GB,腳本瞬間就被幹掉了。
在告訴你如何製作 Zip 炸彈之前,我必須先警告你,你的設備可能會崩潰甚至被毀。繼續操作需自行承擔風險。
以下是製造 Zip 炸彈的方法:
dd if=/dev/zero bs=1G count=10 | gzip -c > 10GB.gz
我將該命令的作用解釋如下:
1. dd:dd命令用於複製或轉換數據。
2. if:輸入文件,指定 /dev/zero 產生無限零字節流的特殊文件。
3. bs:塊大小,將塊大小設置為 1 千兆字節 (1G),這意味著 dd 將一次以 1 GB 的塊讀取和寫入數據。
4. count=10:這告訴 dd 處理 10 個塊,每個塊大小為 1 GB。因此,這將生成 10 GB 的零數據。
然後,我們將命令的輸出傳遞給 gzip,它會將輸出壓縮成 10GB.gz 檔案。在本例中,生成的檔案大小為 10MB。
在我的伺服器上,我添加了一個中間件,用於檢查目前請求是否惡意。我設定了一個黑名單 IP 位址列表,這些 IP 位址會反覆嘗試掃描整個網站。我還設定了其他啟發式方法來偵測垃圾郵件發送者。許多垃圾郵件發送者會嘗試向某個頁面發送垃圾郵件,然後再回來查看垃圾郵件是否已經到達該頁面。我使用以下模式來偵測它們。
它看起來像這樣:
<?php
if(ipIsBlackListed() || isMalicious()) {
header("Content-Encoding: deflate, gzip");
header("Content-Length: " + filesize(ZIP_BOMB_FILE_10G)); //10 MB
readfile(ZIP_BOMB_FILE_10G);
exit;
}
就是這樣。我唯一的代價就是現在有時需要提供 10MB 的檔案。如果我的文章要火,我會把它壓縮到 1MB,效果也一樣好用。
還有一點,Zip 炸彈並非萬無一失。它很容易被偵測到並規避。畢竟,你只能讀取部分內容。但對於那些盲目爬取網頁、擾亂伺服器的不熟練機器人來說,Zip 炸彈已經足夠保護你的伺服器了。
製造ZIP炸彈方法之二
首先,我們加點量,先建立一個 10GB 的 GZIP 檔案,檔案內容全部為零。我們可以進行多次壓縮,目前先簡單處理一下。
dd if=/dev/zero bs=1M count=10240 | gzip > 10G.gzip
可以看到,它有 10G 大。我們還可以做得更好,但目前已經足夠了。
現在我們已經成功建立好了這個東西,讓我們設定一個 PHP 腳本來將它傳遞給客戶端。
<?php
//prepare the client to recieve GZIP data. This will not be suspicious
//since most web servers use GZIP by default
header("Content-Encoding: gzip");
header("Content-Length: ".filesize('10G.gzip'));
//Turn off output buffering
if (ob_get_level()) ob_end_clean();
//send the gzipped file to the client
readfile('10G.gzip');
好的,就是這樣!
因此我們可以把它作為一個簡單的防禦,像這樣的:
<?php
$agent = filter_input(INPUT_SERVER, 'HTTP_USER_AGENT');
//check for nikto, sql map or "bad" subfolders which only exist on wordpress
if (strpos($agent, 'nikto') !== false || strpos($agent, 'sqlmap') !== false || startsWith($url,'wp-') || startsWith($url,'wordpress') || startsWith($url,'wp/'))
{
sendBomb();
exit();
}
function sendBomb(){
//prepare the client to recieve GZIP data. This will not be suspicious
//since most web servers use GZIP by default
header("Content-Encoding: gzip");
header("Content-Length: ".filesize('10G.gzip'));
//Turn off output buffering
if (ob_get_level()) ob_end_clean();
//send the gzipped file to the client
readfile('10G.gzip');
}
function startsWith($a, $b) {
return strpos($a, $b) === 0;
}
正如我們在上面所說的那樣,這個腳本顯然不是雞蛋的黃色,但它可以防禦我之前提到的那些腳本小子,他們不知道所有這些工具都有更改使用者代理的參數。
使用Zip炸彈反擊內容竊賊!
你可能注意到了動畫中的小爆炸。讓我來解釋一下。
有一天,我發現一個網站正在實時竊取我的內容。每當有人訪問他們的頁面時,他們就會抓取我的部落格文章,替換掉我的品牌標識,並將其據為己有。
一開始,我手動反擊,給他們輸入了假資料。但很快就沒用了。所以我動用了我的秘密武器:給它來一個 zip 炸彈。
當他們的機器人訪問我的網站時,我向它提供了一個很小的壓縮檔案。他們的伺服器迫不及待地下載並解壓了它,結果卻帶來了好幾 GB 的混亂資料。💥砰砰砰!遊戲結束。
多年來,zip 炸彈已經成為我抵禦試圖抓取、利用或濫用我的網站的機器人的盾牌。
送給大家的經驗教訓
Dang(Hacker News mod)會更改你的文章標題,而我們對此無能為力。
大多數流量來自機器人,而非人類。它們不停地掃描、抓取資訊,並發送垃圾郵件。
Apache 的工作執行緒限制很重要。我發現有兩個正在運行的實例的最大工作執行緒數為 75 個,這是我之前沒有注意到的。
經過優化的輕量級配置勝過昂貴的基礎設施。有了適當的快取,每月 6 美元的伺服器就能承受數萬次訪問——無需 Kubernetes。
結論
這段血淚經歷,教會我的不僅僅是伺服器管理。
觀察網路請求視覺化的展開,讓我對流量的流動方式、機器人的運作方式有了新的認識,甚至最簡單的優化決策也可能決定伺服器是崩潰還是順利運行。
最重要的是:如果你想要走紅,請充分做好準備哦~
作者:行動中的大雄
參考:
https://idiallo.com/blog/pc-is-not-dead-no-need-for-new-ones
https://github.com/ibudiallo/reqvis
相關閱讀:
資料從業人員必讀:抓取了一千億個網頁後我才明白,爬蟲一點都不簡單
因為爬蟲失控,CTO和程式設計師雙雙被判刑
使用Node.js 抓取網頁內容