はじめに
先日、exif_imagetype()だけで画像かどうかを判別してはいけないというエントリを書いたが、その後、別のWordPressプラグインで同様の脆弱性が発見された。そちらではgetimagesize()を使っていたので取り上げる。
概要
脆弱性の見つかったプラグインはPowerPressというポッドキャストのためのプラグイン。画像のふりをしたPHPスクリプトをアップロードして任意のコードを実行することが可能だが、脆弱性を利用するためにはサイト管理者の権限が必要なため、サイトの運用によってはそこまで危険性は無い。
解説
PoCは以下の通りhttps://wpscan.com/vulnerability/10427 で、脆弱性の修正箇所は https://plugins.trac.wordpress.org/changeset/2396808 になる。
PoCの一部を見ると、POSTする値に以下のような記述がある。ここから、PNGに偽装したup.phpというファイルをアップロード、実行させようとしていることが分かる。
"------WebKitFormBoundary5Ac7Ayyi2qVtiLqA\r\n" +
"Content-Disposition: form-data; name=\"rss2_image_file\"; filename=\"up.php\"\r\n" +
"Content-Type: image/png\r\n" +
"\r\n" +
"\x89PNG\r\n" +
"\x1a\n" +
"\x3c?php phpinfo(); ?\x3e\r\n" +
"------WebKitFormBoundary5Ac7Ayyi2qVtiLqA--\r\n";
このup.phpをファイルに切り出すと以下のようになる。
$ hexdump up.php
0000000 5089 474e 0a0d 0a1a 3f3c 6870 2070 6870
0000010 6970 666e 286f 3b29 3f20 3e3e
000001c
$ file up.php
up.php: PNG image data, 1885957734 x 1864902971, 32-bit
プラグインの中では、$ImageData = @getimagesize($temp);
として取得した値を利用して画像かどうかを判定してしまっているが、PHP.netのドキュメントにも以下のように記載されている。
警告
この関数は、filename が適切な画像ファイルであることを想定しています。 画像以外のファイルを渡してもそれを画像だと判断してしまい、関数の処理は成功するでしょう。 しかし、配列には意味のない値が含まれる場合があります。getimagesize() を使って、そのファイルが画像であるかどうかを確かめることはできません。 そのようなことをしたい場合は、そのために用意されたソリューション (Fileinfo 拡張モジュールなど) を使いましょう。
getimagesize('up.php');
で得られる値は以下のようになる。
array(6) {
[0]=>
int(1885957734)
[1]=>
int(1864902971)
[2]=>
int(3)
[3]=>
string(38) "width="1885957734" height="1864902971""
["bits"]=>
int(32)
["mime"]=>
string(9) "image/png"
}
なお、PHP.netの注意書き通りにFileinfoを利用しても、今回は以下のようになってしまうため、代替とはならない。
string(47) "PNG image data, 1885957734 x 1864902971, 32-bit"
結局、当該プラグインではアップロードを許可するファイルの拡張子を制限することで対応している。