getimagesize 関数は完全に信頼できるわけではありません

黄舟
リリース: 2016-12-28 13:04:37
オリジナル
1773 人が閲覧しました

getimagesize 関数は GD 拡張機能の一部ではありません。この関数は標準でインストールされている PHP で使用できます。まず、この関数のドキュメントの説明を見てください: http://php.net/manual/zh/function.getimagesize.php

指定されたファイルが有効な画像でない場合は、false が返され、ドキュメント タイプは、返されるデータ フィールドにも示されます。ファイルのサイズを取得するためにこれを使用するのではなく、アップロードされたファイルが画像ファイルであるかどうかを判断するために使用する場合は、たとえば、次のような警告をブロックする必要があります。コードは次のようになります:

<?php
$filesize = @getimagesize(&#39;/path/to/image.png&#39;);
if ($filesize) {
    do_upload();
}
# 另外需要注意的是,你不可以像下面这样写:
# if ($filesize[2] == 0)
# 因为 $filesize[2] 可能是 1 到 16 之间的整数,但却绝对不对是0。
ログイン後にコピー

しかし、この種の検証を行うだけで、残念なことに、コードに WebShell の脆弱性を埋め込むことに成功したことになります。

この問題を分析するために、まずこの関数のプロトタイプを見てみましょう:

static void php_getimagesize_from_stream(php_stream *stream, zval **info, INTERNAL_FUNCTION_PARAMETERS)
{
    ...
    itype = php_getimagetype(stream, NULL TSRMLS_CC);
    switch( itype) {
        ...
    }
    ...
}

static void php_getimagesize_from_any(INTERNAL_FUNCTION_PARAMETERS, int mode) {
    ...
    php_getimagesize_from_stream(stream, info, INTERNAL_FUNCTION_PARAM_PASSTHRU);
    php_stream_close(stream);
}

PHP_FUNCTION(getimagesize)
{
    php_getimagesize_from_any(INTERNAL_FUNCTION_PARAM_PASSTHRU, FROM_PATH);
}
ログイン後にコピー

スペースが限られているため、いくつかの詳細が隠されています:

最後の処理関数は php_getimagesize_from_stream です。ファイルタイプを決定する関数は php_getimagetype です

php_getimagetype:

PHPAPI int php_getimagetype(php_stream * stream, char *filetype TSRMLS_DC)
{
    ...
    if (!memcmp(filetype, php_sig_gif, 3)) {
        return IMAGE_FILETYPE_GIF;
    } else if (!memcmp(filetype, php_sig_jpg, 3)) {
        return IMAGE_FILETYPE_JPEG;
    } else if (!memcmp(filetype, php_sig_png, 3)) {
        ...
    }
}
ログイン後にコピー

の実装を見てみましょう。一部の詳細は削除されていますが、php_sig_gif php_sig_png などがファイルヘッダーで定義されています:

PHPAPI const char php_sig_gif[3] = {&#39;G&#39;, &#39;I&#39;, &#39;F&#39;};
...
PHPAPI const char php_sig_png[8] = {(char) 0x89, (char) 0x50, (char) 0x4e, (char) 0x47,
                                    (char) 0x0d, (char) 0x0a, (char) 0x1a, (char) 0x0a};
ログイン後にコピー

画像の種類はファイル ストリーム (ファイル ヘッダー) の最初の数バイトによって判断されます。この場合、この判断を回避するために特別な PHP ファイルを作成できるでしょうか?ぜひ試してみてはいかがでしょうか。

次のような PHP ステートメントを作成する 16 進エディタを見つけます:

<?php phpinfo(); ?>
ログイン後にコピー

これらの文字の 16 進エンコード (UTF-8) は次のとおりです:

3C3F 7068 7020 7068 7069 6E66 6F28 293B 203F 3E
ログイン後にコピー

それを構築して PNG に変換しましょう ファイルのヘッダー バイトが追加されます

8950 4E47 0D0A 1A0A 3C3F 7068 7020 7068 7069 6E66 6F28 293B 203F 3E
ログイン後にコピー

最後に、test.php などの .php 接尾辞が付いたファイル (上記はファイルの 16 進値であることに注意してください) として保存されます。 php test.phpを実行すると、正常に実行できることがわかります。それでは、getimagesize を使用してそのファイル情報を読み取ることができますか?新しいファイルを作成し、試してみるコードを書いてください:

<?php
print_r(getimagesize(&#39;test.php&#39;));
ログイン後にコピー

実行結果:

Array
(
    [0] => 1885957734
    [1] => 1864902971
    [2] => 3
    [3] => width="1885957734" height="1864902971"
    [bits] => 32
    [mime] => image/png
)
ログイン後にコピー

は正常に読み込まれ、ファイルは正常にPNGファイルとして認識されましたが、幅と高さの値が少しばかげて大きくなります。

これで、WebShell には隠れた危険性があると上記で言われた理由が理解できるはずです。ここにこのようなアップロード判定のみがあり、アップロードされたファイルにアクセス可能であれば、この入り口から任意のコードが注入され、実行される可能性があります。

では、なぜ上記のファイルはPHPで正常に実行できるのでしょうか? token_get_all 関数を使用して、このファイルを確認してください:

<?phpprint_r(token_get_all(file_get_contents(&#39;test.php&#39;)));
ログイン後にコピー

表示が正常であれば、出力配列の最初の要素のパーサー コードが 312 であり、token_name によって取得される名前が T_INLINE_HTML であることがわかります。ファイルヘッダーを意味します。この情報は通常の埋め込み HTML コードとして扱われ、無視されます。

幅と高さが法外に大きい理由については、php_handle_png 関数の実装を見ることでわかります。この情報は、特定のファイルのヘッダー ビットを読み取ることによっても取得されます。

つまり、通常の画像ファイルの場合、getimagesize は完全に機能しますが、意図的に構築された一部のファイル構造ではそれができません。

ユーザーがアップロードしたファイルを処理する場合、phpファイルでない場合は、ファイルの拡張子を簡単かつ大まかに判断し、サーバー上で直接実行できないようにファイル名を加工するのも有効な方法です。その後、getimagesize を使用して補助処理を行うことができます。

上記は getimagesize 関数が完全に信頼できるものではないという内容です。さらに関連する内容については、PHP 中国語 Web サイト (www.php.cn) にご注意ください。


関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のおすすめ
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート