LoginSignup
1
3

More than 3 years have passed since last update.

exif_imagetype()だけで画像かどうかを判別してはいけない

Last updated at Posted at 2020-09-29

はじまり

マイナーな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にアクセスしても、画像としては無効なので表示できません。

dummy.gif
GIF89a
<?php echo time();

ここで、アップロードされたファイルの拡張子を.phpに変更して、ウェブブラウザでアクセスすると、以下のように表示されます。この結果から、exif_imagetype()をすり抜けてPHPスクリプトをアップロードできることがわかります。
もしSimple:Pressのようにアップロード時のファイル名を利用するような場合は大変危険な状態となります。

GIF89a 1601392715
1
3
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3