我先援引一下其它博主的内容:
lylme_spage六零导航页,未授权webshell上传。php 的文件上传的漏洞,使用$_FILE['type']来判断文件类型是否在白名单内,该值是由浏览器提供的,不是可靠的文件类型验证方法。
概况与复现
lylme_spage,六零导航页,“六零导航页 (LyLme Spage) 致力于简洁高效无广告的上网导航和搜索入口,支持后台添加链接、自定义搜索引擎,沉淀最具价值链接,全站无商业推广,简约而不简单。”,项目地址:https://github.com/LyLme/lylme_spage。
影响版本v1.9.5
实际搭建六零导航页后,关闭收录功能,发现漏洞并没有关闭。
主要问题在于 /include/file.php
。
漏洞概述
六零导航页是一款开源的导航页面,设计简洁、无广告,具备可自定义的搜索引擎和隐私链接功能。然而,由于其 /include/file.php
接口存在任意文件上传漏洞,未经授权的攻击者可以通过这个接口上传任意文件。利用此漏洞,攻击者可能将恶意代码上传至服务器,并取得执行权限,进而控制系统。
漏洞利用的 Proof of Concept (PoC)
可以通过以下的 HTTP POST 请求来利用此漏洞:
POST /include/file.php HTTP/1.1
Host: x.x.x.x
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0
Content-Type: multipart/form-data; boundary=---------------------------575673989461736
Content-Length: 234
-----------------------------575673989461736
Content-Disposition: form-data; name="file"; filename="test.php"
Content-Type: image/png
<?php echo "HelloWorldtest"; unlink(__FILE__); ?>
-----------------------------575673989461736--
解释:
- 这个请求向
/include/file.php
发送了带有.php
扩展名的文件test.php
,文件内容包含简单的 PHP 代码:<?php echo "HelloWorldtest"; unlink(__FILE__); ?>
。 - 此代码在上传后执行时会显示
HelloWorldtest
,然后删除自身 (unlink(__FILE__)
)。 - 若成功上传并执行,则证明此漏洞存在。
漏洞检测脚本
可以使用 Python 检测脚本来验证系统是否存在该漏洞: GitHub,通过下列指令批量检测:url.txt
包含需要测试的目标 URL 清单。
python 60NavigationPage_CVE-2024-34982_ArbitraryFileUploads.py -f url.txt
或
python 60NavigationPage_CVE-2024-34982_ArbitraryFileUploads.py -u http://xxxx.xxx.top
修复方法
网上都说官方已针对此漏洞发布补丁,然而并没有找到。
所以让我们手动来修复吧~
将上传目录设置为不可执行
首先,一个很直接的方法就是直接禁止文件上传目录的php执行,这样就算能上传,也不能执行恶意脚本~
在Nginx下对站点加上如下配置:
location /files/upload/ {
disable_symlinks on;
autoindex off;
location ~ \.php$ {
return 403;
}
}
如果用的是Apache服务器,则在上传目录中添加 .htaccess 文件(针对 Apache 服务器):
php_flag engine off
RemoveHandler .php .phtml .php3
修复漏洞文件
file.php
的主要问题包括:
- 文件类型验证不足:仅依赖文件扩展名和用户提供的 MIME 类型,容易被绕过。
- 缺乏对上传文件内容的验证:未确认文件实际内容是否为有效图片。
那我们据此开始修复漏洞
1. 验证上传的 URL
在 download_img
函数中,添加对传入 URL 的合法性验证,防止非法 URL 导致的安全问题。
修改前:
function download_img($url)
{
// 原有代码
}
修改后:
function download_img($url)
{
// 验证 URL 是否有效
if (!filter_var($url, FILTER_VALIDATE_URL)) {
exit(json_encode(['code' => '-2', 'msg' => '无效的URL']));
}
// 继续原有代码
}
2. 严格验证文件扩展名和 MIME 类型
在 validate_file_type
函数中,同时验证文件的扩展名和实际的 MIME 类型,防止通过修改扩展名或 MIME 类型绕过验证。
修改前:
function validate_file_type($type)
{
switch ($type) {
case 'jpeg':
case 'jpg':
$type = 'image/jpeg';
break;
case 'png':
$type = 'image/png';
break;
case 'gif':
$type = 'image/gif';
break;
case 'ico':
$type = 'image/x-icon';
break;
}
$allowed_types = array("image/jpeg", "image/png", "image/gif", "image/x-icon");
return in_array($type, $allowed_types);
}
修改后:
function validate_file_type($extension, $mime_type = null)
{
$allowed_extensions = ['jpg', 'jpeg', 'png', 'gif', 'ico'];
$allowed_mime_types = [
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'png' => 'image/png',
'gif' => 'image/gif',
'ico' => 'image/x-icon',
];
// 验证扩展名
if (!in_array(strtolower($extension), $allowed_extensions)) {
return false;
}
// 如果提供了 MIME 类型,进一步验证
if ($mime_type) {
return isset($allowed_mime_types[strtolower($extension)]) && $allowed_mime_types[strtolower($extension)] === $mime_type;
}
return true;
}
3. 使用 finfo
获取真实的 MIME 类型
在 upload_img
函数中,使用 finfo_file
获取上传文件的真实 MIME 类型,而不是依赖用户提供的 $_FILES["type"]
。
修改前:
$type = $upfile["type"];
修改后:
// 使用 finfo 获取真实的 MIME 类型
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime_type = finfo_file($finfo, $upfile["tmp_name"]);
finfo_close($finfo);
并在调用 validate_file_type
时传入 $mime_type
:
if (!validate_file_type($img_ext, $mime_type)) {
exit(json_encode(['code' => '-4', 'msg' => '上传的图片类型不支持']));
}
4. 验证文件内容是否为有效图片
在 upload_img
和 download_img
函数中,使用 getimagesize
确认文件实际内容是有效的图片。
在 upload_img
函数中添加:
// 验证文件内容是否为有效图片
$image_info = getimagesize($upfile["tmp_name"]);
if ($image_info === false) {
exit(json_encode(['code' => '-4', 'msg' => '上传的文件不是有效图片']));
}
在 download_img
函数中添加:
// 验证下载的文件是否为有效图片
$tmp_file = tempnam(sys_get_temp_dir(), 'img_');
file_put_contents($tmp_file, $data);
$image_info = getimagesize($tmp_file);
if ($image_info === false) {
unlink($tmp_file);
exit(json_encode(['code' => '-4', 'msg' => '抓取的文件不是有效图片']));
}
unlink($tmp_file);
5. 限制文件大小
确保上传和下载的文件大小不超过设定的最大值(如5MB)。
在 download_img
函数中:
if ($size > $maxsize) {
exit(json_encode([
'code' => '-1',
'msg' => '抓取的图片超过' . ($maxsize / pow(1024, 2)) . 'M,当前为:' . round($size / pow(1024, 2), 2) . 'M'
]));
}
在 upload_img
函数中:
if ($upfile["size"] > $maxsize) {
exit(json_encode(['code' => '-1', 'msg' => '图片不能超过' . ($maxsize / pow(1024, 2)) . 'M']));
}
完整修改
<?php
header('Content-Type: application/json');
require_once("common.php");
define('SAVE_PATH', 'files/'); // 保存路径
// 1. 下载图片函数
function download_img($url)
{
if (!filter_var($url, FILTER_VALIDATE_URL)) {
exit(json_encode(['code' => '-2', 'msg' => '无效的URL']));
}
$IMG_NAME = uniqid("img_"); // 文件名
$maxsize = pow(1024, 2) * 5; // 文件大小5M
$size = remote_filesize($url); // 文件大小
if ($size > $maxsize) {
exit(json_encode([
'code' => '-1',
'msg' => '抓取的图片超过' . ($maxsize / pow(1024, 2)) . 'M,当前为:' . round($size / pow(1024, 2), 2) . 'M'
]));
}
$img_ext = strtolower(pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION));
if (!validate_file_type($img_ext)) {
exit(json_encode(['code' => '-4', 'msg' => '抓取的图片类型不支持']));
}
$img_name = $IMG_NAME . '.' . $img_ext;
$dir = ROOT . SAVE_PATH . 'download/';
$save_to = $dir . $img_name;
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
// 使用 curl 下载图片
// ... (保持原有 curl 代码不变)
// 验证下载的文件是否为有效图片
$tmp_file = tempnam(sys_get_temp_dir(), 'img_');
file_put_contents($tmp_file, $data);
$image_info = getimagesize($tmp_file);
if ($image_info === false) {
unlink($tmp_file);
exit(json_encode(['code' => '-4', 'msg' => '抓取的文件不是有效图片']));
}
unlink($tmp_file);
// 保存文件
if (file_put_contents($save_to, $data) === false) {
exit(json_encode(['code' => '-1', 'msg' => '保存图片失败']));
}
$fileurl = '/' . SAVE_PATH . 'download/' . $img_name;
echo json_encode([
'code' => '200',
'msg' => '抓取图片成功',
'url' => $fileurl,
'size' => round($fileSize / 1024, 2) . 'KB'
]);
return $save_to;
}
// 2. 上传图片函数
function upload_img($upfile)
{
if ($upfile['error'] !== UPLOAD_ERR_OK) {
exit(json_encode(['code' => '-1', 'msg' => '文件上传错误']));
}
$IMG_NAME = uniqid("img_"); // 文件名
$maxsize = pow(1024, 2) * 5; // 文件大小5M
$dir = ROOT . SAVE_PATH . 'upload/';
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
// 获取真实的 MIME 类型
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime_type = finfo_file($finfo, $upfile["tmp_name"]);
finfo_close($finfo);
// 获取文件扩展名
$img_ext = strtolower(pathinfo($upfile["name"], PATHINFO_EXTENSION));
// 验证文件类型
if (!validate_file_type($img_ext, $mime_type)) {
exit(json_encode(['code' => '-4', 'msg' => '上传的图片类型不支持']));
}
// 验证文件大小
if ($upfile["size"] > $maxsize) {
exit(json_encode(['code' => '-1', 'msg' => '图片不能超过' . ($maxsize / pow(1024, 2)) . 'M']));
}
// 验证文件内容是否为有效图片
$image_info = getimagesize($upfile["tmp_name"]);
if ($image_info === false) {
exit(json_encode(['code' => '-4', 'msg' => '上传的文件不是有效图片']));
}
$img_name = $IMG_NAME . '.' . $img_ext;
$save_to = $dir . $img_name;
$url = '/' . SAVE_PATH . 'upload/' . $img_name;
if (!move_uploaded_file($upfile["tmp_name"], $save_to)) {
exit(json_encode(['code' => '-1', 'msg' => '上传失败']));
}
echo json_encode(['code' => '200', 'msg' => '上传成功', 'url' => $url]);
return $save_to;
}
// 3. 文件验证函数
function validate_file_type($extension, $mime_type = null)
{
$allowed_extensions = ['jpg', 'jpeg', 'png', 'gif', 'ico'];
$allowed_mime_types = [
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'png' => 'image/png',
'gif' => 'image/gif',
'ico' => 'image/x-icon',
];
if (!in_array($extension, $allowed_extensions)) {
return false;
}
if ($mime_type) {
return isset($allowed_mime_types[$extension]) && $allowed_mime_types[$extension] === $mime_type;
}
return true;
}
// 4. 主逻辑处理
if (empty($_POST["url"]) && !empty($_FILES["file"])) {
$filename = upload_img($_FILES["file"]);
if (isset($islogin) && $islogin == 1 && isset($_GET["crop"]) && $_GET["crop"] == "no") {
// 不压缩图片
exit();
}
} elseif (!empty($_POST["url"])) {
$filename = download_img($_POST["url"]);
} else {
exit(json_encode(['code' => '0', 'msg' => 'error']));
}
imagecropper($filename, 480, 480);
?>
效果
只进行上传目录执行限制时,上传文件后访问其文件:
修复文件漏洞后,尝试用60NavigationPage_CVE-2024-34982_ArbitraryFileUploads.py对漏洞文件发起攻击。
0 条评论