--- title: 【PHP】なぜ、$_FILES['userfile']['type'] の値は信用できないの? tags: PHP HTTP HTML author: 7968 slide: false --- この記事を理解するには、HTTPの基本的な理解が必要です。 リクエスト?レスポンス?何それ?という方は、[ここ](http://qiita.com/7968/items/4bf4d6f28284146c288f) をご一読ください。 PHPマニュアルの [POST メソッドによるアップロード](http://php.net/manual/ja/features.file-upload.post-method.php) には下記の説明があります。 > $_FILES['userfile']['type'] > ファイルの MIME 型。ただし、ブラウザがこの情報を提供する場合。 例えば、"image/gif" のようになります。 この MIME 型は PHP 側ではチェックされません。そのため、 **この値は信用できません。** > > 引用:[POST メソッドによるアップロード](http://php.net/manual/ja/features.file-upload.post-method.php) `$_FILES['userfile']['type']` は、ファイルの MIME 型、つまりファイルの種類を表します。 MIMEタイプは `タイプ/サブタイプ` のように `/` で区切って表します。 各ファイルごとにMIMEタイプは決まっています。 |ファイル|MIMEタイプ| |:--|:--| |GIF|`image/gif`| |JPEG|`image/jpeg`| |PNG|`image/png`| |HTML|`text/html`| |CSS|`text/css`| |JavaScript|`text/javascript`| MIMEタイプは、Internet Assigned Numbers Authority(IANA、アイアナ)という組織が管理しています。 [Media Types](https://www.iana.org/assignments/media-types/media-types.xhtml) に一覧で掲載されています。 `$_FILES['userfile']['type']` は、ブラウザが知らせてくれたファイルの種類をもとにしています。 具体的にいうとリクエストメッセージにあるMIMEタイプをもとにしています。 実際に [Fidder](http://www.telerik.com/fiddler) を使って、どのようなリクエストメッセージになっているのか確認していきます。 今回は、Windows7のXAMPP環境でGoogleChromeを使って確認します。 ファイルを選択し、送信すると、`$_FILES['userfile']['type']` を表示してくるページを作成します。 ![php-file-none.png](https://qiita-image-store.s3.amazonaws.com/0/72985/39ad819c-81a4-153d-bc02-619f81fca97e.png) ```php:PHP PHP

Pictures:

``` JPEG なら `image/jpeg` 、GIFなら `image/gif`、PNGなら `image/png` と表示されるはずです。 実際に JPEG ファイルを選択して、送信してみます。 ![php-file.png](https://qiita-image-store.s3.amazonaws.com/0/72985/f14afefc-3ec8-ae37-b92c-90bd5a3c07dd.png) 予想通り、`image/jpeg` と表示されました。 このときのリクエストメッセージは下記です。 ```http:HTTP(リクエストメッセージ) POST http://localhost/test/ HTTP/1.1 Host: localhost Connection: keep-alive Content-Length: 64201 Cache-Control: max-age=0 Origin: http://localhost Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36 Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7c22X5usFk6Z8VCU Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Referer: http://localhost/test/ Accept-Encoding: gzip, deflate, br Accept-Language: ja,en-US;q=0.8,en;q=0.6 ------WebKitFormBoundary7c22X5usFk6Z8VCU Content-Disposition: form-data; name="pictures"; filename="01.jpg" Content-Type: image/jpeg 以下、画像データ(バイナリ) ``` 注目すべきは、メッセージボディの `Content-Type: image/jpeg` です。 MIMEタイプが記述されてますね。 `$_FILES['userfile']['type']` の値は、このリクエストメッセージの `Content-Type: image/jpeg` をもとにしていたということです。 この結果を見て、一つ気になる点があります。 ブラウザはこの `Content-Type: image/jpeg` をどのように判断したのでしょうか? 確かめて見ましょう。 テキストエディタを開いて、下記のようにscriptを記述して、`xss.png` と名前をつけて保存します。 ```txt:xss.png ``` 拡張子は `png` になっていますが、中にはscriptタグが書かれています。 このファイルを選択して、送信してみましょう。 ![php-file-png.png](https://qiita-image-store.s3.amazonaws.com/0/72985/eba25746-67af-c8c8-8020-955a281e2400.png) `image/png` と表示されました。 このときのリクエストメッセージは下記です。 ```http:HTTP(リクエストメッセージ) POST http://localhost/test/ HTTP/1.1 Host: localhost Connection: keep-alive Content-Length: 214 Cache-Control: max-age=0 Origin: http://localhost Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36 Content-Type: multipart/form-data; boundary=----WebKitFormBoundarySqOUDVmXAGnTyQYn Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Referer: http://localhost/test/ Accept-Encoding: gzip, deflate, br Accept-Language: ja,en-US;q=0.8,en;q=0.6 ------WebKitFormBoundarySqOUDVmXAGnTyQYn Content-Disposition: form-data; name="pictures"; filename="xss.png" Content-Type: image/png ------WebKitFormBoundarySqOUDVmXAGnTyQYn-- ``` メッセージボディには、`Content-Type: image/png` とあります。 `xss.png` というファイル名を `xss.jpg` に変更して、送信すると、メッセージボディの `Content-Type` は `image/jpeg` になりました。 **リクエストメッセージの MIMEタイプ は、拡張子をもとにしているため、全く信用できないということです。** そもそも、今回の `Content-Type` に限らず、リクエストメッセージ全体を偽装することが可能なので、リクエストメッセージで送られてくる値は全て信用できません。 そのため、サーバーサイド(PHP)側で確認する必要があります。 他の Firefox、Opera でも確認しましたが、拡張子にあわせてMIMEタイプが変わりました。 あれ... Internet Explorer がないよね... # Internet Explorer 11 の場合は? Internet Explorer 11 で `xss.jpg` を選択して送信したときのリクエストメッセージが下記です。 ```http:HTTP(リクエストメッセージ) POST http://localhost/test/ HTTP/1.1 Accept: text/html, application/xhtml+xml, */* Referer: http://localhost/test/ Accept-Language: ja-JP User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko Content-Type: multipart/form-data; boundary=---------------------------7e125bc25117e Accept-Encoding: gzip, deflate Connection: Keep-Alive Content-Length: 245 DNT: 1 Host: localhost Pragma: no-cache -----------------------------7e125bc25117e Content-Disposition: form-data; name="pictures"; filename="C:\Users\7968\Desktop\xss.jpg" Content-Type: text/plain -----------------------------7e125bc25117e-- ``` メッセージボディにある `Content-Type` は `text/plain` です。 `xss.png` に拡張子を変更しても、`text/plain` でした。 Internet Explorer には、ファイルの中身から、MIMEタイプを推測する機能があります。 おそらくですが、それが機能して `text/plain` と判断したのではないかと思います。 **Internet Explorer の場合は MIMEタイプ が他のブラウザと異なりますが、どちらにせよ、サーバーサイド(PHP)側でチェックする必要があります。** 続き:[【PHP】アップロードされたファイルの種類をどうやって確認するの?](http://qiita.com/7968/items/e4b3adeb9832a9c32c52) # note note でも記事を公開してるので、興味がある方はご覧ください。 ・[【初学者向けコードリーディング】 PHP の TODO アプリのコードを一緒に読み解こう](https://note.mu/7968/n/n46a8beb87c93)