はじまり
マイナーなWordPressプラグインSimple:Pressに脆弱性が見つかりました。認証無しでPHPスクリプトをアップロードして任意コード実行できる脆弱性なので致命的なものと言えます。
この記事の数日後、Qiitaで@satorunooshieさんのPHPによる(複数)画像アップロード処理という記事を目にすることになります。ここで2つの記事に関連性が見られたので、本稿を書こうと思います。
意外な落とし穴
問題となったSimple:Pressには、ファイルアップロード時に認証を回避できる脆弱性があったわけですが、それでもまだアップロードされたファイルが画像かどうかチェックする機構があれば、任意コード実行のような大きな問題にはならなかったはずです。
ただし、実際にはそのような機構が存在していたにもかかわらず、落とし穴があったため期待通りに機能していませんでした。
exif_imagetype()の問題
Simple:Pressは画像であるかどうかをexif_imagetype()
を使って判別しています。
# check image file mimetype
$mimetype = 0;
$mimetype = exif_imagetype($_FILES['uploadfile']['tmp_name']);
if (empty($mimetype) || $mimetype == 0 || $mimetype > 3) {
echo 'invalid';
die();
}
@satorunooshieさんの記事においてもほぼ同様の処理を行っている部分があります。
$tempfile = $_FILES['photo']['tmp_name'];
switch (@exif_imagetype($tempfile)) {
case 1:
$photo .= '.gif';
break;
case 2:
$photo .= '.jpg';
break;
case 3:
$photo .= '.png';
break;
default:
header('Location: upload.php');
exit();
}
実はここに問題があり、exif_imagetype()
は画像かどうかの判別が**「画像の先頭バイトを読み そのサインを調べます」**となっているため、画像に偽装した有害なファイルをアップロードすることが可能です。
(ただし、@satorunooshieさんのケースでは、拡張子を明示的に付与していることで、PHPスクリプトとしては実行できなくなっています。)
問題の検証
偽装したgif画像として、以下のようなテキストファイルをdummy.gifとしてuser_edit.phpにアップロードします。アップロードされた画像のURLにアクセスしても、画像としては無効なので表示できません。
GIF89a
<?php echo time();
ここで、アップロードされたファイルの拡張子を.phpに変更して、ウェブブラウザでアクセスすると、以下のように表示されます。この結果から、exif_imagetype()
をすり抜けてPHPスクリプトをアップロードできることがわかります。
もしSimple:Pressのようにアップロード時のファイル名を利用するような場合は大変危険な状態となります。
GIF89a 1601392715