PHP代码审计-某0Day分析
字数 1438 2025-08-23 18:31:24
PHP代码审计:某0Day漏洞分析与利用教学文档
漏洞概述
本教学文档详细分析了一个PHP系统中的0Day漏洞,该漏洞存在于后台模块市场的文件上传功能中,通过精心构造的恶意模块包可以实现目录穿越和任意文件上传,最终导致Getshell。
漏洞环境
- 漏洞位置:后台模块市场的上传功能
- 受影响文件:
app/admin/controller/Module.php - 关键类:
app/admin/library/module中的相关方法 - 漏洞类型:文件上传漏洞(目录穿越)
漏洞定位与分析
1. 漏洞入口点
漏洞位于后台的模块市场上传功能,具体在Module.php控制器的upload方法中:
public function upload(): void {
AdminLog::setTitle(__('Upload install module'));
$file = $this->request->get("file/s", '');
$token = $this->request->get("token/s", '');
if (!$file) $this->error(__('Parameter error'));
if (!$token) $this->error(__('Please login to the official website account first'));
$info = [];
try {
$info = Manage::instance()->upload($token, $file);
} catch (Exception $e) {
$this->error(__($e->getMessage()), $e->getData(), $e->getCode());
} catch (Throwable $e) {
$this->error(__($e->getMessage()));
}
$this->success('', ['info' => $info]);
}
2. 关键调用链
上传流程的调用链如下:
Module.php中的upload()方法- 调用
Manage::instance()->upload($token, $file) - 进入
app/admin/library/module中的upload方法
3. 核心漏洞代码分析
在app/admin/library/module中的upload方法存在以下关键处理逻辑:
public function upload(string $token, string $file): array {
$file = Filesystem::fsFit(root_path() . 'public' . $file);
if (!is_file($file)) {
throw new Exception('Zip file not found');
}
// 文件移动
$copyTo = $this->installDir . 'uploadTemp' . date('YmdHis') . '.zip';
copy($file, $copyTo);
// 解压
$copyToDir = Filesystem::unzip($copyTo);
$copyToDir .= DIRECTORY_SEPARATOR;
// 删除zip
@unlink($file);
@unlink($copyTo);
// 读取ini
$info = Server::getIni($copyToDir);
if (empty($info['uid'])) {
Filesystem::delDir($copyToDir);
throw new Exception('Basic configuration of the Module is incomplete');
}
// ... 省略部分检查代码 ...
// 获取ini中的uid
$this->uid = $info['uid'];
// 把原本解压的文件目录改名为modules/ + info.ini中的uid/
$this->modulesDir = $this->installDir . $info['uid'] . DIRECTORY_SEPARATOR;
// ... 省略部分代码 ...
// 放置新模块
rename($copyToDir, $this->modulesDir);
// 检查新包是否完整
$this->checkPackage();
// 设置为待安装状态
$this->setInfo($newInfo);
return $info;
}
4. 漏洞触发点
漏洞的核心在于:
- 从
info.ini文件中读取uid字段 - 直接将
uid值与$this->installDir拼接形成目标目录 - 使用
rename()函数将解压的临时目录移动到拼接后的目录
由于没有对uid值进行严格的过滤和校验,攻击者可以通过构造恶意的uid值实现目录穿越。
漏洞利用步骤
1. 构造恶意模块包
- 创建一个包含恶意代码的文件(如
shell.php) - 创建
info.ini文件,内容如下:
uid=/../public/test
title=恶意模块
intro=这是一个测试模块
author=attacker
version=1.0.0
state=1
- 将这两个文件打包为ZIP压缩包
2. 利用条件
- 需要拥有后台管理员权限(因为上传需要有效的token)
- 需要注册官方账户获取有效token
3. 利用过程
- 登录后台系统
- 进入模块市场上传功能
- 上传构造的恶意ZIP包
- 系统处理流程:
- 解压ZIP包到临时目录
- 读取
info.ini文件获取uid值 - 拼接目录路径:
$this->installDir . '/../public/test' - 使用
rename()将临时目录移动到public/test目录
- 访问上传的恶意文件:
http://target.com/public/test/shell.php
漏洞修复建议
- 严格过滤
info.ini中的uid值,禁止包含路径遍历字符 - 对最终拼接的路径进行规范化处理,使用
realpath()等函数解析绝对路径 - 限制
uid只能包含字母、数字和下划线等安全字符 - 在移动目录前检查目标路径是否在允许的范围内
教学总结
- 危险操作点:模块导入/上传功能通常是高危操作,需要特别关注
- 路径拼接风险:任何用户输入与系统路径拼接的操作都可能存在目录遍历风险
- 文件移动操作:
rename()等文件移动函数使用不当可能导致安全风险 - 防御思路:
- 严格校验所有用户输入
- 限制文件/目录操作的范围
- 对路径进行规范化处理和安全检查
扩展思考
- 如何在不修改代码的情况下通过服务器配置防御此类漏洞?
- 除了目录穿越,类似的路径拼接操作还可能引发哪些安全问题?
- 如何设计安全的模块上传和安装机制?
通过本案例的分析,安全开发人员应更加重视文件操作中的路径处理问题,特别是在涉及用户输入的场景下,必须实施严格的安全检查和过滤机制。