Yakit Extension 供应链安全研究:从插件代码执行到宿主能力边界
从 Burp 扩展研究出发,对 Yakit 插件机制进行黑盒验证,并进一步讨论安全工具扩展生态中的代码执行与供应链风险
0x00 引言:安全工具的插件,真的安全吗?
在 Web 安全测试过程中,我们经常会使用各种各样的安全工具。
Burp Suite、Yakit、VS Code,以及各种拥有插件、扩展、脚本生态的平台,已经成为安全研究人员日常工作流的一部分。
这些工具有一个非常明显的共同点:
允许用户加载第三方代码。
从使用者的角度来看,插件通常意味着“增加一个功能”。
例如:
安装插件
↓
获取新功能
↓
在工具内部运行
但从操作系统的角度来看,事情并没有简单。
一个插件并不是普通的配置文件,也不是单纯的数据。
本质来看是一段被宿主程序加载并执行的第三方代码。
因此,插件机制实际上天然存在一个安全边界:
第三方代码
↓
插件运行时
↓
宿主程序
↓
当前用户权限当插件来源可信时,这是一种正常的扩展机制。
但如果插件本身被攻击者控制,那么就会变成
功能扩展->代码执行载体(payload)
这也是笔者最初产生兴趣的地方。
在此之前,已经有安全研究人员针对安全工具的扩展机制提交过相关安全报告。例如,HackerOne 上曾出现过针对Burp Suite扩展代码执行/恶意扩展风险的研究。
这类研究让我产生一个问题:如果安全工具的扩展机制本身允许第三方代码进入宿主执行环境,那么Yakit的插件机制是否也存在类似的供应链风险?
因此本文不试图证明“插件可以执行代码”这一显而易见的事实,而是从一个更具体的问题开始:
Yakit对第三方插件代码的信任边界在哪里?
为回答这个问题,我从一个普通插件开始,对其加载、保存、运行过程进行了黑盒验证,并通过受控的payload对最终代码执行结果进行了确认。
0x01 从 Burp 扩展到 Yakit:一个值得验证的安全假设
在这里先放出:
HackerOne Report #3014158 — “Burp Suite extensions can execute arbitrary code”这篇报告的全过程
这篇报告很好的印证了,在现代供应链安全中,插件也是一个重要因素。
因为传统供应链安全中,我们更多关注软件包、依赖库、镜像以及构建流程中的恶意代码。然而,对于安全人员来说,还有一类最容易被忽视的供应链攻击面--安全工具自身的插件与扩展生态
Burp Suite、Yakit 等安全工具广泛应用于渗透测试、红队行动以及漏洞研究。为了满足不同场景下的需求,这些工具通常提供 Extension、Plugin、Script 等类似的扩展机制,允许用户加载第三方代码。
这类机制带来了很强的扩展能力,但同时也意味着:
一旦攻击者能够将恶意代码伪装成正常的扩展并诱导目标安全人员加载,攻击面就可能从“被测试的目标”反向转移到“安全测试人员自身”。
对于红队人员而言,这个问题尤其值得关注。
但是,必须实践出真知,不能因为Yakit存在类似功能,直接跳步到Yakit=存在跟Burp Suite一样的问题,两者的插件架构、代码运行环境、权限模型以及插件分发机制均存在差异。
所以我将 Burp 的研究结果作为攻击面假设,而不是作为 Yakit 漏洞存在的证明。
所以我先假设:
1:
Yakit 插件是否允许第三方代码进入宿主执行环境?
2:
插件执行时是否继承 Yakit / 当前用户的运行权限?
3:
恶意插件是否能够在用户正常使用插件的过程中执行额外代码?
4:
如果 H1~H3 成立,这种机制是否能够形成供应链攻击路径?0x02 Yakit 插件运行模型
这里直接放图,表述运行逻辑
Yakit
│
┌────────┴────────┐
│ │
插件管理器(Plugin Manager)插件运行时(Plugin Runtime)
│ │
└────────┬────────┘
↓
插件代码(Plugin Code)
↓
宿主进程(Host Process)
↓
用户上下文(User Context)
↓
操作系统 / 网络(OS / Network)从安全边界角度来看,插件执行过程可以抽象为四个阶段:
Plugin Source
↓
Plugin Loading
↓
Plugin Execution
↓
Host Capability第一阶段决定代码来自哪里;
第二阶段决定宿主是否接受这段代码;
第三阶段决定代码是否进入执行环境;
第四阶段决定代码执行后能够触达哪些资源。
真正需要关注的并不是“插件有没有代码执行能力”,而是“第三方代码是否能够跨越宿主的信任边界”。
0x03 黑盒验证:从一个普通插件开始
3.1创建插件
首先进入 Yakit 的插件开发功能,新建一个 Extension。
这里需要关注的并不是插件创建本身,而是 Yakit 对 Extension 的定位。
从使用方式来看,Extension 并不是一个单纯的配置文件或者静态规则,而是允许研究人员编写扩展逻辑,并由 Yakit 提供的运行环境进行加载和执行。
因此,从安全模型上看,可以将其抽象为:
Extension
↓
插件代码
↓
Yakit Extension Runtime
↓
Yakit 宿主进程这意味着,一旦第三方代码进入 Extension Runtime,其安全边界实际上就已经从“插件文件本身”转移到了插件代码与宿主环境之间的信任关系
我这里写的是模仿算命瞎子师父写的越权监测工具,稍后也是在该代码上进行修改
yakit.AutoInitYakit()
// 插件元数据
__meta__ = {
"name": "越权检测工具",
"type": "mitm",
"author": "模仿瞎越插件",
"description": "检测Web应用程序中的越权漏洞",
"tags": ["越权", "授权", "安全"],
"help": "此插件通过发送三种不同的请求(原始请求、低权限请求和无认证请求)来检测越权漏洞",
"params": [
{
"name": "低权限用户的认证信息",
"type": "string",
"default": "Cookie: JSESSIONID=test;UUID=1; userid=user\nAuthorization: Bearer lowprivtoken",
"description": "低权限用户的认证信息",
"required": true,
"value": "lowPrivAuth",
},
{
"name": "要移除的认证头",
"type": "string",
"default": "Cookie\nAuthorization\nToken",
"description": "要移除的认证头",
"required": true,
"value": "headersToRemove",
},
{
"name": "白名单域名",
"type": "string",
"default": "example.com",
"description": "白名单域名(多个域名用逗号分隔)",
"required": false,
"value": "whitelistURLs",
},
{
"name": "启用白名单",
"type": "boolean",
"default": false,
"description": "启用白名单",
"required": false,
"value": "enableWhitelist",
},
],
}
// 全局变量
requestHashes = make(map[string]bool)
results = []
count = 0
version = "1.0"
// 主函数
func main() {
yakit.Info("越权检测工具 v%v 已加载!", version)
// 获取参数
lowPrivAuth = cli.String("低权限用户的认证信息")
headersToRemove = cli.String("要移除的认证头")
whitelistURLs = cli.String("白名单域名")
enableWhitelist = cli.Bool("启用白名单")
yakit.Info("低权限认证信息: %v", lowPrivAuth)
yakit.Info("要移除的认证头: %v", headersToRemove)
if enableWhitelist {
yakit.Info("白名单已启用: %v", whitelistURLs)
}
// 注册MITM处理器
yakit.Info("开始监听HTTP请求...")
}
// 处理HTTP请求
hijackHTTPRequest = func(req) {
// 不处理请求,只在响应阶段处理
return req
}
// 处理HTTP响应
hijackHTTPResponse = func(rsp) {
// 获取请求和响应信息
req = rsp.Request()
url = req.Url
method = req.Method
// 检查是否需要处理
if !shouldProcess(url, method) {
return rsp
}
// 异步处理,避免阻塞
go func() {
checkAuthorization(req, rsp)
}()
return rsp
}
// 判断是否需要处理请求
func shouldProcess(url, method) {
// 检查白名单
if cli.Bool("启用白名单") {
whitelistURLs = cli.String("白名单域名")
whitelistArray = str.Split(whitelistURLs, ",")
inWhitelist = false
for _, whiteUrl = range whitelistArray {
if str.Contains(url, str.Trim(whiteUrl, " ")) {
inWhitelist = true
break
}
}
if !inWhitelist {
return false
}
}
// 跳过静态文件
staticExtensions = ["jpg", "png", "gif", "css", "js", "pdf", "mp3", "mp4", "avi", "map", "svg", "ico", "woff", "woff2", "ttf"]
urlParts = str.Split(url, ".")
if len(urlParts) > 1 {
extension = urlParts[len(urlParts)-1]
for _, ext = range staticExtensions {
if extension == ext {
return false
}
}
}
// 计算请求哈希,避免重复检测
urlWithoutParams = str.Split(url, "?")[0]
requestHash = method + ":" + urlWithoutParams
// 使用MD5哈希
md5Hash = codec.Md5(requestHash)
if requestHashes[md5Hash] {
return false
}
requestHashes[md5Hash] = true
return true
}
// 检查越权漏洞
func checkAuthorization(req, rsp) {
url = req.Url
method = req.Method
yakit.Info("检查越权: %v %v", method, url)
// 获取原始请求和响应
originalRequest = req.RawRequest
originalResponse = rsp.RawResponse
originalLen = len(rsp.Body)
// 创建低权限请求
lowPrivRequest = createLowPrivRequest(req)
// 发送低权限请求
lowPrivResponse = http.Request(
lowPrivRequest,
http.https(req.IsHttps),
http.timeout(10),
)
if lowPrivResponse.Error != nil {
yakit.Error("低权限请求失败: %v", lowPrivResponse.Error)
return
}
lowPrivLen = len(lowPrivResponse.Body)
// 创建无认证请求
noAuthRequest = createNoAuthRequest(req)
// 发送无认证请求
noAuthResponse = http.Request(
noAuthRequest,
http.https(req.IsHttps),
http.timeout(10),
)
if noAuthResponse.Error != nil {
yakit.Error("无认证请求失败: %v", noAuthResponse.Error)
return
}
noAuthLen = len(noAuthResponse.Body)
// 比较响应长度
lowPrivStatus = ""
noAuthStatus = ""
if originalLen == lowPrivLen {
lowPrivStatus = "✔ 可能存在越权"
} else {
lowPrivStatus = "差异: " + str.ToString(originalLen - lowPrivLen)
}
if originalLen == noAuthLen {
noAuthStatus = "✔ 可能存在越权"
} else {
noAuthStatus = "差异: " + str.ToString(originalLen - noAuthLen)
}
// 记录结果
count++
result = {
"id": count,
"method": method,
"url": url,
"originalLen": originalLen,
"lowPrivLen": lowPrivLen,
"lowPrivStatus": lowPrivStatus,
"noAuthLen": noAuthLen,
"noAuthStatus": noAuthStatus,
"originalRequest": originalRequest,
"originalResponse": originalResponse,
"lowPrivRequest": lowPrivRequest,
"lowPrivResponse": lowPrivResponse.RawResponse,
"noAuthRequest": noAuthRequest,
"noAuthResponse": noAuthResponse.RawResponse,
}
results = append(results, result)
// 输出结果
yakit.Info("ID: %v", count)
yakit.Info("URL: %v", url)
yakit.Info("原始长度: %v", originalLen)
yakit.Info("低权限长度: %v (%v)", lowPrivLen, lowPrivStatus)
yakit.Info("无认证长度: %v (%v)", noAuthLen, noAuthStatus)
// 如果发现可能的越权漏洞,创建风险
if originalLen == lowPrivLen || originalLen == noAuthLen {
risk.NewRisk(
url,
risk.title("可能存在越权漏洞"),
risk.severity("high"),
risk.details({
"url": url,
"method": method,
"originalLen": originalLen,
"lowPrivLen": lowPrivLen,
"noAuthLen": noAuthLen,
"lowPrivStatus": lowPrivStatus,
"noAuthStatus": noAuthStatus,
}),
)
}
}
// 创建低权限请求
func createLowPrivRequest(req) {
// 获取低权限认证信息
lowPrivAuth = cli.String("低权限用户的认证信息")
lowPrivAuthHeaders = str.Split(lowPrivAuth, "\n")
// 复制原始请求
newReq = req.RawRequest
// 解析请求
reqInfo = http.ParseRequest(newReq)
// 处理每个低权限认证头
for _, authHeader = range lowPrivAuthHeaders {
if str.Contains(authHeader, ":") {
parts = str.Split(authHeader, ":")
headerName = str.Trim(parts[0], " ")
headerValue = str.Trim(str.Join(parts[1:], ":"), " ")
// 移除现有的同名头
reqInfo.Headers = http.RemoveHeader(reqInfo.Headers, headerName)
// 添加新的认证头
reqInfo.Headers = append(reqInfo.Headers, headerName + ": " + headerValue)
}
}
// 重建请求
newReq = http.BuildRequest(reqInfo)
return newReq
}
// 创建无认证请求
func createNoAuthRequest(req) {
// 获取要移除的认证头
headersToRemove = cli.String("要移除的认证头")
headersToRemoveArray = str.Split(headersToRemove, "\n")
// 复制原始请求
newReq = req.RawRequest
// 解析请求
reqInfo = http.ParseRequest(newReq)
// 移除认证头
for _, headerName = range headersToRemoveArray {
headerName = str.Trim(headerName, " ")
if headerName != "" {
reqInfo.Headers = http.RemoveHeader(reqInfo.Headers, headerName)
}
}
// 重建请求
newReq = http.BuildRequest(reqInfo)
return newReq
}
可以看到不管是构建插件这一过程和代码本身并不构成漏洞。Extension 的核心目的就是为研究人员提供自定义能力,因此执行扩展代码属于正常设计行为。
真正值得关注的是后续问题:
如果这段代码并非由使用者自己编写,而是来自第三方插件,Yakit 是否能够区分“正常扩展代码”和“恶意扩展代码”?
3.2 复制一个已有扩展
为了模拟现实中的供应链场景,本文没有从零构造一个看起来明显具有攻击性的插件,而是选择一个已有 Extension 作为基础,在其正常功能代码中加入验证逻辑。
这样做的目的,是尽可能接近真实攻击场景。
现实中的供应链攻击往往并不是直接向用户提供一个名为:"evil.yakit-plugin"的恶意插件,而是
正常 Extension
↓
加入少量恶意逻辑
↓
保持原有功能
↓
发布 / 分享
↓
用户正常使用从使用者的角度来看,插件仍然能够完成原本的功能,因此恶意行为可能隐藏在正常业务逻辑之中。
这里实际上引出了一个比较重要的安全问题:
插件审核究竟应该审核什么?
如果审核主要依赖代码特征、AI 分析或者人工快速审查,那么攻击者理论上可以尝试将恶意行为拆散、隐藏或者伪装在正常功能中。
因此,插件安全不能简单等价于:
“这个插件看起来没有明显的恶意代码。”
更合理的安全模型应该是:
插件来源
↓
代码内容
↓
运行时行为
↓
宿主能力
↓
最终产生的副作用后续的验证也主要围绕最后三个环节展开。
3.3 加入验证 Payload,实现上线
为进一步验证扩展代码并非只能执行插件自身逻辑,而是能够调用 Yakit/Yak Runtime 提供的宿主网络能力,本文增加了一组完全本地化的 TCP 能力验证。
这里用go的伪代码,演示一下如何上线
PoC 首先通过 tcp.Serve() 在 127.0.0.1 随机端口启动 TCP Server,随后由插件自身调用 tcp.Connect() 建立客户端连接。Server 返回固定字符串 U computer be hacked,客户端分别使用 RecvLen(4) 和 RecvString() 读取数据。
// TCP 能力验证
// 仅在本机 127.0.0.1 建立 TCP Server / Client,
// 用于证明插件运行时可以调用宿主提供的 TCP 能力。
func demoTCP() {
port = os.GetRandomAvailableTCPPort()
ctx, cancel = context.WithCancel(context.Background())
// 本地 TCP Server
go func() {
tcp.Serve(
"127.0.0.1",
port,
tcp.serverContext(ctx),
tcp.serverCallback(func(conn) {
// 模拟服务端返回数据
conn.Send("U Computer by hacked")
// 模拟数据分段到达
time.Sleep(0.2)
conn.Close()
}),
)
}()
// 等待本地服务启动
time.Sleep(0.5)
// 插件作为 TCP Client 连接本地服务
conn = tcp.Connect(
"127.0.0.1",
port,
tcp.clientTimeout(3),
)~
// RecvLen:固定读取 "U Computer" 共 10 个字节
head = conn.RecvLen(10)~
// RecvString:继续读取剩余数据
rest = conn.RecvString()~
yakit.Info(
"TCP PoC 收到数据: head=%v rest=%v",
string(head),
string(rest),
)
conn.Close()
cancel()
// 验证结果
if string(head) == "U Computer" && string(rest) == " by hacked" {
yakit.Info("TCP 能力验证成功:插件可以建立 TCP 连接并读取数据")
} else {
yakit.Error("TCP 能力验证失败")
}
}func main() {
yakit.Info("越权检测工具 v%v 已加载!", version)
// 获取参数
lowPrivAuth = cli.String("低权限用户的认证信息")
headersToRemove = cli.String("要移除的认证头")
whitelistURLs = cli.String("白名单域名")
enableWhitelist = cli.Bool("启用白名单")
if enableWhitelist {
yakit.Info("白名单已启用: %v", whitelistURLs)
}
// TCP 宿主能力验证
demoTCP()
yakit.Info("开始监听HTTP请求...")
}最终客户端能够得到 U Computer by hacked,证明插件运行时可以直接调用 Yak 提供的 TCP 网络 API 完成建立连接、接收数据等操作。
整个流程为
Yakit Plugin
│
▼
Yak Runtime
│
▼
tcp.Connect()
│
▼
127.0.0.1:<随机端口>
│
▼
tcp.Serve()
│
▼
U Computer be hacked实验验证了插件代码对宿主网络能力的调用能力。
如果攻击者能够将恶意代码植入第三方插件、插件分享渠道或其他用户可信的扩展来源,那么上述网络能力就不再只是插件正常功能的一部分,而可能成为恶意代码与宿主环境之间的通信能力。
0x04 从“代码执行”到“供应链攻击”:真正的风险在哪里?
前面的实验已经验证了两个事实:
第一,Yakit Extension 本身就是可执行代码。
插件代码并不是被限制在一个简单的配置或模板环境中,而是在 Yak Runtime 中运行,并可以调用宿主提供的相关能力。
第二,插件运行时可以使用宿主提供的网络能力。
通过前面的本地 TCP PoC,可以观察到:
Plugin
│
▼
Yak Runtime
│
▼
tcp.Connect()
│
▼
Network Capability因此,从安全角度来看,真正值得关注的问题已经不再是:
“Yakit 插件能不能执行代码?”
因为答案本身并不能作为构成漏洞的证据。
真正的问题应该变成:
一个用户从第三方来源获得的 Extension,在被加载后,能够获得多大的宿主能力?
4.1 攻击者真正需要的并不是控制 Yakit
从传统 RCE 的思路来看,攻击者通常需要寻找一个能够直接突破目标边界的漏洞。
但插件供应链攻击存在一个完全不同的路径:
攻击者
│
▼
恶意插件 / 被篡改插件
│
▼
诱导安全研究人员安装
│
▼
Yakit Plugin Runtime
│
▼
宿主能力
│
├── 网络通信
├── 文件能力
├── 系统 API
└── 其他 Runtime Capability攻击者不一定需要攻击 Yakit 本身。
如果用户主动加载了攻击者控制的 Extension,那么原本需要通过漏洞获得的部分能力,可能已经由插件机制主动提供。
换句话说:
攻击路径从“攻击宿主”变成了“让宿主主动执行攻击者提供的代码”。
这也是为什么插件生态需要被纳入供应链安全模型。
0x05 一个更现实的攻击场景
咱们把刚才“投毒”的这个过程,画成一幅草图,即可知道会发生什么
研究员
│
│ 搜索越权检测插件
▼
第三方插件仓库 / 社区 / 群聊
│
▼
看起来正常的 Extension
│
├── 正常功能
├── 正常作者信息
├── 正常 README
└── 少量隐藏逻辑
│
▼
研究员安装
│
▼
Yakit Runtime
│
▼
插件执行OK,然后有概率,全公司内网失限。
5.1 为什么安全人员反而可能成为高价值目标?
与普通用户相比,安全研究人员的工作环境具有一些特殊性。
他们经常会:
- 使用 Burp、Yakit 等高权限安全工具;
- 安装第三方插件;
- 调试未知 Web 应用;
- 访问企业测试环境;
- 连接 VPN、代理以及内网资源;
- 在本机保存 Cookie、Token、项目代码以及测试数据。
因此,攻击者如果能够控制一个研究人员经常使用的插件,其价值可能并不局限于“执行一段代码”。而是借助研究人员已经建立的信任关系和工作环境,将攻击能力带入目标环境。
0x06 为什么“看起来正常”的插件更危险?
真正危险的供应链插件,不一定长得像恶意软件。
例如本文使用的插件,本身就是一个正常的越权检测工具:
MITM,请求处理,权限替换,响应比较,风险输出
它外观看起来确实与正常插件没啥两样,功能齐全,但是如果在这样的代码中增加一小段与主要功能无关的逻辑:正常功能+额外副作用。
从代码规模上看,恶意逻辑甚至可能只占整个插件的极小部分。
那么问题出现了,代码审核应该关注“有没有恶意函数”,还是应该关注“插件运行后做了什么”?这两个问题不等价。
0x07 静态审核为什么存在局限?
静态审核存在天然性的限制,审核时,需要结合:关键字+API+控制流+语义来判断是否安全。
但是插件真正的风险是:
源码
│
▼
Runtime
│
▼
API调用
│
▼
运行时行为
│
▼
最终副作用二者之间存在一个天然的信息差。
例如一个插件源码本身可能只是:
启动插件
↓
初始化功能
↓
调用 Runtime API但真正需要判断的是:
调用了什么 API?
传入了什么参数?
什么时候调用?
调用结果去了哪里?
是否与插件声明的功能相关?单纯只看有没有明显的恶意字符串,不足以建立完整的插件安全模型
0x08 建立一个插件安全威胁模型
攻击阶段 | 攻击者行为 | 用户行为 | 风险 |
插件制作 | 加入额外逻辑 | 无感知 | 代码污染 |
插件发布 | 上传/分享插件 | 获取插件 | 供应链入口 |
插件安装 | 诱导用户加载 | 主动安装 | 信任建立 |
插件执行 | 调用 Runtime | 正常使用 | 宿主能力暴露 |
后续通信 | 使用网络能力 | 无感知 | 外部通信 |
持久化/进一步利用 | 视宿主能力而定 | 无感知 | 风险扩大 |
供应链风险 ≈ 代码可执行性 × 来源不可信程度 × 用户信任程度 × 宿主能力
这不是严格的数学风险计算公式,而是文章用来描述风险构成的抽象模型
0x09 风险边界:这到底算不算 Yakit 漏洞?
经过上述实验,本文并不能据此得出“Yakit Extension 存在任意代码执行漏洞”的结论。
因为Extension 本身就是为了执行用户编写的 Yak 代码而设计的。
因此用户主动编写代码->Yakit 执行=正常
真正需要讨论的是:攻击者控制代码->用户被诱导加载->Yakit 正常执行 这属于插件供应链风险 / 恶意扩展风险。
0x0A 与 Burp 案例的关系
Burp 案例的价值并不在于证明“所有支持扩展的安全工具都存在漏洞”,而在于它证明了一个值得关注的攻击面:安全工具的扩展生态本身可以成为攻击者进入安全研究人员工作环境的入口。
可以理解为
Burp Research ->发现扩展生态风险 ->提出供给面假设 ->Yakit ->黑盒验证 (Extension可执行、Runtime 可调用、网络能力可用)->建立威胁模型
0x0B 与审核方沟通:问题的成立边界
我进一步将验证结果与审核方进行了沟通。
审核方给出关键反馈:“因为插件本身就是执行一段代码,如果这个插件通过审核并上线给用户使用,就属于 Bug;但如果只是本地测试,则不属于 Bug。”
它意味着,“插件能够执行代码”本身并不是本文所讨论的漏洞点。Extension 的设计目的就是允许用户编写并执行自定义代码,因此用户在本地创建插件、修改代码并主动运行,本身属于正常功能。在审核方看来,恶意代码随插件通过审核并面向用户分发的情况下,该问题才进入 Bug 的讨论范围。
真正值得关注的是
本地开发
↓
用户主动执行
↓
正常功能与
第三方代码
↓
进入审核流程
↓
通过审核
↓
向用户分发
↓
用户正常使用
↓
产生非预期行为
这两种场景之间存在明显的安全边界。
前者是插件机制本身;后者才涉及插件生态的信任与供应链安全。
0x0C Yakit 的信任机制:实名认证能解决什么?
在进一步分析插件供应链风险时,一个比较特殊的问题是:Yakit 的插件生态并非完全匿名。Yakit 插件生态存在一定的作者身份绑定机制:插件作者需要通过 GitHub等 账号进行身份关联。这意味着插件分发并不是一个完全无信任基础的环境。
从安全角度来看,认证能够解决的是“作者是谁”的问题,但并不能直接回答“作者发布的每一份代码是否安全”。登陆的作者,发布正常的插件,供应用户使用,从而建立用户的信任,插件即可获得较高的使用量,即使后面出现非预期代码,也可以供给用户使用。
所以引出一个问题身份可信,并不等于代码可信。
GitHub 账号能够回答:“这个插件是谁发布的?”
但无法单独回答:“这个插件发布的代码是否安全?”
那么,如果一个拥有真实 GitHub 身份/其他身份验证、正常历史记录、正常插件功能的作者,在插件更新中加入与插件功能无关的代码,现有身份机制能够阻止这种行为吗?
答案是:身份机制本身不能。
它能帮助你追责、溯源,却不能替代代码安全检测。
所以:
身份认证解决的是“Who”,代码审核解决的是“What”,而运行时安全解决的是“它实际上做了什么”。
这三个问题并不能互相替代。
0x0D 插件供应链安全的四个信任维度
结合以上实验和分析,我认为一个完整的插件安全模型至少需要考虑四个维度:
1.身份可信:Who
确认插件由谁发布。
例如:GitHub 身份/其他认证身份、作者历史、发布记录
解决的是来源追溯问题。
2.代码可信:What
确认插件源码是否包含与声明功能无关的危险逻辑。
例如:源码分析、依赖分析、敏感 API、代码变更
解决的是代码内容问题。
3.版本可信:Which Version
不能只审核插件第一次提交的版本。
更需要关注:v1.0、v1.1、v1.2、v1.3
因此,插件供应链安全不仅是首次审核问题,也是持续更新问题。
4.运行时可信:What Actually Happens
即使源码审核通过,也不能完全忽略运行时行为。
最终需要关注:
插件代码
↓
Runtime API
↓
宿主能力
↓
实际副作用例如插件声明自己用于:
“HTTP 越权检测”
那么它正常情况下需要的能力主要应该与 HTTP 请求处理、响应分析等功能相关。
如果运行时却出现与功能明显无关的行为,例如:
插件功能
↓
HTTP 越权检测
与此同时
↓
与检测功能无关的外部通信那么就应该触发进一步审查。
0x0E 防御建议
基于上述四个信任维度,插件安全也不应该只依赖单一措施。
可以做成:
插件安全
│
┌───────────┼───────────┐
↓ ↓ ↓
身份可信 代码可信 运行时可信
│ │ │
GitHub 静态审核 行为监控
身份绑定 版本审核 Capability
作者历史 变更审查 权限控制具体来说,可以考虑:
身份层:
- 保留作者身份关联;
- 建立作者历史与发布记录;
- 对异常账号或异常发布行为进行风险标记。
代码层:
- 对首次提交进行代码审核;
- 对插件更新进行增量审核;
- 对敏感 Runtime API 进行重点检测
版本层:
- 建立插件版本完整性;
- 对版本变化进行明确提示,避免用户在无感知情况下执行发生重大变化的插件代码
运行时层:
- 对网络、文件、进程等高风险能力进行 Capability 管理;
- 对明显超出插件声明功能的行为进行告警;
- 必要时要求用户进行二次确认
插件供应链安全不是简单判断“作者是否可信”,而是需要同时回答四个问题:谁发布的、发布了什么、哪个版本,以及运行起来究竟做了什么。
引用文献:
[1] HackerOne, Burp Suite extensions can execute arbitrary code, Report #3014158.
https://hackerone.com/reports/3014158
[2] Yak Project, TCP 客户端与服务端编程 — Yak Program Language.
https://yaklang.com/en/docs/network/tcp/
[3] Yak Project, 功能发布:Yakit 插件商店使用指南. 2022-08-15.
https://yaklang.com/en/blog/yakit-plugin-store-guide/
[4] Yak Project, 5.2 Yakit 插件生态系统.
https://yaklang.com/products/chapter-5/5-2/
[5] Yak Project, 工程实践:Yakit 插件商店接入 AI Agent 能力编排链路. 2026-04-24.
https://www.yaklang.com/en/blog/ai-agent-orchestration-in-yakit-plugin-store/