【実務・中級編】 ファイルアップロード脆弱性とWebシェル実行 – オフェンシブセキュリティ & リバースエンジニアリング防御ガイド

ファイルアップロード脆弱性:その「利便性」がサーバーの命取りになる理由

こんにちは。現場で日々インシデントと対峙していると、どんなに堅牢に見えるシステムでも、たった一つの「ファイルアップロード機能」の設計ミスで崩壊する様を何度も見てきました。

開発者は「画像アップロード機能」を作ったつもりでも、攻撃者は「サーバーのシェル(制御権)を奪うための入り口」を作ったと認識しています。今回は、この古くて新しい、しかし未だに最強クラスの破壊力を持つ脆弱性について、攻撃側の視点から紐解き、鉄壁の防御策を伝授します。

—

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 を強制し、ブラウザが勝手にスクリプトとして解釈するのを防ぐ。

—

最後に:エンジニアとしての心得

脆弱性は技術的な欠陥であると同時に、「性善説」に基づいた設計の産物でもあります。

「ユーザーが変なファイルをアップロードするはずがない」「拡張子チェックをしているから大丈夫」という思い込みが、次のインシデントの火種になります。「アップロードされたものはすべて攻撃コードである」という疑念を持ち、今日紹介した対策を実装に組み込んでください。

もし、今運用しているシステムで「ファイル名がそのまま保存されている」「アップロードディレクトリに直接アクセスできる」箇所を見つけたら、すぐに修正のチケットを切ってください。それが、あなたのシステムを守る唯一の道です。

コメント

タイトルとURLをコピーしました