OSコマンドインジェクションを根絶する:ホワイトリストという「絶対防壁」の設計術
現場でコードをレビューしていると、いまだに「なぜこんな書き方を?」と頭を抱えたくなるような実装に出くわすことがある。特に、ユーザーからの入力をそのままOSコマンドの引数に渡すような設計は、「自らセキュリティホールを全開にして泥棒を招き入れている」のと同義だ。
今回は、OSコマンドインジェクションを物理的に不可能にするための「ホワイトリスト方式」によるバリデーションについて、机上の空論ではない、現場の戦術を叩き込む。
—
1. なぜ「エスケープ」だけでは不十分なのか
多くの開発者は escapeshellarg() や shlex.quote() を使って入力を無害化しようとする。だが、思い出してほしい。これらはあくまで「メタ文字を無効化する」ためのものであり、「コマンドそのものをすり替える」攻撃には無力だ。
攻撃のメカニズム(PoC)
例えば、画像処理のためにユーザーが指定したファイル名をコマンドに渡す処理があったとする。
脆弱な実装例(PHP)
exec(“convert ” . $_GET[‘filename’] . ” output.jpg”);
攻撃者は filename パラメータに以下のような値を送り込む。
image.jpg; rm -rf /; #
実行されるコマンドはこうなる。
convert image.jpg; rm -rf /; # output.jpg
エスケープ関数を使っていても、コマンドの実行順序を操作されたり、意図しないオプションを付与されたりする脆弱性は防げない。悪意ある入力を「無害化」しようとする考え方そのものが、既に負け戦なのだ。
—
2. ホワイトリスト方式:論理的な「鉄壁」
防御の極意はシンプルだ。「悪い入力を拒否する(ブラックリスト)」のではなく、「想定内の正しい入力以外は、システムに触れさせない(ホワイトリスト)」という考え方に切り替えること。
実装の鉄則
1. 入力値はあらかじめ定義した定数や配列と照合する
2. 正規表現は「許可したい文字のみ」を定義する(禁止文字ではない)
3. コマンドの引数はハードコードするか、動的生成なら必ずマッピングテーブルを経由する
—
3. 実践コード:言語別・堅牢な実装サンプル
PHP編:マッピングによる安全な制御
ユーザー入力をそのまま引数にするのではなく、キーに対応する「許可された安全な値」のみを許可する。
‘user_profile.png’,
‘banner’ => ‘header_banner.png’
];
$input = $_GET[‘file_key’] ?? ”;
// マッピングに存在しなければ即終了
if (!isset($allowed_files[$input])) {
die(“不正なアクセスです。”);
}
// 確定した安全な値のみを使用
$safe_filename = $allowed_files[$input];
exec(“convert ” . escapeshellarg($safe_filename) . ” output.jpg”);
?>
Python編:厳格な正規表現によるバリデーション
どうしても可変の入力が必要な場合は、正規表現で「英数字以外は一文字も通さない」という制約を課す。
import re
import subprocess
def process_image(user_input):
# 英数字とアンダースコア、ドットのみを許可
if not re.fullmatch(r'[a-zA-Z0-9_\.]+’, user_input):
raise ValueError(“不適切な入力形式です”)
# リスト形式でコマンドを渡す(shell=Falseを維持することでコマンド連結を防ぐ)
cmd = [“convert”, user_input, “output.jpg”]
subprocess.run(cmd, check=True)
—
4. 運用サイドの防壁:IAMと最小権限の原則
コードがどれだけ完璧でも、サーバー自体に「root」や「Webサーバー実行ユーザー」が過剰な権限を持っていれば、万が一の被害は甚大になる。
- IAMの制約: アプリケーションが実行するプロセスには、必要最小限のディレクトリへの書き込み権限しか与えない。
- Dockerコンテナ化: コマンド実行が必要な処理は、メインのWebアプリケーションとは切り離し、権限を剥奪した独立コンテナ内で実行する。
- WAFの活用: AWS WAF等で
|,;,&,$()などのメタ文字をブロックするルールを常時適用しておくのは最低限の嗜みだ。
—
最後に:エンジニアとしての矜持
「動けばいい」というコードは、いつか必ず誰かのインシデントになる。今回紹介したホワイトリスト方式は、実装にわずかな手間はかかるかもしれない。だが、その手間は、深夜のインシデント対応や謝罪会見を回避するための「最高にコストパフォーマンスの良い投資」だ。
自分の書いたコードに責任を持つこと。それが、世界最高峰のセキュリティを支えるエンジニアの第一歩だ。現場からは以上だ。
コメント