こんにちは!
クライアントサイドよりPOSTされたファイルに対して、ディレクトリトラバーサル対策等の他にも着目すべき点はありますが、今回は「クライアントサイドのフォームでPOSTしたファイルのバリデーション」のうち、「ファイルの拡張子の偽装チェック」を行うことに着目します!
そもそも拡張子の偽装とは何なのか
mv sample.png sample.jpg
上記例のように内部バイナリのコンバート処理を行うことなく、拡張子を変更することであります。
しかし、Hexとして上記ファイルを検閲すると、
pngファイルと確認することの出来るマジックナンバーがバイナリヘッダーに存在しますね!
では、このファイル名上の拡張子がjpgで内部的にpngであるファイルをクライアントサイドのフォームよりPOSTすると、PHPではどういった挙動になるのか確認してみましょう!
サーバーサイドで拡張子が偽装されたファイルのバリデーションをする
まず前提条件として、下記のフィールドよりPOSTされたファイルを扱うとします。
<input type="file" name="up-file">
その際に、sample.png を sample.jpg にリネームしたファイル
をPOSTします。
すると、
echo $_FILES["up-file"]["type"];
では、MIME-Typeとして
image/jpeg
を確認できるはずです。
これは、MIME-Type一覧ページで確認できる通り、jpgファイル
として認識されていることがわかる証拠です。
しかし、
$f_info = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file(
$f_info,
$_FILES["up-file"]['tmp_name']
);
finfo_close($f_info); //念の為メモリ空間に開いたストリームの破棄を行う
echo $mime;
として、POSTされたファイルのバイナリからMIME-Typeを取得すると、
image/png
のように本来はpngファイル
であることを確認できます。
まとめ
$_FILES["識別子"]["type"]
で確認出来るMIME-Typeは、ファイル名上の拡張子から推測されるものであることがわかりました!
なので、
- ファイル名上の拡張子から推測できるMIME-Type
- ファイルのバイナリヘッダーから推測出来るMIME-Type
の合致チェックを行って、よりセキュアにファイルのバリデーションを行おこうと思いました!
以下は、サンプルコードです!
$file = $_FILES["識別子"];
$file_mime_type = $file["type"];
$f_info = finfo_open(FILEINFO_MIME_TYPE);
$binary_mime_type = finfo_file(
$f_info,
$file['tmp_name']
);
finfo_close($f_info);
$is_same_mime_type = $file_mime_type === $binary_mime_type;
ありがとうございました!!!
追記
POSTされたファイルのHexをPHP上で確認する方法。
$binary = file_get_contents(
$_FILES["識別子"]["tmp_name"]
);
echo bin2hex($binary);
bin2hex(正確にはBinary to Hex)でBinaryデータをHexにコンバートします!
上記コード中の
$_FILES["識別子"]["tmp_name"]
には、POSTされたファイルの一時的(temporary)なパスが文字列(String型)として格納されているだけなので、バイナリデータとして扱うためにfile_get_contents
をしています。
あとは、
事ができます!