PHPでMIME-Typeを判定する方法
サイト作成時にユーザーからファイルをアップロードしてもらう場合に気をつけることとして「それが正しいフォーマットかどうか」という点があります。
つまり偽装ファイルでないかどうかということですね。 これを判定するにはMIME-Typeを調べるのが良いとされています。
Webブラウザーからの情報は信用しない=基本
正しいファイルフォーマットを知るためには、ファイルのMIME-Type情報を取得して判断するのですが、ブラウザーがサーバーに伝えるMIME-Typeはウソの情報を与えることもできます。
Webブラウザは拡張子を変えるだけで騙される!?
突然ですが、適当なテキストファイルを作成して拡張子を「.gif」に変えてみてください。 Windowsだと警告がでますが、無視してOKするとテキストファイルが画像アイコンに変わります。
これだけでWebブラウザーは「画像だ」と認識してしまいます。 MIME-Typeでいうと「image/gif」になっちゃうんですね。 ヤバいですよね。
ですから、次のような「$_FILES」配列の「type」を見てMIME-Typeを判定するのは絶対に止めておきましょう。
$mime = $_FILES["myfile"]["type"]; // 嘘のMIME-Typeが入ってしまうので×
アップロードファイルの形式チェックはサーバー上で!
アップロードしたファイルタイプの判定はサーバー上で行いましょう。
mime_content_type() や FileInfoモジュールは使わない
PHPではmime_content_type()関数(公式では非推奨)や、標準搭載されていないFileInfoモジュールを使うとMIME-Typeを判定することができますが、場合によってはこれらの方法が使えないこともあると思います。
そこで、ここではより多くのサーバーで利用できるように汎用的な方法をご紹介します。
どうすればいいかというと、アップロードされたファイルをサーバー上でコマンドを使って判別しましょう。
サーバーがLinuxであれば「file」コマンドという便利な命令があります。 これにオプションで「bi」をつけて実行することでMIME-Typeを判別してくれます。
PHPだと以下のようにします。 まず入力フォームを以下のようにしてみます。
<form action="upload.php" method="post" enctype="multipart/form-data"> <input type="file" name="myfile" /> <input type="submit" value="送信" /> </form>
そして、受信先の upload.php を以下のようにしてみましょう。
<?php
$path = $_FILES['myfile']['tmp_name'];
$mime = shell_exec('file -bi '.escapeshellcmd($path));
$mime = trim($mime);
$mime = preg_replace("/ [^ ]*/", "", $mime);
print($mime);
?>
これで$type変数にMIME-Typeが格納されているので、正しい判定が行えます。
trim()関数を適用させている理由は、shell_exec()関数で帰ってきた文字列に改行コードが含まれているので、それを除去するためです。 また「file -bi」で取得したMIME-TypeにはMIME-Typeが2つ記載されたり、文字コードが含まれたりするので、空白以降をpreg_replace()で除去します。
次に、一般的に利用されるファイルタイプのMIME-Typeリストをご紹介しましょう。
画像系
拡張子 | MIME-Typeリスト |
---|---|
GIF | image/gif image/x-xbitmap image/gi_ |
JPG または JPEG | image/jpeg image/jpg image/jp_ application/jpg application/x-jpg image/pjpeg image/pipeg image/vnd.swiftview-jpeg |
PNG | image/png application/png application/x-png |
BMP | image/bmp image/x-bmp image/x-bitmap image/x-xbitmap image/x-win-bitmap image/x-windows-bmp image/ms-bmp image/x-ms-bmp application/bmp application/x-bmp application/x-win-bitmap |
ドキュメント系
拡張子 | MIME-Typeリスト |
---|---|
application/pdf application/x-pdf application/acrobat applications/vnd.pdf text/pdf text/x-pdf |
圧縮ファイル系
拡張子 | MIME-Typeリスト |
---|---|
ZIP | application/zip application/x-zip application/x-zip-compressed application/octet-stream application/x-compress application/x-compressed multipart/x-zip |
Microsoft Office系のファイルはMIME-Typeも間違われる!?
実は、この方法だとWord, Excel, PowerPoint などのOffice系のファイルを正しく判定できません。(Microsoft は度々厄介な仕様を作るものですね。。) Office2003も、Office2007以降に登場した docx, xlsx, pptx といった新しく登場した拡張子も間違って判定されてしまいます。
これらを正しく判定するには、中身のバイトを検索して正しいバイト列になっているかどうか、といった複雑な判定になります。
バイト列をチェックする方法
バイト列を正確に見て判断できれば、Office2003系のファイルタイプを判断することができます。 判断方法は、通常のファイルハンドルを取得し、指定の開始バイトまでファイルポインタを移動させ、fread()で10バイトほど読み込みます。 それをbin2hex()で16進数に変換して比較します。
以下の例は512バイト目までファイルポインタを移動させて、10バイトを16進数文字列に変換しています。
if (($fh = fopen($path, "rb")) !== FALSE) { fseek($fh, 512); $hex = strtoupper(bin2hex(fread($fh, 10))); }
Office2003以前のファイルはMIME-Typeのみで判別不可
Office2003以前のファイルについては、ExcelやPowerPointもすべて「application/msword」と判定されるので、以下の表に従ってバイト列を比較して判断しましょう。
Office2003以前のファイルは0バイト目から確認しても全部同じバイト列なので、Office系かどうかを判断するだけの分岐に利用し、ファイルタイプを比較するためには512バイト目からのバイト列を比較してください。
たとえば、Excelシート (拡張子:xls)のファイルかどうかを判断したければ、次のように正規表現を作成して判断します。
if (($fh = fopen($path, "rb")) !== FALSE) { // 0バイト目からの判定 fseek($fh, 0); $hex = strtoupper(bin2hex(fread($fh, 10))); if (preg_match("/^D0CF11E0A1B11AE100.*/", $hex)) { // 512バイト目からの判定 fseek($fh, 512); $hex = strtoupper(bin2hex(fread($fh, 10))); if (preg_match("/^(?:FDFFFFFF(?:(?:10|1F|22|23|28|29)02|20000000)|0908100000060500).*/", $hex)) { print("Excelのファイルです。"); } } fclose($fh); }
Office2003以前用ファイルタイプ判別表
拡張子 | MIME-Typeリスト | 開始バイト | バイト列16進数リスト |
---|---|---|---|
DOC | application/msword (公式) application/doc appl/text application/vnd.msword application/vnd.ms-word application/winword application/word application/x-msw6 application/x-msword |
0 | D0CF11E0A1B11AE100 |
512 | ECA5C100 | ||
XLS | application/vnd.ms-excel (公式) application/msexcel application/x-msexcel application/x-ms-excel application/vnd.ms-excel application/x-excel application/x-dos_ms_excel application/xls |
0 | D0CF11E0A1B11AE100 |
512 | FDFFFFFF1002 FDFFFFFF1F02 FDFFFFFF2202 FDFFFFFF2302 FDFFFFFF2802 FDFFFFFF2902 FDFFFFFF20000000 0908100000060500 |
||
PPT | application/vnd.ms-powerpoint (公式) application/mspowerpoint application/ms-powerpoint application/mspowerpnt application/vnd-mspowerpoint application/powerpoint application/x-powerpoint application/x-m |
0 | D0CF11E0A1B11AE100 |
512 | FDFFFFFF0E000000 FDFFFFFF1C000000 FDFFFFFF43000000 FDFFFFFF10000000 006E1EF0 A0461DF0 |
Office2007以降のファイルはバイト列でも無理
Office2007以降のOffice系ファイル(docx, xlsx, pptx など)のMIME-Typeは実はすべて「application/zip」や「application/x-zip」と判断されてしまう。
中身を見るとほんとうにzipファイルなのです。 ですので、この判定をされたら拡張子を調べてそれぞれのファイルのMIME-Type情報に変更しましょう。
9/5追記:Office2007以降のファイルでも判定する方法を書きました。
おまけ
最後に、各ファイルがどういったものか、MIME-Typeは何なのかを調べるためのサイトを2つご紹介しておきましょう。 どちらも英語ですが、特にFilext.comはいろいろと参考になると思いますよ。
- ファイルの拡張子から、詳しい情報を教えてくれる「Filext.com」
- mime-typeを判定するための辞書「FILE SIGNATURES TABLE」
2011-04-27
1 件のコメント
こんばんは。記事、とても参考になりました。
確かに、$_FILESとかは信頼できんですよね。どうしても、ファイルアップロードって、神経質にならなければならないし、でもアップロードのニーズはどうしてもあるし、なので、悩ましいと思います。
ところで、プラットフォームによっては、fileコマンドのオプション指定が異なる場合があるので、注意がいるかと思い、コメント残します。
例えば、OSXで開発している場合などでは(まさに僕がそうです)、file -bi hogehoge.jpgとかすると、mime-typeが表示されず、単に”regular file”が返されます。テスト環境が OSXの場合は注意が必要です。
他のプラットフォームについては調べていないのですが、ケースバイケースで、アプリの本番環境に合わせたチェックの実装が必要となるかと。
では!