GHOST(glibc gethostbyname, CVE-2015-0235)
たった1個の名前解決関数のサイズ計算ミスが、Linux基盤の広範なプログラムをRCEにさらした。境界計算の落とし穴と、基盤ライブラリを直したのに世界が脆弱なままだった理由を学べる。
- glibcの__nss_hostname_digits_dotsがサイズ計算を誤り、数字とドットだけの細工した名前をgethostbyname系へ渡すとヒープが溢れる。CVE-2015-0235でCVSSv2は10.0。
- 溢れる量はわずか数バイト(32ビットで4、64ビットで8)だが、Qualysはメールサーバー Exim に対しリモートコード実行を実証した。gethostbyname はほぼ全プログラムが間接的に使うため影響範囲が極めて広い。
- バグは2000年のglibc 2.2から存在し、2013年のglibc 2.18で(セキュリティ扱いされずに)修正済みだった。ライブラリ本体の修正だけでは足りず、動的リンクとNSSの都合で全システムの再起動・再ビルドが必要になった。
何が起きたか(影響範囲・深刻度・CVSS)
GHOST は、Linux の C 標準ライブラリである GNU C Library(glibc)に見つかったヒープバッファオーバーフローです。CVE 番号は CVE-2015-0235、2015年1月27日に Qualys が公開しました。脆弱な関数は gethostbyname() および gethostbyname2() からたどり着く内部関数 __nss_hostname_digits_dots() で、ホスト名として「数字とドットだけ」から成る細工した文字列を渡すとバッファを溢れさせられます。名前が GHOST なのは、脆弱な経路が GetHOST by name であることに由来します。
深刻度は最上級です。NVD の評価では CVSSv2 基本値 10.0(AV:N/AC:L/Au:N/C:C/I:C/A:C)。当時 CVSSv3 は標準化前で、公式スコアは v2 で付けられています。gethostbyname は「名前から IP を引く」という基本操作であり、SSH、メールサーバー、cron、多くの CLI ツールが直接または libc 内部から間接的に呼び出します。つまり単一のプログラムの欠陥ではなく、Linux 基盤の広範なプログラムが一斉に影響を受ける 種類の問題でした。
オーバーフローで書き換えられるのは 32 ビットで 4 バイト、64 ビットで 8 バイト(+数字文字のみ)と非常に限定的です。それでも Qualys は、隣接するヒープ上の管理データを精密に踏むことで Exim に対するリモートコード実行を成立させました。「小さいオーバーフロー=安全」ではありません。
脆弱性の原理(なぜ起きるか)
gethostbyname() は、名前が「すでに IP アドレスの文字列(例 192.168.0.1)」だった場合に DNS 問い合わせを省くため、まず __nss_hostname_digits_dots() を呼びます。この関数は入力を一時的にコピーするヒープバッファを確保し、そのサイズを次のように計算していました(本質だけを擬似コードで示します)。
/* 脆弱だった size 計算(要点のみ) */
size_needed = (sizeof(*host_addr)
+ sizeof(*h_addr_ptrs)
+ strlen(name) + 1);
buffer = malloc(size_needed);
問題は、確保後にこのバッファを複数の領域へ分割して使う点にあります。実際のレイアウトは「host_addr 構造体」「h_addr_ptrs(アドレスポインタの配列)」「h_alias_ptr(エイリアスポインタの配列)」「名前文字列のコピー」でした。ところが size_needed の式には エイリアスポインタ配列ぶんの sizeof(char *) が含まれていません。この一項の欠落により、実際に必要な領域は計算値よりも sizeof(char *) だけ(= 32 ビットで 4、64 ビットで 8 バイト)大きくなります。加えて各ポインタ配列はアライメント境界に丸めて配置されるため、名前の長さを調整するとちょうどこの不足ぶんだけ終端を越えられる境界が生まれます。
ここに「1 バイトずれの積み重ね」が効きます。攻撃者は名前の長さを 1 文字ずつ調整して境界に合わせ、名前をコピーする段でバッファ終端を sizeof(char *) ぶん超えて書き込ませられます。書き込める内容は名前自身なので、後述の検査と相まって書き換えられるバイトは限られます。
__nss_hostname_digits_dots はそもそも「名前が IP リテラルか」を判定する関数です。入力に数字(0-9)とドット(.)以外が含まれるとこの高速経路を抜けて通常の名前解決へ回るため、オーバーフローに到達しません。したがって攻撃可能な入力は 0-9 と . のみで構成され、長さが境界にちょうど乗るものに限られます。この制約が、後述する悪用の難しさの根本原因です。
エクスプロイトの流れ(概念)
横にスクロール
悪用は概念レベルにとどめます。まず攻撃者は、アプリが外部由来の文字列を gethostbyname() へ渡す入口を探します。典型は、メールサーバーが接続元や HELO/EHLO 引数を名前解決する箇所です。そこへ、sizeof(char *) ぶん溢れる長さちょうどの「数字とドットだけ」の名前を送り込みます。
溢れる先はヒープ上でバッファ直後に置かれた領域です。攻撃者は事前に別の割り当てを誘発してヒープ配置を整え(heap grooming)、溢れた数バイトが malloc の管理情報や隣接ポインタの上位バイトを踏むよう仕向けます。Exim の実証では、この改変を足がかりに任意コード実行へ到達しました。わずか数バイトでも、それが制御構造の一部なら壊せる のです。
本節は原理の理解に必要な範囲のみを扱い、そのまま使える完全なペイロードや配置手順は掲載しません。目的は、なぜ小さなオーバーフローが RCE に化けるかを把握し、防御と検知に生かすことです。
修正と対策
修正の核心は、抜けていた sizeof(char *)(エイリアスポインタ配列ぶん)を size_needed の計算に加え、バッファが実配置に対して常に十分大きくなるようにすることです。これにより境界を合わせても strcpy が終端を越えなくなります。
| 項目 | 修正前 | 修正後 |
|---|---|---|
| size 計算 | エイリアス配列ぶんが欠落 | sizeof(char *) を加算し過不足なく確保 |
| 書き込み結果 | 境界一致時に数バイト溢れる | 終端内に収まる |
| 到達経路 | gethostbyname / gethostbyname2 | 同じ経路だが安全に確保 |
重要な事実として、このバグは 2013年8月の glibc 2.18 で既に修正されていました。ただし当時はセキュリティ問題として扱われず、多くの安定版ディストリビューション(当時の RHEL 6/7、Debian 7 など)は 2.2〜2.17 系の古い glibc を使い続けており、修正が取り込まれていませんでした。2015年1月の公開を受けて各ディストリビューターが 2.18 の修正を自版へバックポートし、緊急パッチが配布されました。
対策は yum update / apt upgrade によるパッケージ更新だけでは終わりません。glibc は共有ライブラリとしてほぼ全プロセスにマップされるため、更新後に該当ライブラリを使う全サービスの再起動(実務上はシステム再起動が確実) が必要でした。静的リンクされたバイナリや glibc を同梱した製品は、個別の再ビルド・再配布が必要でした。
教訓(一般化できる原則)
- 境界計算は「実配置」と一致させる: バッファを複数領域に分割するとき、サイズ式は全領域+アライメント丸めを漏れなく反映すべきです。GHOST は式の一項欠落という古典的ミスでした。レイアウトから機械的に導ける式にすることが防御になります。
- 小さなオーバーフローを軽視しない: 数バイトでも、ヒープ管理情報や関数ポインタに重なれば RCE の起点になります。「量が小さいから低リスク」は誤りです。
- 修正の『配布』と『適用』は別問題: バグは 2013 年に直っていたのに、セキュリティ扱いされずバックポートされず 2015 年まで残りました。脆弱性は修正コミットの有無ではなく、稼働環境へ届いて初めて消えます。
- 共有ライブラリの更新は再起動まで含めて完了: 動的リンクの一括更新という利点は、プロセス再起動を伴って初めて実現します。稼働中プロセスは古いコードを保持し続けます。
同種のメモリ安全性・多層防御の考え方はセキュリティ、共有ライブラリとプロセスメモリの仕組みはOS、名前解決の全体像はネットワークの各トピックも参照してください。CPU 側の投機実行に起因する別系統の基盤脆弱性は Spectre / Meltdown で扱います。
脆弱性の解剖の記事ガイド
GHOST(glibc gethostbyname, CVE-2015-0235)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
GHOST
比較で見る軸
難易度: advanced / カテゴリ: 脆弱性の解剖 / タグ数: 6
導入後に効く点
溢れる量はわずか数バイト(32ビットで4、64ビットで8)だが、Qualysはメールサーバー Exim に対しリモートコード実行を実証した。gethostbyname はほぼ全プログラムが間接的に使うため影響範囲が極めて広い。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- 脆弱性の解剖
- タグ数
- 6
判断チェックリスト
- 自社の用途が「GHOST / glibc」に近いか確認する。
- 強みである「glibcの__nss_hostname_digits_dotsがサイズ計算を誤り、数字とドットだけの細工した名前をgethostbyname系へ渡すとヒープが溢れる。CVE-2015-0235でCVSSv2は10.0。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。