Kimdir01/phpuploader-multi-vuln-cve
GitHub: Kimdir01/phpuploader-multi-vuln-cve
phpUploader v2.0.1 的安全概念验证项目,披露了其数据库暴露导致完整身份验证绕过、纵深防御失效及潜在 RCE 等多个严重漏洞。
Stars: 0 | Forks: 0
# GitHub 安全公告:phpUploader v2.0.1 — 缺失访问控制、RCE 纵深防御失效、数据泄露
## 公告信息
| 字段 | 值 |
|-------|-------|
| **严重程度** | 严重 / 高 |
| **CWE** | CWE-200 (敏感信息暴露) / CWE-552 (不受保护的存储) / CWE-693 (纵深防御失效) / CWE-208 (时序攻击) |
| **CVSS v3.1** | 9.1 (数据库 + 文件暴露) / 8.1 (如果扩展名被扩充则为 RCE) / 2.6 (时序攻击) |
| **CVSS 向量** | AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N |
## 摘要
phpUploader v2.0.1(108 个 star,PHP 文件上传应用程序)存在严重的架构安全缺陷:(1) SQLite 数据库存储在 webroot 中且没有任何访问限制——下载 `db/uploader.db` 即可提供访问所有上传文件所需的全部文件哈希值,完全绕过了所有的身份验证、token 和基于密钥的安全机制;(2) 纵深防御失效——阻止 PHP 执行的唯一屏障是基于配置的扩展名白名单;(3) MIME 类型验证代码存在但从未被调用(死代码);(4) 对时间敏感的 master key 比较。
## 漏洞详情
### 受影响的包/仓库
- **仓库:** phpUploader
- **受影响版本:** v2.0.1 及以下版本
- **修复版本:** N/A (0-day)
**Commit Hash (已审计):** `592838097568fda86425e9d7dc673bcaa07888f0`
## CVE-REQUEST-001: 可通过 Web 访问的 SQLite 数据库 — 完全绕过安全机制 (CVSS 9.1)
**文件/目录:** `db/`, `data/`, `logs/`
phpUploader 将其**整个 SQLite 数据库** (`db/uploader.db`) 存储在 webroot 中,且**没有任何访问限制**。整个应用程序中不存在任何 `.htaccess` 文件。该数据库包含攻击者完全绕过所有安全控制并下载每个上传文件所需的一切:
**暴露的数据库内容:**
| 表名 | 字段 | 影响 |
|-------|--------|--------|
| `uploaded` | `id`, `filename` (SHA-256), `original_name`, `comment`, `size`, `upload_date`, `download_key_hash`, `delete_key_hash` | 完整的文件注册表 |
| `downloads` | `file_id`, `ip_address`, `token`, `download_date` | 下载审计记录 |
| `tokens` | `file_id`, `ip_address`, `token`, `created_at` | 一次性 token 注册表 |
**根本原因:** 应用程序将其 SQLite 数据库存储在 `db/uploader.db` —— 一个可通过 HTTP 直接访问的位置。没有任何 `.htaccess`、`index.php` 或 Web 服务器配置限制访问。`index.html` 中的文件列表阻止了目录列出,但并**不能**阻止通过已知路径直接访问文件。
### 攻击链:
**步骤 1 — 下载数据库:**
```
curl http://target/db/uploader.db -o uploader.db
```
**步骤 2 — 提取文件哈希值:**
```
sqlite3 uploader.db "SELECT filename, original_name FROM uploaded"
```
**步骤 3 — 通过哈希值直接下载任何文件:**
```
curl http://target/data/.jpg -o stolen_file.jpg
```
**结果:** 攻击者绕过了 phpUploader 的**所有**安全机制:
- ❌ 基于 token 的下载身份验证 — 被绕过(直接文件访问)
- ❌ 下载密钥 — 被绕过(直接访问时从不检查)
- ❌ 完整性验证 — 被绕过
- ❌ 一次性下载 token — 被绕过
- ❌ IP 地址验证 — 被绕过
- ❌ 下载审计记录 — 攻击者在应用程序外访问了文件
### 为什么是 CVSS 9.1 (严重):
- **机密性:高** — 完全暴露所有上传的文件。攻击者可以读取曾经上传到该服务的每一个文件。
- **完整性:高** — 数据库包含 `delete_key_hash` 值,允许删除文件。虽然密钥使用 Argon2ID 进行了哈希处理(无法直接伪造),但完整文件注册表和删除审计记录的暴露使得以下行为成为可能:
- 通过 `delete.php` endpoint 结合 IDOR 删除任意文件(IP 不匹配仅仅会触发 WARNING,而不是拦截 —— 见发现 6)
- 或者如果与任何路径遍历漏洞结合,可通过 `unlink()` 直接删除
- **可用性:无** — 没有直接的 DoS,尽管可能通过暴露的删除 endpoint 进行批量删除。
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N = **9.1 严重**
### 与正确安全设计的对比:
安全的实现应该:
1. 将数据库存储在 webroot 之外(例如,`/var/lib/phpuploader/`)
2. 或者在 `.htaccess` 中使用 `Deny from all` 保护 `db/`
3. 或者使用无法通过 HTTP 访问的适当数据库服务器(MySQL/PostgreSQL)
4. 通过阻止 PHP 执行 + 访问控制来保护 `data/` 目录
当前的架构从根本上将“混淆”(SHA-256 文件名)与“安全”(访问控制)混为一谈。数据库是解锁每个混淆文件名的钥匙。
## CVE-REQUEST-002: RCE 纵深防御失效 (条件性 CVSS 8.1)
**文件:** `app/api/upload.php`, 第 121-124 行
```
$fileExtension = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
if (!in_array($fileExtension, $config['extension'])) {
$validationErrors[] = 'Unallowed extension';
}
```
阻止 PHP 文件执行的**唯一**屏障是 `config/config.php` 中的扩展名白名单。没有**任何**辅助防御措施:
- `data/` 目录中没有禁用 PHP 执行的 `.htaccess`
- PHP 中没有 `disable_functions`
- 没有 `open_basedir` 限制
- 没有任何 Apache `` 或 `RemoveHandler` 指令
如果管理员将 `.php` 或 `.phtml` 添加到白名单中(微不足道的配置更改),上传的 PHP 文件就可以在 `http://target/data/.php` 直接执行。
## CVE-REQUEST-003: 无效的 MIME 验证代码 (CVSS 5.4)
**文件:** `src/Core/SecurityUtils.php`, 第 54-123 行
`validateUploadedFile()` 方法通过 `finfo`(魔术字节)执行 MIME 类型检查,但在 `upload.php` 中**从未被调用**。上传处理器只检查文件扩展名 —— 没有任何内容检查:
```
// SecurityUtils.php:100-104 — DEAD CODE:
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
```
攻击者可以上传包含多语言内容(有效的 JPEG + PHP 代码)且具有有效扩展名(`.jpg`)的文件。
## CVE-REQUEST-004: 针对 Master Key 的时序攻击 (CVSS 2.6)
**文件:** `app/api/verifydelete.php`, 第 68 行;`app/api/verifydownload.php`, 第 68 行
```
if ($inputKey === $config['master']) {
```
`===` 执行逐字节比较,在发现第一个不匹配时提前返回。这会泄露关于正确 master key 的时序信息。应该使用 `hash_equals()`。
## 其他发现(共 10 项):
| # | 漏洞 | CVSS | 类型 |
|---|--------------|------|------|
| 4 | 数据完整性丢失(过早执行 htmlspecialchars) | N/A | 正确性 |
| 6 | IP 不匹配未阻止操作(仅警告) | 3.7 | 访问控制失效 |
| 7 | 通过 HTTP 标头伪造日志 (User-Agent) | 3.5 | 日志注入 |
| 8 | 旧文件清理中的竞态条件 (TOCTOU) | 2.0 | 竞态条件 |
| 9 | 无效的安全代码 (validateUploadedFile 未使用) | — | 死代码 |
| 10 | 生产环境中开启完整错误报告 (E_ALL) | — | 信息泄露 |
## 安全亮点
代码库**确实**具有良好的安全实践:
- 在所有更改状态的 API endpoint 上验证 CSRF token
- 在执行 `move_uploaded_file()` 之前检查 `is_uploaded_file()`
- 文件名存储为 SHA-256 哈希值(防止直接路径注入)
- 用于下载/删除操作的一次性 token
- 到处都通过 PDO 使用预处理语句
- 使用 Argon2ID 进行密码哈希处理
- 所有 PHP 文件均使用 `strict_types=1`
- 任何地方都没有 `exec()`、`system()`、`shell_exec()`
- 基于白名单的扩展名验证
## 影响
- **完全绕过所有安全机制** — 下载 SQLite DB → 提取所有文件哈希值 → 直接访问每个上传的文件
- **文件机密性完全丢失** — 上传到该服务的所有文件都会被暴露
- **数据库泄露** — 文件注册表、下载/删除密钥、审计记录、用户元数据
- **应用程序日志泄露** — 请求历史、IP 地址、明文形式的 token
- 如果扩展名白名单被扩充,**可能发生 RCE**
- 通过多语言文件绕过 MIME(无效的验证代码)
- 通过时序侧信道**暴力破解 master key**
## 补丁 / 修复
1. 为 `data/`、`db/`、`logs/` 添加 `.htaccess`:
Deny from all
2. 在 `data/` 中添加辅助性的 PHP 执行预防:
Deny from all
3. 在 `upload.php` 中调用 `SecurityUtils::validateUploadedFile()`
4. 将 master key 比较中的 `===` 替换为 `hash_equals()`
5. 在检测到 IP 不匹配时阻止访问(不要仅记录日志)
6. 对日志输入进行清理(去除 User-Agent 中的换行符)
7. 在数据库中存储原始数据,仅在展示层执行 `htmlspecialchars()`
## 披露时间线
- **2026-06-28:** Fatullayev Asadbek (Kimdir01) 发现了所有漏洞
- **TBD:** 报告给维护者
- **TBD:** 通过 GitHub 请求 CVE
- **TBD:** 发布公告
## 致谢
- 发现者:Fatullayev Asadbek | GitHub: Kimdir01
- 独立安全研究员
## 参考
- CWE-552:外部方可访问的文件或目录
- CWE-693:保护机制失效
- CWE-208:可观察的时序差异
- CWE-434:不受限制的文件上传
标签:OpenVAS, PHP, Web安全, 信息泄露, 数据泄露, 漏洞PoC, 蓝队分析