开头导语
2026年8月3日,安全研究员披露了 OpenWrt LuCI 管理界面的一个操作系统命令注入漏洞,编号 CVE-2026-69096,CVSS 评分 8.8(High)。漏洞位于 luci-app-dockerman 插件的 ucode RPC 后端 docker_rpc.uc,攻击者只要持有一个仅具备"只读"权限的 LuCI 会话,就能通过一个 HTTP POST 请求在 OpenWrt 主机上以 root 身份执行任意命令。该漏洞已附带完整 PoC,截至公告发布时官方尚未给出修复版本。
对于大量使用 OpenWrt 软路由、NAS 旁路由、Docker 化家庭实验室的中国用户而言,这是一个必须立刻自查的问题。下面带你两分钟完成排查,再深入分析漏洞原理和修复方案。
快速自查(2 分钟速查)
这个漏洞仅影响 LuCI master 与 openwrt-25.12 快照源中的 luci-app-dockerman(JS/ucode 转换后的新版本后端),影响的前提是安装了 luci-app-dockerman 插件且管理界面暴露在网络可达的位置。openwrt-24.10 与 openwrt-23.05 稳定版不含该 ucode 后端,不受影响。
第一步:SSH 登录你的 OpenWrt 设备,确认是否安装了受影响插件,以及它的版本来源:
# 1. 查看是否安装 luci-app-dockerman
opkg list-installed | grep -i dockerman
# 2. 查看其详细版本(关注包名是否带 25.12 或 master 快照标识)
opkg status luci-app-dockerman | grep -E 'Version|Package'
# 3. 确认你的 OpenWrt 源是稳定版还是快照版
cat /etc/openwrt_release | grep DISTRUCT_RELEASE
关键结论:如果 Version 字段中包含 25.12 或类似快照版本号(如 26.162.29621~507ab5e),并且你确实安装了 luci-app-dockerman,那么你很可能受影响。如果版本号是 24.10 或 23.05 稳定版系列,则不受此漏洞影响。
第二步:检查 LuCI 管理界面是否对外暴露。正常情况下 OpenWrt 的 LuCI 仅监听 LAN 口;如果你为了远程访问把 80 或 443 端口做了端口转发或放行到 WAN,风险会显著放大:
# 查看 uhttpd 监听端口与绑定地址
uci show uhttpd | grep -E 'listen_http|listen_https|listen'
# 查看防火墙是否放行了 LuCI 到 WAN
uci show firewall | grep -E 'dest_port.*(80|443)|src.*wan'
关键结论:如果 uhttpd 的 listen_http/listen_https 出现 0.0.0.0:80 或 0.0.0.0:443,并且防火墙对 WAN 放行了对应端口,那么你的设备直接暴露在公网攻击面内,应立刻关闭 WAN 侧访问。
第三步:检查是否存在低权限/委托账户。漏洞利用前提是攻击者已有一个具备 luci-app-dockerman read ACL 的会话。如果你给家人、访客或第三方插件配置了受限 LuCI 账户,这些账户一旦被攻陷就会成为突破口:
# 查看所有 rpcd/ubus ACL 配置
ls /usr/share/rpcd/acl.d/
# 查看 dockerman 的 ACL(关注 read 段是否暴露 ttyd_start)
cat /usr/share/rpcd/acl.d/luci-app-dockerman.json
# 查看 ACL 中是否对 read 段开放了 docker.* 通配
grep -A5 '"read"' /usr/share/rpcd/acl.d/luci-app-dockerman.json
关键结论:如果 read 段里出现 docker.* 或 docker.container.* 通配符,意味着任何只读权限的账户都能调用本应仅限管理员使用的 ttyd_start 变更方法——这正是漏洞的根因。
影响范围(风险确认表)
先看受影响条件。只有同时满足以下几项,才构成实际可利用路径:
| 条件维度 | 受影响情形 |
|---|---|
| 固件版本 | LuCI master 或 openwrt-25.12 快照(含 ucode 后端 docker_rpc.uc 的版本) |
| 插件包 | 已安装 luci-app-dockerman(如 26.162.29621~507ab5e) |
| 攻击者权限 | 持有一个具备 luci-app-dockerman read ACL 的认证会话 |
| 网络可达性 | LuCI/rpcd 的 /ubus 端点对攻击者网络可达 |
| 依赖组件 | uhttpd-mod-ubus、rpcd、ubusd 正常运行 |
再看不受影响的情形,避免误判和恐慌:
| 情形 | 说明 |
|---|---|
| openwrt-24.10 稳定版 | 不含 ucode 后端 docker_rpc.uc,不受影响 |
| openwrt-23.05 稳定版 | JS 后端实现,不含受影响的 ucode RPC 路径 |
| 未安装 dockerman 插件 | 虽然装了 OpenWrt,但没有 luci-app-dockerman,不构成攻击面 |
| 仅管理员单账户环境 | 如果管理面只对完全可信的管理员开放,实际风险受管理信任边界限制 |
| LuCI 仅监听 LAN | 攻击者无法从外部网络触达 /ubus 端点 |
常见误区一:以为装了 OpenWrt 就一定中招。 这个判断是错的。漏洞仅存在于 luci-app-dockerman 这一可选插件中,而且仅在 openwrt-25.12/master 快照源的特定 ucode 后端中。绝大多数使用 24.10/23.05 稳定版的用户并不受影响,不要被"OpenWrt 全军覆没"之类的夸张说法误导。
常见误区二:以为这是未认证远程 RCE。 漏洞利用必须先获得一个具备 read ACL 的认证会话。它不是 OpenWrt 默认的未授权漏洞,攻击门槛在于"如何拿到这个低权限会话"。但千万不要因此掉以轻心——很多家庭/小公司环境会弱密码、共享密码或把 LuCI 暴露在公网,门槛实际上很低。
常见误区三:以为只读权限就安全。 这是本次漏洞最值得警惕的工程教训。read ACL 本应只允许查看容器列表、读取状态,但因为 ACL 配置使用了 docker.* 通配符,它顺带暴露了 ttyd_start 这种本应是变更操作的方法。权限的"读"边界被一个通配符悄悄突破了。
技术分析
漏洞的根因是两个独立缺陷的组合:一个是 ACL 边界错误,另一个是 命令拼接未转义。两者叠加才形成完整的 root RCE 链路。下面逐一拆解。
缺陷一:read ACL 暴露变更方法。 在 luci-app-dockerman 的 ACL 配置文件 /usr/share/rpcd/acl.d/luci-app-dockerman.json 中,read 段使用了 docker.* 与 docker.container.* 这样的通配符,本意是让只读账户能查看容器状态。但 docker.container.* 通配符顺带把 docker.container.ttyd_start 这个方法也包含进去了。ttyd_start 的语义是"为容器启动一个 web 终端",这是一个典型的写操作——它会拉起 ttyd 进程、占用端口、创建可写入的会话。把写操作放进 read 段,等于把权限边界悄悄撕开了一个口子。
缺陷二:shell 命令拼接未转义。 当 ttyd_start 被调用时,rpcd 会执行 ucode 后端 docker_rpc.uc 中的 run_ttyd(request) 函数。该函数在第 300 行附近构造 ttyd_cmd 字符串:
// docker_rpc.uc:300 附近
ttyd_cmd = `ttyd -q -d 2 --once --writable -p ${port} docker`
ttyd_cmd = `${ttyd_cmd} -H "${sock_str}" exec -it`
ttyd_cmd = `${ttyd_cmd} ${id} ${cmd} &` // <-- id/cmd 来自请求,未转义
system(ttyd_cmd) // <-- 直接 system() 调用
这里 id、cmd、uid 三个字段直接从 ubus 请求中取出,被原样插入到 shell 命令字符串中,然后通过 system() 在 rpcd 的 root 上下文中执行。system() 调用的是 /bin/sh -c,会解释所有 shell 元字符。攻击者只要在 id 字段里塞入 x & id > /tmp/marker # 这样的内容,shell 就会先把原始 ttyd 命令放到后台,再执行攻击者注入的 id 命令,最后用 # 注释掉剩余片段。
完整利用链路如下:攻击者持有一个具备 luci-app-dockerman read ACL 的认证会话,向 /ubus 发送一个 JSON-RPC POST 请求,uhttpd-mod-ubus 检查会话权限时,因为 read ACL 通配符的存在,判定该会话有权调用 docker.container/ttyd_start;rpcd 的 ucode 对象随后进入 run_ttyd,把攻击者控制的 id/cmd/uid 拼到 ttyd_cmd 中,system() 调用 /bin/sh 解释元字符,命令在 rpcd/root 上下文中执行。整条链路不需要真实的 Docker 容器存在,shell 元字符序列本身就能完成后台化、命令注入和注释截断。
研究员在授权的 OpenWrt rootfs/容器实验环境中验证了漏洞。实验先创建一个无 ACL 会话作为阴性对照(被正确拒绝,返回 Access denied),再创建一个 read-ACL 等效会话,向其发送构造的请求。响应中直接返回了被拼接后的完整命令字符串,并在实验室目录下写入了 root 标记文件,证明命令确实以 root 身份执行。PoC 本身只写标记文件、不下载 payload、不连接外部回调主机、不修改启动项、不留持久化,符合负责任的披露规范。
下面是 CVSS 3.1 向量的逐项解读:
| 向量项 | 取值 | 含义 |
|---|---|---|
| 攻击途径 AV | Network | 通过网络可达的 /ubus 端点即可利用 |
| 攻击复杂度 AC | Low | 无需特殊条件,构造一个 JSON-RPC 请求即可 |
| 所需权限 PR | Low | 仅需具备 read ACL 的低权限/委托会话 |
| 用户交互 UI | None | 不需要受害者做任何操作 |
| 影响范围 S | Unchanged | 影响局限于受影响组件本身 |
| 机密性 C | High | 可读取所有 secrets、配置、密钥 |
| 完整性 I | High | 可修改防火墙规则、网络配置、容器 |
| 可用性 A | High | 可中断网关/NAS 服务,造成拒绝服务 |
从同类历史漏洞看,OpenWrt 生态中的命令注入问题并不罕见,但多数集中在第三方插件或老版本的 luci 组件。CVE-2026-69096 的特殊之处在于:它是 openwrt-25.12 引入 ucode 后端转换后的新代码引入的回归型缺陷——JS 到 ucode 的重写过程中,原本的权限边界和参数校验没有被完整迁移,read ACL 的通配符设计埋下了隐患。这提醒所有做语言/框架迁移的项目:迁移代码时,权限模型和安全检查必须显式重新审计,不能假设"功能等价"就等于"安全等价"。
进一步从代码层面看,run_ttyd 函数的问题不止在于使用了 system(),更在于它把多个用户可控字段拼接到同一条命令字符串里。id 字段本应只接受容器 ID(一串十六进制字符),cmd 字段本应只接受受控的终端命令枚举值,但在实际实现中这两个字段都被当作自由文本直接插入 shell 命令。即便不考虑 ACL 边界问题,这种"多个未校验字段拼接成一条 shell 命令"的写法本身就是高危模式。安全的做法是:对 id 做严格的容器 ID 格式校验(正则 ^[a-f0-9]{12,64}$),对 cmd 使用白名单枚举,执行时改用 argv 数组形式的 execve 调用,从根本上让 shell 元字符失去意义。
还有一个值得关注的细节是 ttyd_start 这个方法本身的设计定位。它的本意是为容器启动一个 web 终端(ttyd 是一个把终端暴露成 web 应用的工具),这天然就是一个高权限操作——一旦 web 终端建立,操作者等于拿到了容器内的 shell。把这样一个本质上等价于"开 shell"的方法命名为 ttyd_start 并塞进容器管理接口,再用通配符暴露给 read 权限,相当于把"只读用户可以随时给自己开一个 root 终端"写进了系统配置。这是命名与权限设计严重不匹配的典型案例,值得所有设计 RPC/管理接口的开发者引以为戒。
中国用户相关性分析: OpenWrt 在国内的渗透率极高。软路由爱好者社区(如恩山论坛、KoolShare)长期以 OpenWrt 及其衍生固件(如 ImmortalWrt、Lean's LEDE)为核心,大量用户会在软路由上跑 Docker——这正是 luci-app-dockerman 插件的目标场景。常见的部署模式包括:x86 软路由 + Docker 跑 AdGuard Home / Navidrome / Jellyfin / Home Assistant;NAS 设备通过旁路由方式接入 OpenWrt;小米/华硕/网件路由器刷 OpenWrt 解锁更多功能。一旦这些设备为了远程管理而把 LuCI 端口暴露到公网(很多人会用 DDNS + 端口转发),攻击面就成立了。即便不暴露公网,内网中被横向移动的攻击者拿到一个低权限 LuCI 会话同样可以利用本漏洞提权到 root,进而控制整个网关。
修复指南(分优先级)
P0 紧急操作(立即执行)
第一,关闭 LuCI 对 WAN 的暴露。这是最立竿见影的缓解措施。如果你的 uhttpd 监听在 0.0.0.0,并且防火墙放行了 WAN 侧的 80/443 端口,请立即关闭:
# 1. 让 uhttpd 只监听 LAN(假设 LAN 网段是 192.168.1.0/24)
uci set uhttpd.main.listen_http='192.168.1.1:80'
uci set uhttpd.main.listen_https='192.168.1.1:443'
uci commit uhttpd
/etc/init.d/uhttpd restart
# 2. 删除对 WAN 放行 80/443 的防火墙规则(按实际情况调整)
uci show firewall | grep -B2 'dest_port.*80'
# 找到对应 rule name 后:
# uci delete firewall.@rule[N]
# uci commit firewall && /etc/init.d/firewall restart
第二,临时收紧 dockerman 的 read ACL。由于官方尚未发布修复版本,你可以手动编辑 ACL 文件,把 ttyd_start 从 read 段中移除。注意 OpenWrt 升级会覆盖 /usr/share/rpcd/acl.d/ 下的文件,所以这只是临时措施:
# 备份原 ACL
cp /usr/share/rpcd/acl.d/luci-app-dockerman.json \
/usr/share/rpcd/acl.d/luci-app-dockerman.json.bak
# 用 vi 编辑,在 read 段把通配符 docker.container.* 改为
# 显式列表,明确排除 ttyd_start:
# "docker.container": ["list","logs","inspect","stats"]
# 保存后重启 rpcd:
/etc/init.d/rpcd restart
第三,如果不需要 Web 管理容器,直接卸载插件。对于只是偶尔用 docker CLI 的用户,luci-app-dockerman 并非必需:
opkg remove luci-app-dockerman
# 确认 Docker 本身仍正常工作
docker ps
P1 长期方案(架构级修复)
第一,回退到稳定版固件。openwrt-25.12 和 master 快照本就是面向开发者的不稳定版本,生产环境不应使用。如果你的设备对稳定性有要求,建议回退到 openwrt-24.10 稳定版,它不含受影响的 ucode 后端:
# 查看当前固件版本
cat /etc/openwrt_release
# 如需回退,从 https://firmware-selector.openwrt.org
# 选择你的设备型号和 24.10 稳定版,按官方流程刷写
# 注意:跨大版本回退通常需要保留配置或重置
第二,关注官方修复进度并及时升级。漏洞披露时官方尚未给出修复版本,但相关的 commit(44618b5b、f4d0a449)已在 LuCI 仓库中。建议订阅 openwrt/luci 的 security advisories,修复版本发布后第一时间升级 luci-app-dockerman。修复方向应包含两点:一是从 read ACL 中移除 docker.container.* 通配符,只保留真正的只读方法;二是 run_ttyd 中改用 argv 风格的执行方式(如 execve 或 ucode 的 process 模块),彻底避免 shell 元字符注入。
额外防护建议
第一,审计所有 rpcd ACL 配置。不只 dockerman,OpenWrt 生态中很多第三方插件的 ACL 都存在类似的通配符问题。建议定期审查 /usr/share/rpcd/acl.d/ 下所有 JSON 文件,确认 read 段中没有任何会触发命令执行、服务控制或容器变更的方法。
第二,限制 rpcd 会话的有效期与权限。对于必须给他人开放的低权限账户,尽量缩小其 ACL 范围,并设置较短的超时时间。远程管理优先使用 VPN(如 WireGuard)接入内网后再访问 LuCI,而不是直接把 LuCI 暴露到公网。
第三,监控 /ubus 端点的异常请求。可以在 uhttpd 日志中过滤对 /ubus 的 POST 请求,关注请求体中是否包含 ttyd_start、docker.container 调用,以及 id/cmd 字段中的 shell 元字符(;、&、|、$()、反引号)。一旦发现这类模式,基本可以判定为利用尝试。
第四,凭据轮换与持久化排查。如果你怀疑设备曾被利用,除了升级固件,还应轮换所有存储在 OpenWrt 上的密钥(WireGuard 私钥、DDNS token、VPN 凭据),并检查 /etc/rc.local、crontab、/etc/init.d/ 下是否有攻击者植入的持久化脚本。
总结 + 参考信息
CVE-2026-69096 是一个典型的"权限边界 + 命令拼接"组合型漏洞。它再次提醒我们三件事:read 权限不等于安全,通配符是 ACL 配置的隐形炸弹,system() 接收用户输入是永远的雷区。对于中国大量使用 OpenWrt 软路由 + Docker 的用户,建议按以下行动清单处置:
1. SSH 登录设备,确认是否安装 luci-app-dockerman 及其版本来源(快照版还是稳定版)。
2. 立即关闭 LuCI 对 WAN 的暴露,让 uhttpd 只监听 LAN 口。
3. 若不需要 Web 管理容器,直接 opkg remove luci-app-dockerman。
4. 生产环境回退到 openwrt-24.10 稳定版固件,避免使用快照源。
5. 订阅 openwrt/luci 安全公告,修复版本发布后第一时间升级。
6. 审计 /usr/share/rpcd/acl.d/ 下所有 ACL 文件,剔除 read 段中的通配符与变更方法。
7. 远程管理改用 VPN 接入内网,不再把 LuCI 直接暴露公网。
参考信息
CVE 编号:CVE-2026-69096
CVSS 评分:8.8(High),向量 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
CWE 类型:CWE-78(操作系统命令注入)
受影响产品:OpenWrt LuCI luci-app-dockerman(openwrt-25.12/master 快照,ucode 后端)
公布日期:2026-08-03
发现者/来源:VulnCheck(disclosure@vulncheck.com)
官方公告:GitHub Security Advisory GHSA-cq4h-h8jr-3xqv(openwrt/luci)
相关 commit:openwrt/luci@44618b5b、openwrt/luci@f4d0a449
PoC 状态:已公开(附于 GHSA,仅写标记文件,无恶意载荷)
最后多说一句安全工程的教训。这个漏洞的根因是 openwrt-25.12 在做 JS 到 ucode 的后端迁移时,read ACL 的通配符设计没有同步收紧。这其实是所有框架迁移、语言重写项目都该警惕的典型陷阱:功能层面的"等价迁移"很容易通过测试,但权限边界、参数校验、错误处理这些安全属性往往是非显式的,迁移时极易遗漏。建议任何做大规模重构的团队,把"ACL/权限模型的逐方法审计"作为迁移 checklist 的硬性条目,而不是依赖事后渗透测试来兜底。OpenWrt 社区已承诺会为 LuCI 包增加 ACL 审查测试,用来捕获那些把命令执行、服务控制、容器变更暴露在 read 段的配置——这是一个值得所有插件生态借鉴的防御思路。
龙虾池子
夜雨聆风