资讯专栏INFORMATION COLUMN

getimagesize 函数不是完全可靠的

Heier / 964人阅读

摘要:函数并不属于扩展的部分,标准安装的都可以使用这个函数。执行一下你会发现完全可以执行成功。所以,对于正常的图片文件,完全可以胜任,但是对于一些有心构造的文件结构却不行。

getimagesize 函数并不属于 GD 扩展的部分,标准安装的 PHP 都可以使用这个函数。可以先看看这个函数的文档描述:http://php.net/manual/zh/function.getimagesize.php

如果指定的文件如果不是有效的图像,会返回 false,返回数据中也有表示文档类型的字段。如果不用来获取文件的大小而是使用它来判断上传文件是否是图片文件,看起来似乎是个很不错的方案,当然这需要屏蔽掉可能产生的警告,比如代码这样写:


但是如果你仅仅是做了这样的验证,那么很不幸,你成功的在代码里种下了一个 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] = {"G", "I", "F"};
...
PHPAPI const char php_sig_png[8] = {(char) 0x89, (char) 0x50, (char) 0x4e, (char) 0x47,
                                    (char) 0x0d, (char) 0x0a, (char) 0x1a, (char) 0x0a};

可以看出来 image type 是根据文件流的前几个字节(文件头)来判断的。那么既然如此,我们可不可以构造一个特殊的 PHP 文件来绕过这个判断呢?不如来尝试一下。

找一个十六进制编辑器来写一个的 PHP 语句,比如:

这几个字符的十六进制编码(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

最后保存成 .php 后缀的文件(注意上面是文件的十六进制值),比如 test.php。执行一下 php test.php 你会发现完全可以执行成功。那么能用 getimagesize 读取它的文件信息吗?新建一个文件写入代码试一下:


执行结果:

Array
(
    [0] => 1885957734
    [1] => 1864902971
    [2] => 3
    [3] => width="1885957734" height="1864902971"
    [bits] => 32
    [mime] => image/png
)

成功读取出来,并且文件也被正常识别为 PNG 文件,虽然宽和高的值都大的有点离谱。

现在你应该明白为什么上文说这里留下了一个 webshell 的隐患的吧。如果这里只有这样的上传判断,而且上传之后的文件是可以访问的,就可以通过这个入口注入任意代码执行了。

那么为什么上面的文件可以 PHP 是可以正常执行的呢?用 token_get_all 函数来看一下这个文件:


如果显示正常的话你能看到输出数组的第一个元素的解析器代号是 312,通过 token_name 获取到的名称会是 T_INLINE_HTML,也就是说文件头部的信息被当成正常的内嵌的 HTML 代码被忽略掉了。

至于为什么会有一个大的离谱的宽和高,看一下 php_handle_png 函数的实现就能知道,这些信息也是通过读取特定的文件头的位来获取的。

所以,对于正常的图片文件,getimagesize 完全可以胜任,但是对于一些有心构造的文件结构却不行。

在处理用户上传的文件时,先简单粗暴的判断文件扩展名并对文件名做一下处理,保证在服务器上不是 php 文件都不能直接执行也是一种有效的方式。然后可以使用 getimagesize 做一些辅助处理。

个人博客地址:http://0x1.im

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/21197.html

相关文章

  • PHP图片操作

    摘要:保持原图长宽比,以图片的长度,或者宽度中,最小的一个为准,尽可能填充整个缩略图的画框,只显示原图中的某一部分。涉及到的相关图片操作函数如下创建黑色图片获取图片相关信息一个图片中的一部分到另外一个图片输出图片具体使用方法请查看相关手册。 1, PHP 中 图片的处理 要 依靠于扩展库, 可以选择gd2,或者imagemagick 第一步,首先要开启gd2的扩展库,在phpinfo() 中...

    yacheng 评论0 收藏0
  • PHP高级语法总结

    摘要:一执行系统外部命令输出并返回最后一行结果。相同点都可以获得命令执行的状态码用提供的专门函数提供共了个专门的执行外部命令的函数,,。第二个参数是可选的,用来得到命令执行后的状态码。 php高级语法总结。 一、执行系统外部命令 system() 输出并返回最后一行shell结果。 exec() 不输出结果,返回最后一行shell结果,所有结果可以保存到一个返回的数组里面。 pass...

    Hydrogen 评论0 收藏0
  • 分享一下利用phpqrcode二维码生成类库和imagecopymerge函数制拼合(镶嵌、合并、水

    摘要:二维码图片宽度二维码图片高度图片宽度图片高度重新组合图片并调整大小最后直接输出图像即可还是老样子,我们给图片来个名字这次用为随机数时间戳而后输出图像整个带的二维码生成就完成了。 利用 phpqrcode 二维码生成类库和 imagecopymerge 函数制作带二维码的图片 首先引用phpqrcode类库 下载phpqrcode类库 下载地址就不提供了,百度一搜一大把; 新建...

    Turbo 评论0 收藏0
  • 分享一下利用phpqrcode二维码生成类库和imagecopymerge函数制拼合(镶嵌、合并、水

    摘要:二维码图片宽度二维码图片高度图片宽度图片高度重新组合图片并调整大小最后直接输出图像即可还是老样子,我们给图片来个名字这次用为随机数时间戳而后输出图像整个带的二维码生成就完成了。 利用 phpqrcode 二维码生成类库和 imagecopymerge 函数制作带二维码的图片 首先引用phpqrcode类库 下载phpqrcode类库 下载地址就不提供了,百度一搜一大把; 新建...

    SexySix 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<