http://qiita.com/sakana_kirai/items/6e512f7aea2898a6e8f2
http://qiita.com/mpyw/items/939964377766a54d4682
アップロードされたファイルについて、getimagesize()やfinfo::file()でMIMEタイプを判定しています。
<?php
// hoge.pngを判定
$imagesize = getimagesize('path/to/hoge.png');
$finfo = new finfo(FILEINFO_MIME_TYPE);
$finfofile = $finfo->file('path/to/hoge.png');
var_dump($imagesize, $finfofile);
ここでhoge.pngの中身を以下のように書き換えてみます。
GIF8<script>alert("xss");</script>
結果。
array(6) {
[0]=>
int(29283)
[1]=>
int(28777)
[2]=>
int(1)
[3]=>
string(28) "width="29283" height="28777""
["channels"]=>
int(3)
["mime"]=>
string(9) "image/gif"
}
string(9) "image/gif"
なんと頭に「GIF8」と書いておくだけでMIMEタイプの判定をすり抜けてしまいました。
まあMIMEタイプがimage/gifだとわかったので、ファイルは見えないところに隠しておいて、出力時にきちんとContent-Typeヘッダを出してあげれば問題ありません。
<?php
header('Content-type: image/gif');
readfile('path/to/hoge.png');
これで完璧!
お、おう。
これはIEでのみ発生する可能性のあるXSSです。
IEは設定されたContent-Typeをあまり気にせず、ファイルの中身を見て適切な形式で出力してくれるという素敵極まりない機能が付いています。
で、IEは「GIF8」をgifとはみなさず、後続のHTMLを見てHTMLと判断してくれやがるようです。
「GIF87」にすると画像と判断されます。
X-Content-Type-OptionsというHTTPヘッダによって、この余計で傍迷惑な機能を止めることができます。
http://d.hatena.ne.jp/hasegawayosuke/20110106/p1
http://blog.everqueue.com/chiba/2011/01/06/484/
<?php
header('Content-type: image/gif');
header('X-Content-Type-Options: nosniff');
readfile('path/to/hoge.png');
これでIEでもXSSが発生しないようになります。
結論としては、とりあえずあらゆる出力にX-Content-Type-Optionsを書いとけ、というところでしょうか。
ちなみに
http://d.hatena.ne.jp/hasegawayosuke/20110106/p1
X-Content-Type-Options: nosniff つかわないやつは死ねばいいのに!
とか書いてるこのページにはX-Content-Type-Optionsが設定されていません。