メールアドレスもシェルに渡せば命令になる:GNU Savannah の GPG 鍵テストからの侵入
外部コマンドに利用者の入力を渡すときは、文字列を連結してシェルに渡さず、引数の配列として渡す。メールアドレスのような「形式が決まっていそうな値」も例外にしない。
何が起きたか
- GNU の開発基盤 Savannah(PHP 製の Savane)で、GPG 鍵のテスト時にメールアドレスをコマンド文字列に連結し、PHP の
proc_openでシェルへ渡していた。アドレス内の特殊文字がシェルに解釈され、登録ユーザーがコマンドを実行できた。 - Hacktron の研究者は AI 監査ツールでこれを見つけ、DB 認証情報の読み取り、管理者への昇格、Bash メンテナーの鍵登録画面の編集が可能だったと報告。古いカーネルの OverlayFS 欠陥で root も取得したとしている。
- FSF は、アドレスを命令の一部ではなくデータとして GPG へ渡すよう修正し、供給網の侵害はなかったとの見解を示している。
防ぎ方
- シェルを経由しない: PHP なら
proc_openに文字列ではなく配列を渡す(PHP 7.4 以降は配列を渡すとシェルを通さず直接実行する)。Node.js はexecFile/spawn(shell: false)、Python はsubprocess.run([...])を使う。 - オプションと値の区切りを入れる: 値が
-で始まるとオプションとして解釈される。対応するコマンドでは--を挟んでから値を渡す。 - 入力検証は補助と考える: メールアドレスの正規表現チェックは、RFC 上許される
'や$などを通しうる。エスケープ関数や検証に頼らず、まずシェルを使わない構造にする。 - ライブラリがあればそれを使う: GPG なら外部コマンドを組み立てるより、GPGME などの API バインディングで値を渡すほうが安全。
被害を広げないための教訓
- Web サーバー権限で DB 認証情報が読めたことが、管理者昇格につながった。Web アプリの実行ユーザーから読める設定ファイルの範囲を最小にする。
- 古いカーネルの既知の権限昇格で root まで届いた。基盤サーバーのカーネル更新を後回しにしない。