ケース 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/jpeg、image/png、image/gif のいずれかであるかをチェックしている。
Burp Suite などでリクエストをインターセプトし、Content-Type を image/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 ではファイル名としてそのまま保存され、通常は実行されない。