SQL Slammer(376バイトで世界を止めた)
たった376バイトのコードがなぜ10分で世界のインターネットを飽和させたのか、単一UDPパケットと帯域律速増殖の原理から解き明かし、パッチ運用の重みを持ち帰れる。
- 2003年1月25日、SQL Server 2000解決サービス(UDP 1434)のバッファオーバーフローを突く376バイトのワームが出現。全体が単一パケットに収まり、感染ホストは応答を待たず乱数IPへ撃ち続けた。
- コネクションレスなUDPゆえ往復遅延に縛られず帯域だけが上限になり、増殖は8.5秒で倍増。3分以内にピーク毎秒5500万スキャン、約10分で脆弱ホストの9割・7万5千台超へ拡散した。
- 悪用された穴のパッチMS02-039は半年前に出ていた。小さなコードの破壊力と、パッチ適用・不要ポート遮断という地味な運用こそが最後の防波堤になる。
何のコードか(背景と何が有名か)
SQL Slammer(別名 Sapphire)は、2003年1月25日 05:30 UTC ごろに突如出現し、わずか10分で世界のインターネットを実質的に麻痺させたワームです。有名なのは、その常識外れの速さと極端な小ささにあります。ワーム本体は 376バイト、UDP/IP のヘッダを足しても 404バイトの単一パケットに丸ごと収まりました。ソースコードでもファイルでもなく、たった1つのパケットが自分自身のコピーを撒き散らす「生きた弾丸」だったのです。
標的は Microsoft SQL Server 2000 と、それを内蔵する MSDE 2000 の 解決サービス(SQL Server Resolution Service, SSRS)が待ち受ける UDP ポート 1434 でした。ここに存在したバッファオーバーフローを突いて任意コードを実行させます。CAIDA を中心とした事後解析によれば、感染ホスト数は約 8.5秒で倍増し、出現から 3分足らずでピーク毎秒5500万スキャンに達し、約10分で脆弱ホストの 9割・7万5千台超を飲み込みました。これは当時「史上最速のワーム」と呼ばれ、今なお指数増殖の破壊力を示す教科書的事例です。
通常の TCP を使う攻撃は、標的ごとに3ウェイハンドシェイクの往復(RTT)を待たねばならず、増殖速度は「遅延律速(latency-limited)」になります。Slammer はコネクションレスの UDP を単発で撃つため往復待ちがゼロで、上限は各ホストの「回線帯域」だけ、つまり「帯域律速(bandwidth-limited)」でした。この違いが桁違いの拡散速度を生みました。
コードと仕組みの解説
横にスクロール
Slammer の本体は、ハンドチューニングされたアセンブリの塊です。動作は「オーバーフローで実行権を奪う」→「必要な関数を自力で解決する」→「乱数IPへ自分を撃ち続ける」の3段です。
1. オーバーフローと制御奪取。 SSRS は 0x04 で始まるパケットを受けると、続くデータをスタック上のバッファへ長さ検査なしにコピーします。攻撃パケットはこのバッファを溢れさせ、関数の戻りアドレスを書き換えます。書き込む値は sqlsort.dll 内に存在した jmp esp 命令のアドレス 0x42B0C9DC です。関数が戻ると jmp esp が実行され、実行位置は「スタック上に居座った攻撃データ自身」へ飛びます。以降はそこに置かれたワームコードが走ります。
# 制御奪取の骨子(概念)
[SSRSが受信] 0x04 + 長い文字列 …… 長さ検査なしでスタックへコピー
[溢れる] 戻りアドレスを 0x42B0C9DC(sqlsort.dll の jmp esp)で上書き
[ret 実行] jmp esp → esp が指すスタック上の攻撃データへジャンプ
[payload] スタックに載ったワーム本体が実行開始
固定アドレスへ飛ぶ設計だからこそ、当時 ASLR(アドレス空間配置のランダム化)が無い Windows では、脆弱なホストならどれでも同じ一撃が通りました。
2. 関数の自力解決。 ディスクに何も書かないため、必要な API は実行時に自分で見つけます。まず kernel32.dll を起点に GetProcAddress と LoadLibraryA の番地を得て、ws2_32.dll(ソケット)を読み込み、socket・sendto と、乱数の種に使う GetTickCount を解決します。
3. 乱数IPへの無限送信。 ここが増殖エンジンです。GetTickCount(起動からの経過ミリ秒)を種に、線形合同法(LCG)で疑似乱数を生成し、それを32ビットの標的IPとして UDP 1434 へ 404バイトを撃ちます。応答は一切待たず、生成と送信をひたすら回す「撃ちっぱなし(fire and forget)」です。
# 増殖ループの骨子(擬似コード)
seed = GetTickCount()
loop:
seed = seed * A + B # 線形合同法で次の疑似乱数
target_ip = seed # 32ビットをそのままIPと解釈
sendto(target_ip, port=1434, payload) # 404バイトを送出、応答は待たない
goto loop # 回線帯域が許す限り最速で反復
LCG の定数選択と初期化・ビット混合の実装ミスにより、生成される標的アドレスの分布に偏りが生じ、特定のアドレス帯へは決して到達しない一方、別の帯へは過剰に集中しました。完全な一様乱数ではなかったにもかかわらず10分で世界を覆えた事実が、単一パケット設計の破壊力を逆説的に物語ります。
なぜ「速すぎた」のか。1台の感染ホストは回線が許す限り毎秒数万パケットを吐き、そのたびに新しい感染源が増えます。応答待ちがないため CPU と帯域だけが律速で、増殖は純粋な指数関数を描きました。さらに大量の UDP パケット自体がルータのバッファを溢れさせ、BGP セッションが切れるなどインターネットの土管そのものを詰まらせたため、感染していない組織まで通信不能に陥りました。ワームは自らの成功で通信路を壊し、結果的に自分の拡散も頭打ちにしたのです。
逸話と経緯
穴の発見者は、著名なセキュリティ研究者 David Litchfield(当時 Next Generation Security Software)でした。彼は2002年、この SSRS のオーバーフローを Microsoft へ報告し、Black Hat で概念実証を示しました。皮肉なことに、その後に公開された攻撃手法の考え方が、素性不明の作者によってワーム化されます。Slammer の作者は今日まで特定されていません。
決定的な事実は、パッチが半年前に出ていたことです。悪用された脆弱性は 2002年7月24日の MS02-039 で修正され、同年10月の累積パッチ MS02-061(および SQL Server 2000 Service Pack 3a)にも含まれていました。つまり出現の時点で、正しくパッチを当てた組織は無傷だったはずなのです。それでも被害が甚大だったのは、運用現場でのパッチ適用がいかに遅れがちかを露呈しました。パッチ適用時のファイル上書きの手順が煩雑で、当てたつもりが実は無防備、という事故も少なくありませんでした。
帯域を食い潰した副作用として、Bank of America など複数銀行の ATM が停止し、Continental Airlines は発券・搭乗手続きに支障を来して欠航が発生、米シアトルの 911 緊急通報システムにも影響が及びました。米オハイオ州の Davis-Besse 原子力発電所では、監視ネットワークにワームが侵入し安全パラメータ表示システムが約5時間ダウンしました(プラント自体は停止中で安全は保たれた)。物理世界のインフラがネットワークの混雑一つで揺らぐことを見せつけた事件です。
対応は、UDP ポート 1434 を境界ルータ・ファイアウォールで遮断し、感染ホストを再起動して(メモリ常駐のみなので再起動で本体は消える)パッチを当てる、という順序で進みました。ワームは自己増殖の速さゆえ数十分で飽和し尽くしたため、拡散のピーク自体は短時間で過ぎ去りました。
遺産と教訓
Slammer は「小さいコードほど無害」という直感を完全に裏切りました。要点を整理します。
| 論点 | Slammer が示したこと | 現在の定石 |
|---|---|---|
| 拡散速度の上限 | TCP の往復待ちがない単一UDPパケットは帯域律速で指数増殖する | 既知ポートの露出最小化、レート制限、UDP増幅の監視 |
| パッチ運用 | 半年前のパッチ未適用が全被害の前提だった | 重大脆弱性は迅速適用、適用状況を継続監査 |
| 攻撃面の縮小 | 不要な解決サービスが全世界に露出していた | 不要サービス停止、境界での既定拒否とセグメント分離 |
| 副次被害 | ワーム自身のトラフィックが基幹網とインフラを巻き添えにした | 監視・制御網を業務網から分離し輻輳の連鎖を断つ |
第一に、パッチ適用は技術ではなく運用の問題だという教訓です。修正コードは存在したのに、それが現場に行き渡っていなかった。脆弱性管理とは「パッチを作ること」ではなく「漏れなく当て続け、当たっていることを確認し続ける」プロセスだと業界が痛感しました。第二に、攻撃面(アタックサーフェス)の最小化です。多くの被害ホストは、そもそも外部から UDP 1434 が見える必要のないサーバーでした。既定で拒否し、必要なものだけ開ける原則が徹底されていれば被害は激減したはずです。第三に、単一パケットのコネクションレス攻撃という設計原理そのものが、後年の DDoS やインターネットワームの脅威モデルを定義しました。
- 標的は SQL Server 2000 解決サービス(SSRS)の UDP 1434、原因はバッファオーバーフロー、CVE-2002-0649/MS02-039
- 全体376バイト・単一UDPパケットゆえ「帯域律速」で増殖し、8.5秒で倍増・約10分で脆弱ホストの9割へ
- 戻りアドレスを sqlsort.dll の jmp esp(0x42B0C9DC)で上書きし、GetTickCount 種のLCGで乱数IPへ撃ち続けた
- パッチは半年前(2002年7月)に公開済み。教訓はパッチ運用と攻撃面最小化。作者は不明、穴の発見者はDavid Litchfield
バッファオーバーフローがなぜ制御奪取に直結するのかというメモリとスタックの原理はセキュリティの基礎に、コネクションレスな UDP が往復を持たない仕組みはネットワークの基礎に立ち返ると腑に落ちます。376バイトが世界を止めた事実は、コードの大きさと影響の大きさが無関係であること、そして最後に世界を守るのは派手な技術ではなく地味なパッチ適用であることを、20年以上経った今も静かに教え続けています。
コード遺産の記事ガイド
SQL Slammer(376バイトで世界を止めた)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
セキュリティ
比較で見る軸
難易度: advanced / カテゴリ: コード遺産 / タグ数: 5
導入後に効く点
コネクションレスなUDPゆえ往復遅延に縛られず帯域だけが上限になり、増殖は8.5秒で倍増。3分以内にピーク毎秒5500万スキャン、約10分で脆弱ホストの9割・7万5千台超へ拡散した。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- コード遺産
- タグ数
- 5
判断チェックリスト
- 自社の用途が「セキュリティ / ワーム」に近いか確認する。
- 強みである「2003年1月25日、SQL Server 2000解決サービス(UDP 1434)のバッファオーバーフローを突く376バイトのワームが出現。全体が単一パケットに収まり、感染ホストは応答を待たず乱数IPへ撃ち続けた。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。