ファイルアップロード脆弱性:その「利便性」がサーバーの命取りになる理由
こんにちは。現場で日々インシデントと対峙していると、どんなに堅牢に見えるシステムでも、たった一つの「ファイルアップロード機能」の設計ミスで崩壊する様を何度も見てきました。
開発者は「画像アップロード機能」を作ったつもりでも、攻撃者は「サーバーのシェル(制御権)を奪うための入り口」を作ったと認識しています。今回は、この古くて新しい、しかし未だに最強クラスの破壊力を持つ脆弱性について、攻撃側の視点から紐解き、鉄壁の防御策を伝授します。
—
1. 攻撃者が狙う「盲点」:チェックをすり抜けるテクニック
多くの開発者が実装する「拡張子チェック」や「MIMEタイプチェック」は、実はザルです。攻撃者は以下の手法でこれらを無効化します。
- 拡張子の偽装:
shell.phpをアップロードしようとすると弾かれるため、shell.php.jpgやshell.php%00.jpg(Nullバイト攻撃)、あるいは設定ミスを突いた.htaccessのアップロードを行います。 - MIMEタイプの改ざん: リクエストヘッダーの
Content-Type: image/jpegをサーバー側で検証している場合、Burp Suite等のプロキシツールを使えば、中身がPHPコードでもヘッダーを書き換えるだけでいとも簡単にパスします。 - ファイル署名の無視: 拡張子やMIMEタイプだけを見て、バイナリの中身(マジックナンバー)を確認していないサーバーは非常に危険です。
—
2. 撃墜される前に:セキュアな実装の鉄則
ファイルを保存する際、単に「アップロードされたファイル名」をそのまま保存していませんか? それが最大の過ちです。以下の3つのルールを徹底してください。
1. ファイル名を完全にランダム化し、拡張子をホワイトリストで強制する。
2. Webサーバーの実行権限から隔離されたディレクトリに保存する。
3. アップロード先ディレクトリでスクリプトの実行を禁止する。
PHPによるセキュアなアップロード処理例
<?php
// 1. ファイル名のランダム化と拡張子のホワイトリスト制限
$allowed_extensions = ['jpg', 'jpeg', 'png'];
$file_extension = pathinfo($_FILES['user_file']['name'], PATHINFO_EXTENSION);
if (!in_array(strtolower($file_extension), $allowed_extensions)) {
die("許可されていない拡張子です。");
}
// 2. 元のファイル名は捨て、安全なランダム名を生成
$new_filename = bin2hex(random_bytes(16)) . '.' . $file_extension;
$upload_dir = '/var/www/uploads/'; // 公開ディレクトリの外が理想的
if (move_uploaded_file($_FILES['user_file']['tmp_name'], $upload_dir . $new_filename)) {
echo "アップロード成功";
}
?>
—
3. インフラ側で「止めを刺す」:Nginxの設定
アプリケーション側のバグは、インフラの設定で多重防御(Defense in Depth)をかけるのがプロの流儀です。たとえ悪意のあるコードが置かれても、それが実行されなければ被害はゼロです。
以下は、NginxでアップロードディレクトリのPHP実行を明示的に禁止する設定です。
# 特定のアップロードディレクトリでのPHP実行を禁止
location /uploads/ {
# ここに配置されたファイルは、たとえ.php拡張子でも実行させない
location ~ \.php$ {
deny all;
}
}
# さらに、.htaccessや他の隠しファイルを無効化する
location ~ /\.(?!well-known).* {
deny all;
}
—
4. クラウド時代の守り:IAMとストレージの分離
現代のWeb開発では、ファイルをWebサーバー内に置くことは推奨されません。AWS S3のようなオブジェクトストレージを使用し、Webサーバーには直接ファイルを触らせない構成(IAMロールによる最小権限の付与)を構築してください。
- S3バケットポリシー: WebサーバーのIAMロールに対してのみ
s3:PutObjectを許可し、s3:GetObjectはCloudFront経由に限定する。 - Content-Dispositionヘッダー: S3から配信する際は
Content-Disposition: attachmentを強制し、ブラウザが勝手にスクリプトとして解釈するのを防ぐ。
—
最後に:エンジニアとしての心得
脆弱性は技術的な欠陥であると同時に、「性善説」に基づいた設計の産物でもあります。
「ユーザーが変なファイルをアップロードするはずがない」「拡張子チェックをしているから大丈夫」という思い込みが、次のインシデントの火種になります。「アップロードされたものはすべて攻撃コードである」という疑念を持ち、今日紹介した対策を実装に組み込んでください。
もし、今運用しているシステムで「ファイル名がそのまま保存されている」「アップロードディレクトリに直接アクセスできる」箇所を見つけたら、すぐに修正のチケットを切ってください。それが、あなたのシステムを守る唯一の道です。
コメント