二次渲染原理:
在我们上传文件后,网站会对图片进行二次处理(格式、尺寸要求等),服务器会把里面的内容进行替换更新,处理完成后,根据我们原有的图片生成一个新的图片并放到网站对应的标签进行显示。
绕过:
1、配合文件包含漏洞:
将一句话木马插入到网站二次处理后的图片中,也就是把一句话插入图片在二次渲染后会保留的那部分数据里,确保不会在二次处理时删除掉。这样二次渲染后的图片中就存在了一句话,在配合文件包含漏洞获取webshell。
2、可以配合条件竞争:
这里二次渲染的逻辑存在漏洞,先将文件上传,之后再判断,符合就保存,不符合删除,可利用条件竞争来进行爆破上传
点我进入条件竞争漏洞
3 如何判断图片是否进行了二次处理?
对比要上传图片与上传后的图片大小,编辑器打开图片查看上传后保留了拿些数据
4 工具:
burpsuite
或
010 Editor16进制编辑器
5.利用
利用过程:
GIF绕过:
gif图片的特点是无损, 修改图片后,图片质量几乎没有损失
我们可以对比上传前后图片的内容字节,在渲染后不会被修改的部分插入木马。
可以使用010编辑器(更直观一点)
左侧是上传前,右侧是上传后,比较发现这段数据一模一样
修改这段数据,再上传对比,数据没有丢失,木马插入成功
连接成功
条件竞争
基本概念:竞争条件发生在多个线程同时访问同一个共享代码、变量、文件等没有进行锁操作或者同步操作的场景中。
开发者在进行代码开发时常常倾向于认为代码会以线性的方式执行,而且他们忽视了并行服务器会并发执行多个线程,这就会导致意想不到的结果。
漏洞逻辑:首先将文件上传到服务器,然后检测文件后缀名,如果不符合条件再删掉。
攻击思路:首先上传一个php文件,当然这个文件会被立马删掉,所以我们使用多线程并发的访问上传的文件,总会有一次在上传文件到删除文件这个时间段内访问到上传的php文件,一旦我们成功访问到了上传的文件,那么它就会向服务器写一个shell。
以upload-labs-master的第17关为例:
随意上传一张图片:
成功后查看图片,可以看到上传后的路径如下:
漏洞利用
我们先在本地创建一个test.php文件:
//访问该页面,就会在本地写入一个info.php文件。
直接上传的话,肯定会被删除:
使用burp抓取上传报文:
再抓取一个访问报文:
将以上两个报文都发送至intruder模块。
在报文url中加上?a=1,并将a设置为变量,用于不断发送:
都选择类型为Numbers的字典,数量为一万:
将线程都调到20:
尝试访问生成的info.php:
数组拼接后缀名
通过审计发现,先检查文件类型,后检查是否上传了文件名没有则为文件的名字,判断是否为数组,若不是则以点分割返回一个数组,取数组最后一位数为后缀,文件保存为reset输出数组第一个数,和最后一位数保存
$is_upload = false;
$msg = null;
if(!empty($_FILES['upload_file'])){
//检查MIME
$allow_type = array('image/jpeg','image/png','image/gif');
if(!in_array($_FILES['upload_file']['type'],$allow_type)){
$msg = "禁止上传该类型文件!";
}else{
//检查文件名
$file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];
if (!is_array($file)) {
$file = explode('.', strtolower($file));
}
$ext = end($file);
$allow_suffix = array('jpg','png','gif');
if (!in_array($ext, $allow_suffix)) {
$msg = "禁止上传该后缀文件!";
}else{
$file_name = reset($file) . '.' . $file[count($file) - 1];
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' .$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$msg = "文件上传成功!";
$is_upload = true;
} else {
$msg = "文件上传失败!";
}
}
}
}else{
$msg = "请选择要上传的文件!";
}
explode(a,b)函数以a为分割,把b转为数组。
reset()函数把数组内部指针移动到数组第一个元素,并返回值。
end() 把数组内部指针移动到数组最后一个元素,并返回值。
count()函数数组元素的数量。
通过
f
i
l
e
n
a
m
e
=
r
e
s
e
t
(
file_name = reset(
filename=reset(file) . ‘.’ .
f
i
l
e
[
c
o
u
n
t
(
file[count(
file[count(file) - 1];可以知道最终的文件名是由数组的第一个和最后一个元素拼接而成。如果是正常思维来讲,无论如何都是没有办法绕过的,但是有个地方给了一个提示。
这里有个判断,如果不是数组,就自己拆成数组,也就是说,我们是可以自己传数组进入的。如果第一个元素是x.php/,最后一个元素是end(),讲道理
f
i
l
e
[
c
o
u
n
t
(
file[count(
file[count(file)-1]也就是最后一个,但是为什么不直接使用end()呢,也就是说有特殊的情况下,这两个东西是不等的,其实就是这样的一个数组$array=([0] -> ‘x.php/’ [2]->‘jpg’)。
KeyNode
如果
f
i
l
e
不是数组的话,就会将
file 不是数组的话,就会将
file不是数组的话,就会将file 以" . " 分割,打散成数组;
正常逻辑抓包改包,即使绕过文件类型检测,文件后缀名白名单检测绕不过去;
利用上图的代码可以POST数组绕过,如下:
这样构造的话,
f
i
l
e
本身就是数组了,直接执行
e
n
d
函数,
e
n
d
(
file 本身就是数组了,直接执行end函数,end(
file本身就是数组了,直接执行end函数,end(file) = jpg, 接下来通过文件后缀名白名单检测,继续执行;
f
i
l
e
n
a
m
e
=
r
e
s
e
t
(
file_name = reset(
filename=reset(file) . ‘.’ .
f
i
l
e
[
c
o
u
n
t
(
file[count(
file[count(file) - 1];
这样count(
f
i
l
e
)
−
1
=
1
,
file) -1 =1,
file)−1=1,file[1] 就为空,最终拼接后 $file_name = up.php/.
继续分析
move_uploaded_file
这个函数在执行的时候会忽略掉末尾的/. 最终文件已php的后缀名成功上传。
实战中盲试