ファイルアップロード脆弱性の検証と回避手法(前編)

ケース 1:クライアントサイド検証のみ

PHPファイル(例: aaa.php)をアップロードしようとすると、ブラウザ上で即座にエラーメッセージが表示され、通信自体がサーバーに到達しない。これはファイル拡張子のチェックがJavaScriptで行われていることを示している。

開発者ツール(F12)でソースを確認すると、checkFile() 関数内で許可された拡張子(.jpg|.png|.gif)以外は拒否される仕組みになっている。この関数を無効化するか、JavaScriptを無効にしてリクエストを送信することで、制限を回避できる。

function checkFile() {
    const fileInput = document.getElementsByName('upload_file')[0].value;
    if (!fileInput) {
        alert("ファイルを選択してください!");
        return false;
    }
    const allowedExts = ".jpg|.png|.gif";
    const ext = fileInput.substring(fileInput.lastIndexOf("."));
    if (allowedExts.indexOf(ext + "|") === -1) {
        alert(`許可されていない拡張子です。許可: ${allowedExts}, 入力: ${ext}`);
        return false;
    }
}

ケース 2:Content-Type によるサーバーサイド検証

PHPファイルを送信すると「ファイルタイプ不正」と表示される。ソースコードを確認すると、$_FILES['upload_file']['type'] の値が image/jpegimage/pngimage/gif のいずれかであるかをチェックしている。

Burp Suite などでリクエストをインターセプトし、Content-Typeimage/jpeg に偽装することで、検証を通過できる。

if ($_FILES['upload_file']['type'] === 'image/jpeg' ||
    $_FILES['upload_file']['type'] === 'image/png' ||
    $_FILES['upload_file']['type'] === 'image/gif') {
    // アップロード処理
}

ケース 3:拡張子ブラックリスト(基本形)

.php.asp などの拡張子が明示的に拒否されている。ただし、.php5.phtml.pht などは含まれていない。

これらの代替拡張子を使用してアップロードし、Apache の設定でそれらが PHP として解釈されるようになっていれば、コード実行が可能となる。

$deny_ext = ['.asp', '.aspx', '.php', '.jsp'];
$file_ext = strtolower(strrchr($_FILES['upload_file']['name'], '.'));
if (!in_array($file_ext, $deny_ext)) {
    // アップロード許可
}

Apache の httpd.conf に以下のような設定が必要:

AddType application/x-httpd-php .php5 .phtml .pht

また、.htaccess がブラックリストに含まれていない場合、そのファイルをアップロードして以下のように記述することで、任意拡張子をPHPとして実行可能にする:

AddType application/x-httpd-php .jpg

ケース 4:拡張子ブラックリスト(強化版)

より多くの拡張子(.php1.php5.pHp.jSpa など)がフィルタリングされているが、依然として .htaccess が除外されていない。

.htaccess に以下を記述:

SetHandler application/x-httpd-php

これにより、ディレクトリ内のすべてのファイルがPHPとして処理される。その後、.jpg 拡張子のWebシェルをアップロードすれば実行可能となる。

ケース 5:大文字小文字による回避

ブラックリストには .Php.PHp5 などが含まれているが、ファイル名の拡張子を取得後に小文字変換は行われているものの、リスト自体に網羅されていないパターン(例: .PhP)が存在する可能性がある。

ただし、このケースでは実際には小文字変換後に比較しているため、真の回避は別途必要。しかし、もし小文字変換が漏れていた場合、shell.PHP のように大文字混じりの拡張子で突破できる。

ケース 6:末尾スペースによる回避(Windows 特性)

ファイル名の末尾にスペースを付与(例: shell.php[スペース])すると、Windows では自動的にスペースが削除されるが、PHPスクリプト側ではスペースを含んだまま拡張子を抽出する。

このとき、trim() がファイル名全体に適用されているが、拡張子抽出後に再度 trim() が行われていない場合、.php[スペース] はブラックリストに一致せず、アップロードが成功する。

ケース 7:末尾ドットによる回避

このバージョンではファイル名の末尾スペースは trim() で除去されるが、末尾のドット(.)は除去されない。Windows では file.php. というファイル名は file.php として保存される。

したがって、shell.php. をアップロードすると、拡張子として .php. が抽出され、ブラックリスト(.php のみ)にヒットせず、アップロードが成功する。

ケース 8:NTFS Alternate Data Streams(::$DATA)

Windows の NTFS ファイルシステムでは、filename.ext::$DATA という形式でファイルストリームを操作できる。PHPスクリプトがこの文字列を適切に処理していない場合、shell.php::$DATA というファイル名を送信すると、拡張子として .php::$DATA が抽出される。

ブラックリストに ::$DATA の除去処理が含まれていない場合、この拡張子は拒否されず、アップロードが成功する。実際には Windows が ::$DATA を無視して shell.php として保存する。

ケース 9・10:複合的な末尾操作(ドット+スペース)

ファイル名の末尾に「ドット+スペース+ドット」(例: shell.php. .)を付与すると、Windows はこれを正規化して shell.php として保存する。

スクリプトが一度だけ末尾ドットを削除(deldot())し、その後 trim() を適用しても、中間のスペースとドットが残るため、拡張子として . ..php. などが抽出され、ブラックリストに一致しない可能性がある。

この手法は Windows 環境下でのみ有効であり、Linux ではファイル名としてそのまま保存され、通常は実行されない。

タグ: PHP ファイルアップロード セキュリティテスト Apache .htaccess

8月2日 04:08 投稿