Loading... # 1、背景 在 DVWA 网站学习文件上传漏洞 Medium 级别时,后台代码对上传的图片文件通过 getimagesize 函数进行校验。要完成利用,得绕过该检测。 # 2、分析 [getimagesize](https://www.php.net/manual/zh/function.getimagesize.php) 函数,接收一个文件路径参数,返回一个包含有文件大小等数据的数组。在文件非合法图片时,返回 false。 getimagesize 可以识别获取多种图片格式文件的大小数据,然而,在校验文件是否为合法图片时存在缺陷(实际上也很难完美识别),可以轻松绕过。要知道 getimagesize 函数的内在逻辑是怎么识别图片合法的,我们需要从源码入手,还好 php 是开源的。 getimagesize 函数的源代码在 php 仓库的 [\ext\standard\image.c](https://github.com/php/php-src/blob/master/ext/standard/image.c) 中定义,在经过一些包装函数后,进入到核心函数 php_getimagesize_from_stream 中(省略了部分代码): ```c static void php_getimagesize_from_stream(php_stream *stream, char *input, zval *info, INTERNAL_FUNCTION_PARAMETERS) /* {{{ */ { int itype = 0; struct gfxinfo *result = NULL; if (!stream) { RETURN_FALSE; } itype = php_getimagetype(stream, input, NULL); switch( itype) { case IMAGE_FILETYPE_GIF: result = php_handle_gif(stream); break; case IMAGE_FILETYPE_JPEG: if (info) { result = php_handle_jpeg(stream, info); } else { result = php_handle_jpeg(stream, NULL); } break; case IMAGE_FILETYPE_PNG: result = php_handle_png(stream); break; …… default: case IMAGE_FILETYPE_UNKNOWN: break; } if (result) { char temp[MAX_LENGTH_OF_LONG * 2 + sizeof("width=\"\" height=\"\"")]; array_init(return_value); add_index_long(return_value, 0, result->width); add_index_long(return_value, 1, result->height); add_index_long(return_value, 2, itype); snprintf(temp, sizeof(temp), "width=\"%d\" height=\"%d\"", result->width, result->height); add_index_string(return_value, 3, temp); if (result->bits != 0) { add_assoc_long(return_value, "bits", result->bits); } if (result->channels != 0) { add_assoc_long(return_value, "channels", result->channels); } add_assoc_string(return_value, "mime", (char*)php_image_type_to_mime_type(itype)); efree(result); } else { RETURN_FALSE; } } ``` 上述逻辑首先通过 php_getimagetype 函数获取函数类型,函数类型是通过判断文件头特征来识别的。定义好的文件头特征如下: ```c /* file type markers */ PHPAPI const char php_sig_gif[3] = {'G', 'I', 'F'}; PHPAPI const char php_sig_psd[4] = {'8', 'B', 'P', 'S'}; PHPAPI const char php_sig_bmp[2] = {'B', 'M'}; PHPAPI const char php_sig_swf[3] = {'F', 'W', 'S'}; PHPAPI const char php_sig_swc[3] = {'C', 'W', 'S'}; PHPAPI const char php_sig_jpg[3] = {(char) 0xff, (char) 0xd8, (char) 0xff}; PHPAPI const char php_sig_png[8] = {(char) 0x89, (char) 0x50, (char) 0x4e, (char) 0x47, (char) 0x0d, (char) 0x0a, (char) 0x1a, (char) 0x0a}; PHPAPI const char php_sig_tif_ii[4] = {'I','I', (char)0x2A, (char)0x00}; PHPAPI const char php_sig_tif_mm[4] = {'M','M', (char)0x00, (char)0x2A}; PHPAPI const char php_sig_jpc[3] = {(char)0xff, (char)0x4f, (char)0xff}; PHPAPI const char php_sig_jp2[12] = {(char)0x00, (char)0x00, (char)0x00, (char)0x0c, (char)0x6a, (char)0x50, (char)0x20, (char)0x20, (char)0x0d, (char)0x0a, (char)0x87, (char)0x0a}; PHPAPI const char php_sig_iff[4] = {'F','O','R','M'}; PHPAPI const char php_sig_ico[4] = {(char)0x00, (char)0x00, (char)0x01, (char)0x00}; PHPAPI const char php_sig_riff[4] = {'R', 'I', 'F', 'F'}; PHPAPI const char php_sig_webp[4] = {'W', 'E', 'B', 'P'}; ``` 然后在一个 switch-case 中,针对不同的图片,有不同的 hanlder 函数用于采集进一步的数据。 比如 png 格式,其文件头特征为 8 个字节,然后在 php_handle_png 函数中,从文件第 16 个字节开始读取 width、height 等共计 9 字节数据。注意这里并不校验数据是否有效合理,只要有值就行: ```c /* {{{ php_handle_png * routine to handle PNG files */ static struct gfxinfo *php_handle_png (php_stream * stream) { struct gfxinfo *result = NULL; unsigned char dim[9]; /* Width: 4 bytes * Height: 4 bytes * Bit depth: 1 byte * Color type: 1 byte * Compression method: 1 byte * Filter method: 1 byte * Interlace method: 1 byte */ if (php_stream_seek(stream, 8, SEEK_CUR)) return NULL; if((php_stream_read(stream, (char*)dim, sizeof(dim))) < sizeof(dim)) return NULL; result = (struct gfxinfo *) ecalloc(1, sizeof(struct gfxinfo)); result->width = (((unsigned int)dim[0]) << 24) + (((unsigned int)dim[1]) << 16) + (((unsigned int)dim[2]) << 8) + ((unsigned int)dim[3]); result->height = (((unsigned int)dim[4]) << 24) + (((unsigned int)dim[5]) << 16) + (((unsigned int)dim[6]) << 8) + ((unsigned int)dim[7]); result->bits = (unsigned int)dim[8]; return result; } /* }}} */ ``` 因此如果我们构造一张虚假的 png 图片,只要保证文件头前 8 个字节为指定的特征值,然后文件总长度大于 25 字节,既可以绕过 getimagesize,即返回非 false 值。 同理我们可以分析得到 getimagesize 对所有不同格式图片的识别逻辑并相应绕过。 最后修改:2022 年 04 月 17 日 04 : 56 PM © 允许规范转载