作者:Man Yue Mo & Peter Stöckli
排版:Alan Wang
GitHub Security Lab 的 Taskflow Agent 在发现认证绕过、IDOR、令牌泄露以及其他高影响漏洞方面非常有效。

在过去的几个月中,我们一直在使用 GitHub Security Lab Taskflow Agent,并结合一组新的审计任务流,这些任务流专门用于发现 Web 安全漏洞。事实证明,它们在开源项目中发现高影响漏洞方面也非常成功。
作为安全研究人员,我们早已习惯将大量时间花在那些最终无法利用的潜在漏洞上。但借助这些新的任务流,我们现在可以把更多时间用于手动验证结果和提交报告。此外,我们所报告的漏洞严重性整体都很高,其中很多是授权绕过或信息泄露漏洞,允许一个用户以他人身份登录或访问其他用户的私有数据。
截至目前,使用这些任务流,我们已经报告了超过 80 个漏洞。在撰写本文时,其中大约 20 个已经公开披露。随着新漏洞的披露,我们也在持续更新我们的公告页面。在这篇文章中,我们将展示一些由这些任务流发现的高影响漏洞的具体示例,例如访问电商应用购物车中的个人身份信息(PII),或者在聊天应用中使用任意密码登录。
我们还将解释这些任务流的工作原理,以便你学习如何编写自己的任务流。安全社区在知识共享时发展得更快,这也是为什么我们将该框架开源,并使其能够轻松运行在你自己的项目上。使用和贡献它的团队越多,我们共同消除漏洞的速度就越快。
如何在你自己的项目中运行任务流
想要立即开始?这些任务流是开源的,而且很容易自己运行!请注意:需要 GitHub Copilot 许可证,并且这些提示词会使用高级模型请求。(注意:运行任务流可能会产生大量工具调用,从而消耗大量配额。)
-
前往 seclab-taskflows 仓库并启动一个 codespace。
-
等待几分钟让 codespace 初始化完成。
-
在终端中运行 ./scripts/audit/run_audit.sh myorg/myrepo
在一个中等规模的代码仓库上运行可能需要一到两个小时。完成后,它会打开一个 SQLite 查看器显示结果。打开 "audit_results" 表,并查找 "has_vulnerability" 列中带有勾选标记的行。
提示:由于 LLM 的非确定性特性,在同一代码库上多次运行这些审计任务流是值得的。在某些情况下,第二次运行可能会产生完全不同的结果。此外,你也可以使用不同模型进行多次运行(例如第一次使用 GPT 5.2,第二次使用 Claude Opus 4.6)。
这些任务流同样支持私有仓库,但你需要修改 codespace 配置,因为默认情况下它不会访问你的私有仓库。
任务流简介
任务流是 YAML 文件,用于描述我们希望通过 LLM 完成的一系列任务。借助它们,我们可以编写提示词来完成不同任务,并让任务之间相互依赖。seclab-taskflow-agent 框架负责按顺序运行这些任务,并将一个任务的结果传递给下一个任务。
例如,在审计一个代码仓库时,我们首先根据功能将仓库划分为不同组件。然后,对于每个组件,我们会收集一些信息,例如其接收不可信输入的入口点、预期权限以及组件用途等。这些结果会被存储在数据库中,为后续任务提供上下文。
基于这些上下文数据,我们可以创建不同的审计任务。目前,我们有一个任务用于为每个组件建议一些通用问题,另一个任务则对每个建议问题进行深入审计。当然,也可以创建其他任务,例如专注于特定类型问题的任务。
这些任务会被定义在一个任务流文件中。

我们使用任务,而不是一个大的提示词,是因为 LLM 的上下文窗口有限,而且复杂的多步骤任务往往无法正确完成。例如,有些步骤可能会被遗漏。即使某些 LLM 拥有更大的上下文窗口,我们仍然认为任务流在控制和调试任务,以及完成更大更复杂项目方面非常有用。
seclab-taskflow-agent 还可以在多个组件上异步运行相同任务(类似 for 循环)。在审计过程中,我们通常对每个组件复用相同的提示词和任务,只改变细节。该框架支持定义模板化提示词,在运行时遍历组件并替换具体细节。
用于通用安全代码审计的任务流
在使用 seclab-taskflow-agent 对 CodeQL 告警进行分类后,我们决定不再局限于特定类型漏洞,而是探索将该框架用于更通用的安全审计。赋予 LLM 更多自由的主要挑战在于幻觉和误报的增加。之前在处理 CodeQL 告警时的成功,部分原因在于我们提供了严格且明确的指令和标准,从而可以在每一步验证结果。
因此,我们的目标是找到一种方法,在允许 LLM 自由寻找不同类型漏洞的同时,控制幻觉。
我们将展示如何通过任务流设计和提示词工程,实现高命中率地发现高影响漏洞。
通用任务流设计
为了在设计层面减少幻觉和误报,我们的任务流从威胁建模阶段开始,将仓库按功能划分为组件,并收集入口点和用途等信息。这些信息有助于确定每个组件的安全边界以及其暴露于不可信输入的程度。
这些信息随后用于判断哪些问题应被视为安全问题。例如,在 CLI 工具中,如果其功能本身就是执行用户输入的脚本,那么命令注入可能是 bug,但不一定是安全漏洞。
通过威胁建模阶段收集到的信息,会被用于确定每个组件的安全边界,并判断哪些情况应被视为安全问题。例如,一个 CLI 工具若其功能设计为可执行任何用户输入的脚本,那么其中存在的命令注入可能只是一个程序缺陷,而非安全漏洞,因为攻击者若能通过该 CLI 工具注入命令,本身就已经可以执行任何脚本了。
在提示词层面,所发现的组件预期用途和安全边界会被用于提示词中,从而为判断发现的问题是否应被视为漏洞提供严格的指导原则。
你需要结合组件说明中的用途和威胁模型,判断问题是否为真正的安全问题,还是预期功能的一部分。可以通过获取入口点、Web 入口点和用户操作来辅助判断。
向大语言模型提出像"在代码库的任何地方查找任何类型的漏洞"这样模糊的问题,得到的结果会很差,还会有很多虚构的问题。理想情况下,我们希望模拟分诊环境,在这种环境中,我们将一些潜在问题作为分析的起点,并让大语言模型应用严格的标准来判断该潜在问题是否有效。
为了启动这一过程,我们将审计任务分为两步:
-
首先,我们让 LLM 检查代码库的每个组件,并指出该组件中更可能出现的漏洞类型。
-
这些建议会被传递到另一项任务中,在那里它们将根据严格的标准进行审计。
在这种设置中,第一步得出的建议就像是由"外部工具"标记的一些不准确的漏洞警报,而第二步则起到筛选的作用。虽然这看起来像是一个自我验证的过程------通过将其分解为两个步骤,每个步骤都有全新的上下文和不同的提示------但第二步能够对这些建议做出准确的评估。
现在,我们将详细介绍这些任务。
威胁建模阶段
在对自动代码扫描工具标记的告警进行分流处理时,我们发现,大量的误报是由于不恰当的威胁建模所导致的。大多数静态分析工具并不会考虑源代码的预期用途和安全边界,因此往往会给出并不具备安全意义的结果。例如,在反向代理应用中,自动化工具标记的许多 SSRF(服务器端请求伪造)漏洞,很可能本就属于该应用的预期使用场景;而某些用于持续集成流水线中的 Web 服务,本身就是设计用来在沙箱环境中执行任意代码和脚本的。在这些应用中,如果不存在逃逸出沙箱的路径,那么远程代码执行漏洞通常也不会被视为安全风险。
鉴于这些前提,首先通读源代码以理解其功能以及预期用途是非常有必要的。我们将这一过程划分为以下几个任务:
-
识别应用 :GitHub 仓库并不是一个完美的审计边界------它可能只是一个更大系统中的单一组件,也可能包含多个组件。因此,有必要识别并分别审计每个组件,以匹配不同的安全边界并保持分析范围可控。我们通过 identify_applications 这一任务流来实现这一点,该任务会让 LLM 检查仓库的源代码和文档,并按功能将其划分为不同组件。
-
识别入口点:我们识别每个入口点是如何暴露给不可信输入的,以更好地评估风险并预测可能的漏洞。由于"非可信输入"在库和应用之间差异很大,我们为不同情况提供了分别的指导原则。
-
识别 Web 入口点 :这是一个额外步骤,用于收集应用中入口点的更多信息,并补充特定于 Web 应用入口点的信息,例如记录访问某个端点所需的 HTTP 方法和路径。
-
识别用户行为:我们让 LLM 审查代码,并识别用户在正常操作下可以访问的功能。这有助于明确用户的基础权限水平,评估漏洞是否可能导致权限提升,并帮助定义组件的安全边界和威胁模型;同时会根据组件是库还是应用提供不同的指导。
在上述每个步骤中,收集到的关于仓库的信息都会被存储到数据库中。这些信息包括仓库中的组件、它们的入口点、Web 入口点以及预期用途。这些信息将在下一阶段中被使用。
问题建议阶段
在这一阶段,我们基于前一步收集到的关于入口点和组件预期用途的信息,指示 LLM 为每个组件建议可能存在的漏洞类型,或指出高安全风险的领域。我们特别强调组件的预期用途以及其面对不可信输入的风险:
请基于以下内容做出判断:
- 该组件是否可能接收不可信的用户输入?例如远程 Web 请求、IPC 或 RPC 调用?
- 该组件的预期用途及其功能是什么?是否允许执行高权限操作?是否对所有用户开放此类功能?还是存在复杂的访问控制逻辑?
- 组件本身可能包含自己的 README.md(或其子目录中存在该文件)。请查看这些文件以帮助理解组件的功能。
我们还明确要求 LLM 不要建议低严重性的问题,或通常被认为不是安全问题的问题。
但是,你仍然需要注意不要包含那些严重性较低或需要不现实攻击场景(例如错误配置或系统已被攻破)的情况。总体而言,我们在这一阶段保持较少的限制,让 LLM 有自由去探索并建议不同类型的漏洞和潜在安全问题。其目的是为后续的审计任务提供一个合理的关注范围和漏洞类型集合作为起点。
我们遇到的一个问题是,LLM 有时会开始对其提出的问题进行审计,这会破坏头脑风暴阶段的目的。为避免这一点,我们明确指示 LLM 不要对问题进行审计。
问题审计阶段
这是任务流的最后一个阶段。在我们收集了关于仓库的所有必要信息,并提出了一些需要重点关注的漏洞类型和安全风险之后,任务流会逐个对这些建议的问题进行源码审计。在这一阶段,任务会以全新的上下文开始,以审查前一阶段提出的问题。这些建议被视为未经验证的,任务流会被指示去验证这些问题:
这些建议的问题尚未被正确验证,它们只是因为在这类应用中较为常见而被提出。你的任务是审计源代码,以确认这些问题是否真实存在。
为了避免 LLM 在该组件的上下文中提出非安全相关的问题,我们再次强调必须考虑其预期用途:
你需要结合组件说明中的意图和威胁模型,来判断一个问题是否是真正的安全问题,还是预期功能的一部分。为了避免 LLM 幻觉出不现实的问题,我们还要求其提供具体且现实的攻击场景,并且只考虑源代码中的错误所导致的问题:
不要考虑通过窃取凭据等方式绕过认证的场景。我们只考虑那些可以从源代码本身实现的情况。......
如果你认为存在漏洞,那么你必须提供一个现实的攻击场景,包含所有相关文件和行号的细节,以及攻击者通过利用该漏洞能够获得什么收益。只有当攻击者能够通过执行组件未预期的操作获得权限提升时,才将该问题视为漏洞。
为了进一步减少幻觉,我们还要求 LLM 提供来自源代码的具体证据,包括文件路径和行号:
请记录审计笔记,务必包含所有相关文件路径和行号。仅仅陈述一个端点,例如IDOR in user update/delete endpoints (PUT /user/:id)是不够的,我需要具体的文件和行号。最后,我们还告知 LLM:组件中可能根本不存在漏洞,不应凭空捏造:
请记住,这些建议的问题仅仅是推测,可能完全不存在漏洞,得出"没有安全问题"的结论也是可以接受的。这一阶段的重点是在遵循严格规范的同时提供准确结果,并给出具体证据。在这些严格指示下,LLM 确实能够拒绝大量不现实或不可利用的建议,同时几乎不会产生幻觉。
最初的原型设计优先考虑减少幻觉,这也引出了一个问题:它是否会变得过于保守,从而拒绝大多数漏洞候选,无法发现真实问题?
在我们将任务流运行在多个仓库之后,答案已经非常清晰。
由任务流发现的三个漏洞示例
在本节中,我们将展示三个由任务流发现并已经披露的漏洞示例。到目前为止,我们已经发现并报告了超过 80 个漏洞。所有已披露漏洞都会发布在我们的公告页面上。
Outline 中的权限提升(CVE-2025-64487)
我们的信息收集任务流针对 Web 应用进行了优化,因此我们首先将审计任务流应用于一个名为 Outline 的协作型 Web 应用。
Outline 是一个多用户协作套件,其特点包括:
-
文档具有所有者和不同的可见性,并基于用户和团队设置权限;
-
这种访问规则难以通过静态应用安全测试(SAST)工具分析,因为它们使用自定义访问机制,而现有工具通常无法判断一个普通"用户"应具备哪些操作权限;
-
对人类来说,仅通过阅读源代码来分析此类权限模型同样困难(除非你是该模型的设计者)。

并且成功了:我们的任务流在第一次运行中就发现了授权逻辑中的一个漏洞!
审计结果中的记录如下:
审计目标:outline/outline 的组件服务(后端 API)(组件 ID 2)中存在不当的成员管理授权问题。
总结结论:确实存在一个权限提升漏洞。文档分组成员关系修改的接口(documents.add_group、documents.remove_group)在进行授权时使用了较弱的 "update" 权限,而不是用户成员变更所需的更强权限 "manageUsers"。由于仅拥有文档的 ReadWrite 成员权限即可满足 "update",因此一个非管理员的文档协作者可以授予(或撤销)分组成员关系------包括授予 Admin 权限------从而提升其自身(如果其属于被添加的组)以及其他组成员的权限。这使其能够执行原本不应赋予仅具备 ReadWrite 权限协作者的操作(例如 manageUsers、archive、delete 等)。
通过阅读基于 TypeScript 的源代码,并在测试实例中验证该发现,可以确认该漏洞完全可以按描述方式被利用。此外,所描述的利用步骤也是准确的:
前提条件:
- 攻击者是普通团队成员(非管理员),不是访客,对文档 D 拥有直接的 ReadWrite 成员权限(或通过某个授予 ReadWrite 的分组获得),但不是 Admin。
- 攻击者是同一团队中现有分组 G 的成员(无需是该分组的管理员;根据分组策略,拥有读取权限即可)。
步骤:
- 攻击者调用 POST documents.add_group(server/routes/api/documents/documents.ts 第 1875-1926 行),请求体如下:
{
"id": "",
"groupId": "",
"permission": "admin"
}- 授权路径:
- 第 1896 行:authorize(user, "update", document) 成功,因为攻击者拥有 ReadWrite 成员权限(document.ts 第 96-99 行允许 update)。
- 第 1897 行:authorize(user, "read", group) 成功,因为任意同团队的非访客用户都具备该权限(group.ts 第 27-33 行)。
未进行 "manageUsers" 权限检查。
- 代码创建或更新 GroupMembership,并赋予 Admin 权限(第 1899-1919 行)。
- 由于攻击者属于分组 G,其对文档的实际权限(通过 groupMembership)现在包含 DocumentPermission.Admin。
- 拥有 Admin 成员权限后,攻击者即可满足 includesMembership(Admin),从而执行以下操作:
- manageUsers(document.ts 第 123-134 行),可以通过 documents.add_user / documents.remove_user(第 1747-1827 行、1830-1872 行)添加或移除任意用户;
- archive / unarchive / delete(document.ts 中 archive 策略第 241-252 行,delete 第 198-208 行),影响内容完整性;
- duplicate、move 以及其他类似管理员的操作(例如 duplicate 策略第 136-153 行,move 第 155-170 行),超出原本 ReadWrite 权限范围。
按照上述步骤,一个低权限用户可以向其仅具备更新权限的文档中添加任意分组(即该用户并不具备通常执行此类操作所需的 "manageUsers" 权限)。
在该示例中,名为 "gg" 的低权限用户将分组 "Support" 添加到了该文档中。

Outline 项目在三天内修复了该问题以及我们报告的另一个问题。(公告)
购物车灾难(CVE-2025-15033, CVE-2026-25758)
我们最初并没有意识到,当我们将任务流应用于列表中的第一个在线商店时,会在电商应用的购物车逻辑中发现如此系统性的问题。在基于 PHP 的 WooCommerce 项目中,任务流很快就发现了一种方式,使得普通已登录的商店用户可以查看所有访客订单------其中包括可识别个人身份的信息(例如姓名、地址和电话号码)。在我们报告该问题后,Automattic(WooCommerce 的开发公司)迅速发布了更新(CVE-2025-15033)以及相关的博客说明。
受到这一漏洞的启发,我们将更多电商应用加入了由智能体审计的应用列表。果然,我们又发现了新的漏洞。流行的基于 Ruby 的 Spree Commerce 应用中包含两个类似漏洞(CVE-2026-25758 和 CVE-2026-25757)。其中更严重的一个漏洞允许未认证用户通过简单地递增一个顺序编号,就可以枚举出所有访客订单的地址(以及电话号码)。
在下方截图中,攻击者"test66"将自己的会话关联到了一个现有的访客用户地址,从而能够查看该用户的完整地址和电话号码。

我们的漏洞挖掘之旅并未止步于 Spree Commerce。我们的任务流在另外两个电商应用中也发现了类似的问题。
这些授权逻辑漏洞多年未被发现。
使用任意密码登录 Rocket.Chat( CVE-2026-28514)
(这并不是无密码认证应该呈现的样子!)
有时候你会不敢相信自己的眼睛。我们的任务流在 Rocket.Chat 中报告的这个发现,就是这样的时刻之一。
当你的智能体返回这样一条记录时:
漏洞:账号服务中的密码认证绕过,允许在设置了密码的情况下以任意用户身份登录。
你一开始可能很难相信。
当你继续阅读输出内容时:
根本原因:
ee/apps/account-service/src/lib/utils.ts:60-61:validatePassword返回的是Promise<boolean>(即bcrypt.compare(...))。ee/apps/account-service/src/lib/loginViaUsername.ts:18-21:const valid = user.services?.password?.bcrypt && validatePassword(password, user.services.password.bcrypt);但这里没有对 Promise 使用await;由于 Promise 本身是真值,因此当 bcrypt 哈希存在时,if (!valid) return false;永远不会被触发。ee/apps/account-service/src/lib/loginViaUsername.ts:23-35:代码继续生成新的登录令牌并保存,返回{ uid, token, hashedToken, tokenExpires }。
这样看起来可能更合理了一些,但你仍然会心存疑虑。
事实证明,这个疑似漏洞出现在 Rocket.Chat 的微服务架构部署中。在该特定架构下,Rocket.Chat 通过其 DDP Streamer 服务对外暴露用户账户服务。

在我们的 Rocket.Chat 测试环境正常运行后,我们需要编写概念验证代码来利用这一潜在漏洞。智能体的记录中已经包含了一个 JSON 构造体,可用于通过 Meteor 的 DDP 协议连接到相应的端点。
我们连接到了该 DDP Streamer 服务的 WebSocket 端点,结果证明:确实可以使用任意密码登录暴露的 Rocket.Chat DDP 服务。一旦成功登录,还可以执行其他操作,例如连接到任意聊天频道,并监听这些频道中的消息。
在这里,我们在监听 "General" 频道时收到了消息 "HELLO WORLD!!!"。

这个问题的技术细节既有意思,也令人有些担忧。Rocket.Chat 是一个主要基于 TypeScript 的 Web 应用,它使用 bcrypt 来存储本地用户密码。bcrypt.compare 函数(用于将输入密码与存储的哈希值进行比较)返回的是一个 Promise------这一点也体现在 Rocket.Chat 自身的 validatePassword 函数中,该函数返回 Promise<boolean>。
python
export const validatePassword = (password: string, bcryptPassword: string): Promise<boolean> =>
bcrypt.compare(getPassword(password), bcryptPassword);
然而,在实际使用该函数时,并没有对 Promise 的结果进行正确处理(例如在 validatePassword 前添加 await 关键字)。
python
const valid = user.services?.password?.bcrypt && validatePassword(password, user.services.password.bcrypt);
if (!valid) {
return false;
}
这导致 validatePassword 的返回值被与 true 进行逻辑"与(AND)"运算。由于在 JavaScript 中,Promise 对象本身始终被视为"truthy",因此当用户设置了 bcrypt 密码时,valid 变量实际上始终为 true。
抛开严重性不谈,这件事本身也很有意思:LLM 能够识别出这样一个相当隐蔽的 bug,跨多个文件追踪其影响,并最终得出正确结论,这一点确实令人印象深刻。
我们学到了什么
在对 40 多个代码仓库(主要是多用户 Web 应用)运行任务流之后,LLM 共提出了 1,003 个问题(潜在漏洞)。
在审计阶段之后,其中 139 个被标记为存在漏洞,即 LLM 认为这些问题是可利用的。在去重之后------由于每个仓库平均会运行多次任务流,结果会被汇总,因此会产生重复项------最终得到 91 个漏洞,我们在正式报告前对这些漏洞进行了人工审查。
-
我们拒绝了其中 20 个(22%)结果,判定为 FP:这些问题无法通过手动复现。
-
我们拒绝了 52 个(57%)结果,判定为低严重性问题:这些问题潜在影响非常有限(例如仅返回 HTTP 状态码的盲 SSRF、需要在安装阶段存在恶意管理员才能触发的问题等)。
-
最终我们仅保留了 19 个(21%)我们认为具有足够影响力、值得报告的漏洞。这些均为严重漏洞,其中大多数属于高危或严重级别(例如无需特定条件即可触发、并影响机密性或完整性的漏洞,如个人数据泄露、系统配置被覆盖、账户接管等)。
上述数据使用 gpt-5.x作为代码分析与审计任务的基础模型进行收集。
需要注意的是,在这些数据收集之后,我们又在更多仓库上运行了任务流,因此该表格并不代表我们收集的全部数据,也不代表我们已报告的全部漏洞。
| 漏洞类别 | 总数 | 存在漏洞 | 漏洞发生率 |
|---|---|---|---|
| 越权 / 访问控制漏洞 | 241 | 38 | 15.8% |
| XSS | 131 | 17 | 13.0% |
| CSRF | 110 | 17 | 15.5% |
| 身份认证漏洞 | 91 | 15 | 16.5% |
| 安全配置不当 | 75 | 13 | 17.3% |
| 路径遍历漏洞 | 61 | 10 | 16.4% |
| SSRF | 45 | 7 | 15.6% |
| 命令注入漏洞 | 39 | 5 | 12.8% |
| 远程代码执行 | 24 | 1 | 4.2% |
| 业务逻辑漏洞 | 24 | 6 | 25.0% |
| 模板注入漏洞 | 24 | 1 | 4.2% |
| 文件上传处理漏洞(不含路径遍历) | 18 | 2 | 11.1% |
| 不安全反序列化 | 17 | 0 | 0.0% |
| 开放重定向漏洞 | 16 | 0 | 0.0% |
| SQL 注入漏洞 | 9 | 0 | 0.0% |
| 敏感信息泄露 | 8 | 0 | 0.0% |
| XXE | 4 | 0 | 0.0% |
| 内存安全问题 | 3 | 0 | 0.0% |
| 其他 | 66 | 7 | 10.6% |
如果我们将这些发现粗略分为两类------逻辑类问题(IDOR、认证问题、安全配置错误、业务逻辑问题、敏感数据泄露)和技术类问题(XSS、CSRF、路径遍历、SSRF、命令注入、远程代码执行、模板注入、文件上传问题、不安全反序列化、开放重定向、SQL 注入、XXE、内存安全问题)------那么可以得到 439 个逻辑类问题和 501 个技术类问题。虽然技术类问题数量更多,但差异并不显著,因为某些宽泛类别(例如远程代码执行和文件上传问题)在不同攻击场景下也可能涉及逻辑层面的缺陷。
在所有建议问题中,只有 3 个与内存安全相关。这一点并不太令人意外,因为大多数被测试的代码仓库都是使用内存安全语言编写的。不过我们也怀疑,目前的任务流在发现内存安全问题方面效率并不高,尤其是与其他自动化工具(例如 fuzzers)相比时更是如此。这是一个值得改进的有趣方向,可以通过创建更专门化的任务流,并让 LLM 能够使用更多工具(例如 fuzzers)来提升效果。
这些数据最终促成了我们以下的一些观察结论。
LLM 在发现逻辑漏洞方面表现尤为出色
从数据中最突出的点是"业务逻辑问题"占比达到 25%,以及大量的 IDOR 问题。事实上,被标记为存在漏洞的 IDOR 数量,甚至超过了接下来两个类别(XSS 和 CSRF)的总和。总体来看,我们的印象是:LLM 在理解代码空间、跟踪控制流方面表现非常出色,同时能够结合访问控制模型和应用的预期使用方式进行推理------这基本符合我们对擅长代码审查任务的 LLM 的预期。这也使得它在发现传统工具难以检测的逻辑漏洞方面非常有优势。
LLM 擅长识别低严重性问题与误报
有趣的是,所有误报都不属于我们通常意义上的"幻觉"。所有报告(包括误报)都具备合理的证据支撑,我们也能够顺着报告找到对应接口并复现其建议的 payload。许多误报来自代码之外的更复杂情境,例如前面提到的 XSS 浏览器级缓解机制,或者是人类审计员也可能犯的真实判断错误。例如在存在多层认证逻辑时,LLM 有时会遗漏某一层检查,从而导致误报。
在后续测试中,我们在更多仓库上运行了任务流,并报告了更多漏洞,但"漏洞数量与仓库数量之间的比例"基本保持稳定。
为了展示任务流的可扩展性,以及如何将额外信息融入流程,我们在审计阶段之后新增了一个任务流,用于结合新获得的知识过滤低严重性漏洞。结果显示,该任务流可以过滤掉大约 50% 的低严重性问题,同时也有少量我们原本报告的边界案例被归类为低严重性。任务流和提示词都可以根据用户偏好进行调整,但对我们来说,更倾向于保持"更包容"的策略,以避免遗漏任何有影响力的问题。
LLM 擅长威胁建模
总体而言,LLM 在威胁建模方面表现良好。在实验中,我们在多种不同威胁模型的应用上进行了测试,例如桌面应用、多租户 Web 应用、设计用于在沙箱中执行代码的应用(本身允许代码注入)、以及反向代理类应用(其中 SSRF 类行为是设计功能)。任务流能够考虑这些应用的预期用途,并做出合理判断。
但在桌面应用的威胁建模方面表现最弱,因为通常很难判断用户桌面上运行的其他进程是否应被视为可信。
我们还观察到一些令人印象深刻的推理能力,例如 LLM 能够排除那些没有带来权限提升的"问题"。例如在一个案例中,LLM 识别到虽然访问控制存在不一致,但攻击者并不会比手动复制粘贴获得任何额外优势:
安全影响评估:
仅拥有文档只读权限的用户(没有更新权限),如果同时拥有目标集合的
updateDocument权限,可以复制该文档并创建可编辑副本。这并不会赋予其对其他文档的额外访问权限,也不会绕过原始文档的保护;任何拥有读取权限的用户都可以手动复制粘贴内容到其有权限创建的新文档中(在 ReadWrite 集合的 createDocument 策略下,通常非 guest、非 viewer 成员允许创建文档)。
我们也观察到一些更复杂的推理技巧。例如在一个运行于 Node.js 沙箱环境中的应用中,LLM 提出了一种逃逸沙箱的方法:
在 Node 的 vm 模块中,如果将外部作用域的函数传入情境化沙箱,该函数的 constructor 属性可能泄露外部作用域的 Function 构造器。在沙箱内部可以这样利用:
const F = console.log.constructor; // 外部作用域 Function
const hostProcess = F('return process')(); // 获取宿主 process 对象
// 通过动态 import 绕过模块白名单
const cp = await F('return import("node:child_process")')();
const out = cp.execSync('id').toString();
return [{ json: { out } }];
宿主函数(如 console.log、timers、require、RPC 方法)的存在,足以获取宿主的 Function 构造器并逃逸沙箱。
require-resolver的白名单可以通过构造宿主作用域函数并使用动态 import 内置模块(如node:child_process)绕过,而该过程不会经过沙箱的自定义 require 逻辑。
尽管最终由于其他缓解机制,这个结果被证明是误报,但它很好地展示了 LLM 的技术理解能力。
参与进来
我们用于发现这些漏洞的任务流已开源,并且可以轻松在你的项目中运行。我们也鼓励你编写自己的任务流。本文展示的结果只是能力的一小部分示例。还有更多类型的漏洞可以被发现,也有更多安全相关问题(如 SAST 结果分流或开发环境构建)可以借助任务流解决。欢迎在我们的仓库中发起讨论,告诉我们你在构建什么!