环境准备
图片马想要执行需要的条件:
1.图片可以上传到目标服务器上。
2.图片可解析为PHP代码。
要满足第一个条件,可能的方式有文件上传、远程文件下载、SSRF等方式。
要满足第二个条件可能的方式有上传比如png格式木马但是可以改名为php,从而解析为php;或者可以修改.htacces文件来控制这个php解析类型,使其支持解析png为php代码执行;或者是只能上传png格式图片,但是可以文件包含这个png来执行php代码。
当然以上描述的情况这个跟目标环境有关系。为了方便测试,我制作了一个docker镜像。镜像中web服务支持文件上传,上传时会检查MIME类型是否为图片,但是不检查上传文件的扩展名。这表示可以上传一个图片格式的.php文件到服务器上。
1 | docker pull ordar/astrolock |
PNG结构
- PNG的基本构成为:
8字节头文件+4字节数据长度+4字节数据标识符+数据块数据+4字节CRC校验码
PNG图片由很多数据块组成,每个数据块包含了不同的信息。PNG定义了两种类型的数据块:
- 一种是称为关键数据块(critical chunk),这是标准的数据块,每个PNG文件必须包含。
- 另一种叫做辅助数据块(ancillary chunks),这是可选的数据块。
关键数据块
关键数据块包括:文件头数据块、调色板数据块、图像数据块和图像结束数据块
“89 50 4E 47 00 DA 1A 0A” png文件的标识符
文件头数据块IHDR
调色板数据块PLTE
图像数据块IDAT
图像结束数据块IEND
辅助数据块
其余 18 个块类型称为辅助块类型, 编码器可以生成哪些,解码器可以解释。
透明度信息:tRNS
色彩空间信息:cHRM、gAMA、iCCP、sBIT、sRGB、cICP
文本信息:iTXt,tEXt,zTXt
杂项信息:bKGD、hIST、pHYs、sPLT、eXIf
时间信息:tIME
动画信息:acTL,fcTL,fdAT
部分数据块如下:
数据块符号 | 数据块名称 | 多数据块 | 可选否 | 位置限制 |
---|---|---|---|---|
IHDR | 文件头数据块 | 否 | 否 | 第一块 |
cHRM | 基色和白色点数据块 | 否 | 是 | 在PLTE和IDAT之前 |
gAMA | 图像γ数据块 | 否 | 是 | 在PLTE和IDAT之前 |
sBIT | 样本有效位数据块 | 否 | 是 | 在PLTE和IDAT之前 |
PLTE | 调色板数据块 | 否 | 是 | 在IDAT之前 |
bKGD | 背景颜色数据块 | 否 | 是 | 在PLTE之后IDAT之前 |
hIST | 图像直方图数据块 | 否 | 是 | 在PLTE之后IDAT之前 |
tRNS | 图像透明数据块 | 否 | 是 | 在PLTE之后IDAT之前 |
pHYs | 物理像素尺寸块 | 否 | 是 | 在IDAT之前 |
IDAT | 图像数据块 | 是 | 否 | 与其他IDAT连续 |
tIME | 图像最后修改时间数据块 | 否 | 是 | 无限制 |
tEXt | 文本信息数据块 | 是 | 是 | 无限制 |
zTXt | 压缩文本数据块 | 是 | 是 | 无限制 |
IEND | 图像结束数据块 | 否 | 否 | 最后一个数据块 |
在PNG中注入PHP代码
first-part 简单插入
查看**/first-part**的源代码,这非常简单:网络表单需要标题、描述和有效的PNG文件。然后,应用程序将从上传的图像文件中获取数据,从原始文件名构建一个唯一的文件名,并将文件简单地存储在文件系统中(suits_thumbnails目录中,可公开访问)。
1 | # 创建“suit”对象并将其链接到Symfony表单 |
方法一 直接拼接
网上搜索图片马制作的方法,很容易就能找到的方法就是这种。以下方法的效果是等效的。
1.文本方式打开图片,然后末尾粘贴一句话木马;
2.cmd中 copy 1.png /b + 2.php 3.png
3.使用echo命令追加代码到png图片末尾
这种方法已经烂大街了,这里就不多说了,懂得都懂。在png图像结束块IEND后面紧跟着phpinfo。
方法二 预定义文本块注入
PNG 图像格式允许向文件添加注释以存储一些meta data数据。通过查阅PNG文档可以找到已经定义的文本块关键字。
当然这些关键字是可以修改的,通过下载工具https://exiftool.org/来设置这些预定义关键字。
当然它是可以成功解析的,可以看到文本块copyright后面紧接着就是phpinfo。
PHP-GD库的压缩图像
大多数时候,图像文件不会像/first-part所假设的那样按原样存储在服务器上。而是使用一些标准的PHP 库(如 PHP-GD)将图像调整大小、压缩或编码为特定的文件格式。
/second-part 路径为攻击者实现了一个稍微更现实、也更困难的场景。在存储用户上传的文件之前,应用程序将使用 PHP-GD 函数 imagepng 压缩所有上传到 Web 服务器的图像。
1 | # 创建“suit”对象并将其链接到Symfony表单 |
方法三 PLTE块注入
压缩 PNG 文件时,PHP-GD将删除辅助块以减小输出文件的大小。这就是为什么我们在第一部分中注入的PHP payload无法在压缩过程中幸存下来。
但是,如果我们可以将我们的payload注入到 PNG 文件的关键块中呢?当然,这些块在压缩图像时不会被破坏。执行这种注入的完美候选者是 PLTE 块,这是一个包含 PNG 图像的“调色板”的关键块,即颜色列表。根据 PNG 规范:
PLTE 块包含 1 到 256 个调色板条目,每个条目都是三字节的形式:
1 | Red: 1 byte (0 = black, 255 = red) |
使用PLTE块,我们可能有256*3字节可用于将我们的payload注入到这样一个关键块中,这应该绰绰有余。唯一的限制是有效载荷的长度必须能被3整除。
使用下面的脚本,可以轻松创建PLTE块注入payload的png图像。
1 |
|
执行脚本:
1 | php gen.php '<?php phpinfo(); ?>' png-plte-inject.php |
使用PLTE注入可以绕过PHP-GD库的压缩,可以成功上传并且成功解析。
PHP-GD库调整图像大小
Web 应用程序在处理图像时执行的另一个标准操作是调整它们的大小以标准化它们的格式。为此,应用程序可以例如使用PHP-GD库imagecopyresized函数或 imagecopyresampled函数。
/third-part路由实现了目标应用程序在存储输入PNG 文件之前对其进行压缩和调整大小的场景。
1 | # 创建“suit”对象并将其链接到Symfony表单 |
方法四 IDAT块注入
调整图像大小时,即使是 PLTE关键块的内容也会被破坏,我们的payload也会随之破坏。这些函数实际上只使用原始文件中的像素数据来创建一个全新的图像。关键数据块或辅助数据块中包含的数据都可能会被忽略。
将PHP载荷注入PNG文件的一种相当复杂但有效的方法是将其编码为PNG的IDAT块。
当将原始图像保存为PNG时,图像的每一行都按字节进行过滤,并且该行前缀有一个数字,该数字描述了所使用的过滤器类型(0x01到0x05),不同的行可以使用不同的过滤器。这背后的基本原理是提高压缩比。过滤完所有行后,它们都使用 DEFLATE 算法压缩以形成 IDAT 块。
要生成一个包含有效PHP代码的IDAT 块,应该找到原始像素的精确组合,这些像素一旦被 PNG线过滤器和DEFLATE算法处理,就会输出所需的有效载荷。
字符串不能包含任何重复的代码块,否则它们将被压缩。
虽然很难,但确实是可以实现的。
可以使用以下脚本来生成110x110的PNG图像,一旦调整为55x55,IDAT汇总将包含 PHP 代码:**=$_GET[0]($_POST[1]);?>**
1 |
|
此脚本生成恶意 PNG 图像:
1 | php generate_idat_png.php > png-idat-inject.php |
这时候将生成的图像通过/third-part路由上传,就可以触发webshell。
通过发送POST请求来触发php执行。
IMAGICK库调整图像大小
除了PHP-GD之外,最流行的图像处理库之一是Imagick,它是ImageMagick的PHP实现。/fourth-part路由说明了一个应用程序,它使用thumbnailImage功能调整用户上传的文件的大小。此函数用于产生较小的low-cost缩略图图像,适合在Web上显示。
1 | # 创建“suit”对象并将其链接到Symfony表单 |
方法五 自定义文本块注入
当Imagick调整图像大小时,它实际上会对文本块执行几个操作:
- 擦除标记为“Comment”的tEXt块。
- 覆盖以下tEXt块的值(或者如果它们不存在则定义它们):
date:create, date:modify, software, Thumb::Document::Pages, Thumb::Image::Height, Thumb::Image::Width, Thumb::Mimetype, Thumb::MTime, Thumb::Size, Thumb::URI
。 - 保留任何其他tEXt块的原始值(包括不存在预定义关键字的tEXt块)。
在方法二的PNG注释中,我们已经知道了预定义的tEXt块的预定义关键字有如下这些:
因为我们修改的是copyright块,所以是可以绕过thumbnailImage方法的处理的。
使用方法二的图像,可以成功执行。如下图,可以看到它并不是紧跟在tEXt块后面执行。
除了以上预定义关键字,我们还可以自定义关键字。通过以下简单的python代码可以实现。
1 | # -*- coding: utf-8 -*- |
用exiftool工具查看图片信息,可以发现Aaaaa文本信息成功添加进去了。
当然也可以解析执行。
总结
当服务器可以将图像文件解释为PHP时,例如弱扩展检查、解析漏洞、本地文件包含漏洞、错误配置PHP解析扩展等方式,可以使用这些技术来造成代码执行。
不处理图像 | PHP-GD 压缩图像 | PHP-GD调整图像大小 | Imagick调整图像大小 | |
---|---|---|---|---|
直接拼接 | 可以解析 | 不可以 | 不可以 | 不可以 |
预定义文本块注入 | 可以解析 | 不可以 | 不可以 | 部分预定义的关键字可以解析 |
PLTE块注入 | 可以解析 | 可以解析 | 不可以 | 不可以 |
IDAT块注入 | 可以解析 | 可以解析 | 可以解析 | 可以解析 |
自定义文本块注入 | 可以解析 | 不可以 | 不可以 | 可以解析 |
- 本文作者: ordar
- 本文链接: https://mrwq.github.io/在PNG中注入PHP代码/
- 版权声明: 本文作者: ordar123 转载请注明出处!