このブログを始めて数ヶ月後、トラフィックはかつてないほど急増しました。私は記事を書き、Hacker NewsとRedditの両方で「バズ」りました。
それがトップページに掲載されるやいなや、私の小さなサーバーは完全に崩壊寸前でした。津波のようにリクエストが殺到し、Apacheは苦戦し、私は無力に座って、消防士が放水銃で火を消すように、何度もマシンを再起動するしかありませんでした。
インターネット用語では、これは「死の抱擁」と呼ばれ、非常に恐ろしかったです。
当時、私が受け取ったリクエストがどれほど密集していたか、そしてそれが私にどれほどのプレッシャーをもたらしたかを言葉で表現するのは困難でした。
今年の2月、私は別の記事を書き、数分でHacker Newsのトップになりました。今回は、十分な準備ができていました。まずサーバーログを保存し、視覚化グラフを作成して、私の月額6ドルの小さなサーバーが正確に何を経たのかを示しました。
Webリクエストの視覚化
サーバーへの各Webリクエストは、サーバーに向かって移動する円で表されます。右下の凡例を参照してください:
- ボット vs. 実ユーザー:ユーザーエージェント検出に基づきます。正規のボットの名前には通常「bot」が含まれ、他のボットはヒューリスティックに識別されます。
-応答の種類は次のとおりです:
- ✅ 200 OK:リクエスト成功
- 🔄 リダイレクト:揺れる点として表示されます
- ❌ 404 Not Found:画面から落ちる赤い点
- 💥 Zip爆弾:後で詳しく説明します
私のサーバー仕様
混乱にもかかわらず、私の月額6ドルの小さなサーバーは、わずか1GBのRAM、Apache 2 + PHP、および基本的なMySQLデータベースを備えていましたが、依然として立ち続けました。
派手なクラウドオートスケーリングも、ロードバランサーもありませんでした。シンプルで効率的な設定と適切なキャッシュの使用だけです。
ホスト:DigitalOcean(1GB RAM)
Webサーバー:Apache 2
環境:Ubuntu + PHP
データベース:MySQL
価格:月額6ドル
私のブログはカスタムPHPフレームワークで動いています。ほとんどのページはmemcachedにキャッシュされているため、データベースは1時間ごとに1回しかクエリされません。この効率的な設定は、過去に数百万のリクエストを処理しており、機械学習に解雇されたことに関する私の投稿や、BBCで特集された人気記事も含まれます。
イベントタイムライン
🕓 午後4:43 (PST) — Hacker Newsに投稿を送信。
🕓 午後4:53 (PST) — トップページに掲載。多数のボットが押し寄せます。
🕔 午後5:17 (PST) — Hacker Newsで1位を獲得。堰が切られたようです。
🕗 午後8:00(PST)— モデレーターが記事のタイトルを変更しました(理由は不明)。トラフィックが急降下しました。
🕓 午前3:56 (PST) — ボットが300のURLをスキャンして脆弱性を探します。
🕘 午前9:00 (PST) — トラフィックが再び急増し、主にMastodonネットワークからです。
🕤 午前9:32 (PST) — 大規模なスパム攻撃:1分間に約4,000件のリクエストがあり、そのほとんどがダークウェブマーケットプレイスの広告でした。
🕓 午後4:00(PST)— 24時間以内に、私のサーバーは46,000件のリクエストを処理しました。
部屋のゾウ
サーバーは一度もクラッシュしませんでした。実際、CPU使用率は16%を超えることはありませんでした。
しかし、視覚化に何か奇妙な点があることに気付いたかもしれません。私の1GB RAMサーバーのメモリ使用率は常に50%を維持していました。なぜでしょうか?MySQLです。
このブログを書き始めた当初、私は野心的にすべてのリクエストをデータベースにログしていました。これは投稿の人気を追跡するのに役立ちました。しかし12年後、データベースは膨れ上がりました。簡単な分析のために何百万行ものデータをソートすることは、コストのかかる操作になりました。
バイラルな急増の後、私はデータをバックアップし、テーブルを削除しました。もう十分でした。
Zip爆弾を作成する1つの方法
ウェブ上のトラフィックのほとんどはボットからのものです。これらのボットは主に新しいコンテンツを発見するために使われます。RSSフィードリーダー、コンテンツをクロールする検索エンジン、または最近ではLLMにコンテンツを提供するAIボットなどです。
しかし、多くの悪意のあるボットも存在します。これらのボットはスパマー、コンテンツスクレーパー、またはハッカーから来ています。以前の雇用主では、ボットがWordPressの脆弱性を発見し、サーバーに悪意のあるスクリプトを挿入しました。そして、そのサーバーはDDOS攻撃のためのボットネットになりました。私の初期のウェブサイトの1つは、ボットがスパムを生成したためにGoogle検索結果から完全に削除されました。その後、これらのボットから自分自身を保護する方法を考えなければなりませんでした。そこで私はzip爆弾の使用を始めました。
zip爆弾は、比較的小さな圧縮ファイルですが、非常に大きなファイルに展開され、マシンを圧倒する可能性があります。
ウェブ上で早期に開発された機能の1つは、圧縮にgzipを使用することでした。インターネット速度が遅く、情報が密であったため、データを送信する前に可能な限りデータを圧縮するというアイデアがありました。したがって、テキストで構成された50KBのHTMLファイルは10KBに圧縮でき、40KBの転送スペースを節約できます。ダイヤルアップモデムの場合、これはページのダウンロードに12秒ではなく3秒かかることを意味しました。
同じ圧縮技術は、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が一度に1GBのブロックでデータを読み書きすることを意味します。
4. count=10:これはddに10個のブロックを処理するように指示します。各ブロックのサイズは1GBです。したがって、これは10GBのゼロデータを生成します。
次に、コマンドの出力をgzipにパイプし、出力を10GB.gzというファイルに圧縮します。この場合、生成されるファイルサイズは10MBです。
私のサーバーでは、現在のリクエストが悪意のあるものかどうかをチェックするミドルウェアを追加しました。サイト全体を繰り返しスキャンしようとするブラックリストに登録された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爆弾作成方法
まず、さらに増量し、ゼロのみで構成される10GBのGZIPファイルを作成しましょう。複数回圧縮することも可能ですが、ここではシンプルにしておきます。
dd if=/dev/zero bs=1M count=10240 | gzip > 10G.gzip
ご覧の通り、10GBです。もっと良くすることもできますが、今のところこれで十分です。
これで、このものが正常に作成できたので、クライアントに提供する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');
よし、これでOKです!
したがって、次のような簡単な防御として使用できます。
<?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のモデレーター)はあなたの記事タイトルを変更します。そして、私たちにはそれに対して何もできません。
ほとんどのトラフィックは人間ではなくボットからのものです。彼らは常に情報をスキャン、スクレイピング、スパムを送信しています。
Apacheのワーカーthread制限は重要です。2つの実行中のインスタンスの最大ワーカーthread制限が75だったことに気付きませんでした。
最適化された軽量な設定は、高価なインフラストラクチャに勝ります。適切なキャッシュがあれば、月額6ドルのサーバーでも数万件の訪問に耐えられます - Kubernetesは必要ありません。
結論
この洗礼のような経験は、サーバー管理以上のことを私に教えてくれました。
Webリクエストの視覚化が展開されるのを見ることで、トラフィックがどのように流れ、ボットがどのように動作するのか、そして最も単純な最適化の決定でさえ、サーバーがクラッシュするかスムーズに動作するかの違いを生む可能性があることについて、新たな洞察を得ることができました。
最も重要なこと:バイラルになるなら、準備しておこうね~
著者:行動中の大雄
参照:
https://idiallo.com/blog/pc-is-not-dead-no-need-for-new-ones
https://github.com/ibudiallo/reqvis
関連記事:
データ専門家必読:1000億のウェブページをクロールして初めて、ウェブスクレイピングが決して単純ではないことに気づきました
クローラーの暴走により、CTOとプログラマーが揃って刑務所へ
Node.jsでウェブコンテンツをスクレイピングする