在下载任何内容之前,先看已有哪些公开信息:
| 来源 | 提供的信息 |
|---|---|
| 海康威视安全公告 | “API 端点缺少身份验证检查”,建议升级至 V2.6.3 / V3.0.1 |
| NVD CVE-2025-39247 | CVSS 指标;攻击向量为网络,无需认证,影响范围改变,机密性影响高 |
| Wiz、ZeroPath、SentinelOne、OffSeq、gbhackers、cybersecuritynews 的摘要 | 均复述公告内容;无技术细节、无 PoC、未指明受影响的端点 |
| GitHub PoC 聚合仓库(poc-in-github、0xMarcio/cve 等) | 未收录任何 PoC |
| 论坛(ipcamtalk、cctvforum、reddit)、BT 索引站 | 无 PoC,无泄露的安装包;ipcamtalk 有 HikCentral 使用讨论帖,但没有任何与利用相关的内容 |
因此,截至分析日期,没有公开的技术文章披露该漏洞。任何复现工作都必须从二进制比对开始。
CVE-2025-39247 公开后,V2.6.2 的下载页面已被下线,但 CDN 上的文件从未被清除。文件名可从在下线前索引过该页面的第三方镜像(FileHorse —— 发布了文件名和 MD5)中恢复。有了该文件名,仅使用普通浏览器 User-Agent 和匹配的 Referer 头,直接向海康威视 CDN 发送 GET 请求,仍可获取该文件。```bash
curl -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0"
-H "Referer: https://www.hikvision.com/en/support/download/software/hikcentral-professional-v2-6-2/"
-O
"https://www.hikvision.com/content/dam/hikvision/en/support/download/vms/hcp-2-6-2/HikCentral-Professional_Full-Pack_V2.6.2.202501211507_Win_x64_Installer.exe"
结果:
- 大小: 1,314,043,792 bytes (1.22 GB)
- MD5: `76d42e7cb16dc0e177b9a35d0fed7ced` — 与 FileHorse 发布的值匹配, 证实是正版 Hikvision 签名安装程序 (而非第三方重新打包)
- CDN 响应头: `HTTP/2 200`, `content-type: application/x-msdownload`, `eo-cache-status: HIT`, `age: 2520395` (≈ 29 天在 Tencent EdgeOne 边缘缓存中, 未被淘汰)
**值得向 Hikvision PSIRT 报告的附带观察:** V2.6.2 页面的"下架"只是表面文章. 该孤立文件可在 CDN 上访问, 无需认证, 也没有签名 URL 门禁. V2.5.1 和 V2.6.0 Full Packs 的响应方式相同.
### 2.2 已修补的安装程序 (V2.6.3 Base Pack)
当前从 V2.6.3 下载页面链接; 同样的获取方法:```
https://www.hikvision.com/content/dam/hikvision/en/support/download/vms/hcp-2-6-3/HikCentral-Professional_Base-Pack_V2.6.3.202508280142_Win_x64_Installer.exe
仍然从 V2.6.2 下载页面链接(Hikvision 希望用户应用它):``` https://www.hikvision.com/content/dam/hikvision/en/support/download/vms/cve-2025-39247-security-vulnerability-patch/HikCentral-Professional_CVE-2025-39247_FixPack_V2.3.1-V2.6.2V3.0.0_20250904.exe
- 大小: 32,599,504 字节 (31 MB)
---
## 3. 静态提取
这三个都是 InstallShield 风格的 PE 包装器。`7z` 分两遍将其剥离:```bash
# pass 1 — extract PE resources
7z x -o./extracted/v262/pe downloads/<v2.6.2 installer>
7z x -o./extracted/v263/pe downloads/<v2.6.3 installer>
# pass 2 — extract the nested 7-Zip streams hiding in PE resources
# (note: the resource-type label says "ZIP" but the byte signature is 7-Zip)
for ver in v262 v263; do
for z in extracted/$ver/pe/.rsrc/2052/ZIP/*; do
sz=$(stat -c %s "$z"); [ "$sz" -lt 1048576 ] && continue
7z x -o"extracted/$ver/payload/$(basename $z)" "$z"
done
done
两个安装程序都会展开为大约二十几个命名的载荷归档。顶层目录揭示了架构:
| 归档 | 内容 |
|---|---|
246/Nginx + 246/www | Nginx Web 层(前门)和 SPA Web UI |
244/bin | 原生 C++ 服务二进制文件 + 证书 |
240 | 捆绑的 PostgreSQL + 数据库初始化脚本 |
238 | Bee* 运行时(BeeAgent、BeeGuard、…) |
282 | 应用程序服务和附加组件(C++ 后端代码的主体) |
281, 283-284, 396, 398-399, 513, 520, 538-541, 573 | 较小的附加组件、插件、升级/卸载存根 |
关键是,任何地方都没有 Java JAR/WAR。后端是运行在 Nginx 上的 C++ — 这一点很重要,因为它一开始就告诉你,任何身份验证缺陷都会出现在编译后的二进制文件中,而不是在反编译非常容易的 WAR/JAR 字节码中。
for ver in v262 v263: find . -type f -print0 | xargs -0 md5sum | sort -k2 > inv_$ver.txt
v262 = {ln[34:]: ln[:32] for ln in open("inv_v262.txt")} v263 = {ln[34:]: ln[:32] for ln in open("inv_v263.txt")} common = set(v262) & set(v263) changed = [p for p in common if v262[p] != v263[p]]
结果:在通用路径中有 239 个更改的文件。分布情况:
| Bucket | Count | Comment |
|---|---|---|
| `246/www/*.js`(Web UI 块) | ~200 | 主要是版本升级导致的资产哈希变化;忽略 |
| `246/Nginx/conf/nginx_location.conf` | **1** | **单文件 Nginx 配置差异 — 从这里开始** |
| `246/Nginx*/install.bat` | 6 | 安装脚本,无关 |
| `244/bin/*.{exe,dll}` | 6 | 本机二进制文件(`DistributionFilter.dll`、`FilterChain.dll`、媒体工具) |
| `282/*.{exe,dll}` | 55 | 应用程序服务(最大的集群 — `platform.dll`、`PersonCredential.dll`、`baseacs.*`,…) |
| `240/bin/pg_*` | 6 | Postgres 重建,无关 |
| `284/hplugin/dahua_plugin/*` | 4 | 供应商 SDK 刷新(+10.97 MB)— 无关 |
| `284/hplugin/onvif_plugin/*` | 4 | 供应商 SDK 刷新(+0.99 MB)— 无关 |
| 升级/卸载存根、崩溃报告器 | ~30 | 相同大小的 PE 时间戳重新生成,忽略 |
在排除了供应商 SDK 刷新、构建系统时间戳漂移以及纯粹的装饰性资产更新后,真正与安全相关的差异缩减为:
- 一个 Nginx 配置文件
- 在 `282/` 中的少量 C++ 二进制文件(最显著的是 `platform.dll`,HCMP 后端服务,增加了 57 KB)
---
## 5. Nginx 差异:免费的错误定位
在 V2.6.2 和 V2.6.3 之间运行 `diff -u nginx_location.conf` 会产生恰好一个块:V2.6.3 中一个全新的 `location =` 块。```nginx
#禁止非127.0.0.1和::1的重置密码 ← "Forbid non-127.0.0.1/::1 password reset"
location = /ISAPI/Bumblebee/Platform/V0/Permission/ChangeDefaultUserPassword {
if ($remote_addr ~ ^(127\.0\.0\.1|::1)$) { set $allowed 1; }
if ($allowed != "1") { return 403; }
if ($scheme = "http") { proxy_pass http://http_backend; }
if ($scheme = "https") { proxy_pass http://http2_backend; }
}
这一处 21 行的新增代码就是 CVE-2025-39247 在入口层的全部修复,它足以说明一切:
PUT /ISAPI/Bumblebee/Platform/V0/Permission/ChangeDefaultUserPassword/ISAPI/Bumblebee/Platform/V0/* 代理指令,转发到后端,且不进行远程地址限制,也没有 Nginx 级别的身份验证检查。这条中文注释很重要:它的字面意思是“禁止从非 127.0.0.1/::1 的地址重置密码”。
Web UI 的 JavaScript(已压缩,位于 246/www/Portal/*.js)以如下方式调用该端点:```js
// API definition (de-minified):
changeDefaultUserPassword: function (sid, payload, opts) {
return http.put({
url: "ISAPI/Bumblebee/Platform/V0/Permission/ChangeDefaultUserPassword",
data: { ChangeDefaultUserPasswordRequest: payload },
params: { SID: sid }
}, opts);
}
// Call site: changeDefaultUserPassword(n.SID, { UserName: form.userName, // "admin" Password: jsEncrypt.encrypt(form.newPassword), // RSA-PKCS1v15 ActiveCode: form.activeCode ? jsEncrypt.encrypt(form.activeCode) : "", QuestionList: questionAnswers // [{ID, Answer}, …] or [] }, ...);
// The RSA pubkey comes from another unauthenticated endpoint: getCrypto: function () { http.get({ url: "ISAPI/Bumblebee/Platform/V0/Security/Crypto" }); } // returns: { CryptoKey: <PEM/base64 RSA pubkey>, ... }
因此,即使在反汇编任何内容之前,面向公众的 JS 就已经为你提供了完整的线上交互格式(wire shape)。但它也揭示了尴尬之处:请求体需要 **有效的 `ActiveCode` 或正确的 `QuestionList` 答案**。发送空值不起作用——后端会以文档记录的错误之一拒绝(通过字符串挖掘 `platform.dll`,§7 验证)。
所以 CVE-2025-39247 **并非** "该端点存在认证绕过;发送空字段即可修改密码"。而是 "该端点 *信任本地调用者* 已持有正确的 ActiveCode;漏洞在于缺少网络层的 localhost 门禁,因此任何 *远程* 调用者只要 *也能* 提供有效的 ActiveCode 或 QuestionList 就能成功。" 因此真正的利用难题是:ActiveCode 或 QuestionList 从何而来?
---
## 7. 后端处理器——字符串挖掘 `platform.dll`
`platform.dll` 是 HCMP 后端服务,在 V2.6.2 中为 46.87 MB,在 V2.6.3 中为 46.93 MB(+57 KB)。所有路由字符串、日志格式字符串以及 C++ RTTI / `__FUNCTION__` 字符串均以明文形式存在。
`platform.dll` 中处理器附近的字符串:```
VSMPlatform::CCmd::Security_ChangeDefaultUserPassword
..\..\src\vsmplatform\NetworkComm\ISAPI\CmdHandle\SecurityCmd.cpp
/ChangeDefaultUserPasswordRequest/UserName
/ChangeDefaultUserPasswordRequest/Password
/ChangeDefaultUserPasswordRequest/ActiveCode
/ChangeDefaultUserPasswordRequest/CryptoKey
/ChangeDefaultUserPasswordRequest/QuestionList
/ChangeDefaultUserPasswordRequest/QuestionList[%d]/Answer
[%s]IP=[%s] Reqest Paramter active_code and security_question both empty!
[%s]invalid active_code=%s
[%s]security question verify fail
[%s]IP=[%s] Isn't Super User!
[%s]IP=[%s] User Not Exist!
[%s]RSADecrypt fail! err_code=%d session=%s encrypt_str_id=%s encrypt_answer=%s
[%s] ChangeDefaultUserPassword by ip[%s].
两个版本的 platform.dll 都包含这些字符串。因此验证逻辑在 V2.6.2 中也存在 — ActiveCode 和 QuestionList 确实会被检查。