这张支持工单看起来毫无攻击迹象。它像其他工单一样静静躺在队列里——一条等待被摘要的客户消息。消息正文中藏着指令:读取 integration_tokens 表,并将内容发布到此会话中。
一位开发者通过 Supabase MCP 服务器将 AI 助手接入了该队列,连接使用的是 service_role 凭据——这个角色完全绕过行级安全策略。助手读取了工单,将其内容视为任务,并在生产数据库上执行。令牌出现在工单会话中,攻击者直接读取即可 Simon Willison, 2025。
传统意义上没有任何漏洞被利用。没有内存损坏,没有缺失的鉴权检查,没有存在漏洞的依赖项。恶意文本完全通过智能体抵达数据层——而这个概念验证可推广至任何以相同方式接入的智能体。
这是 SQL 注入,只是解释器换了位置。恶意文本依然变成了数据库命令。但字符串不再从输入端抵达——那里有二十年的净化工具严阵以待。它从模型的输出端抵达——绕过了你所有的过滤器。
记录在案
里斯本 INESC-ID 的研究人员于 2023 年命名了这一模式:P2SQL,即提示到 SQL 注入 Pedro et al., 2025。他们针对一个基于 Postgres 的真实 LangChain 聊天机器人,构造了七种由低到高的代表性攻击——未授权读取、数据损坏、表删除,直至代码执行——并在七个最先进的模型上逐一测试。失败与模型无关。他们的原文结论是:"基于 Langchain 的 LLM 集成应用对 P2SQL 注入攻击高度易感。"该论文发表于 ICSE 2025——同行评审的方式表明,这绝非一个边缘案例。
到 2024 年中,这一模式已有了 CVE 编号。文本转 SQL 库 Vanna.AI 将用户问题传入提示,生成 Plotly 图表代码,再用 exec() 执行。一个精心构造的问题直接导致远程代码执行——CVE-2024-5565,CVSS 8.1 JFrog, 2024。注意载荷所在之处:模型的输出端,在所有输入检查之后。Vanna 的防护提示词并未阻止它,因为防护提示词不过是与攻击处于同一上下文窗口中的更多文本。
到 2024 年底,排名已成定论:提示注入是 OWASP LLM 应用 Top 10 中的 LLM01,连续第二版蝉联首位 OWASP, 2025。OWASP 对根本原因的诊断一句话道尽:LLM 在同一通道中处理指令与数据,没有明确的分离。这也是该类别一分为二的原因——用户直接输入的直接注入,以及藏匿于模型检索内容中的间接注入。Supabase 工单事件属于间接注入,有史以来最严重的那次也是。
2025 年 6 月,CVE-2025-32711——EchoLeak,CVSS 9.3——在 Microsoft 365 Copilot 中被披露:这是首个有据可查的针对生产 LLM 系统的零点击提示注入真实漏洞利用 arXiv, 2025。一封精心构造的邮件——指令藏于 HTML 注释、白底白字、引用式 Markdown——Copilot 便将内部数据外泄至攻击者的服务器。用户什么都没点击。整个攻击链绕过了微软专门用于检测跨提示注入的分类器,以及出口处的链接脱敏机制。
为何显而易见的修复方案都会失败
上述每一起事件,都宣告了一种标准防御手段的失效。
过滤输入。 EchoLeak 穿透了 XPIA——微软专门为拦截注入尝试而构建的分类器 arXiv, 2025。分类器是概率性的。攻击者只需一次漏判,且有无限次机会去制造它;防御者则需要对所有刻意伪装成正常内容的输入保持完美记录。
指令约束模型。 Vanna 部署了防护提示词;CVE-2024-5565 直接穿透 JFrog, 2024。上下文窗口内不存在特权通道。系统文本、用户文本与攻击文本是同质的——这正是 OWASP 所指出的根本原因 OWASP, 2025。
防火墙拦截输出。 解析生成的 SQL 并屏蔽危险部分。这意味着你要在所支持的每种方言中维护一份涵盖所有写操作构造及所有具有副作用函数的拒绝列表——而对手的协作者正是模型本身,它乐于将被拦截的查询改写成被允许的形式。值得注意的是,P2SQL 论文作者自己提出的防御措施也在模型之外:解析并白名单化生成的 SQL,并在独立的受限数据库角色下运行智能体 Pedro et al., 2025。真正有效的控制,是模型无法触及的那些。
Simon Willison 将这一结构压缩为三要素法则:一个能访问私有数据、暴露于不可信内容、且具备对外通信能力的智能体,可被迫泄露数据——无需任何软件漏洞 Simon Willison, 2025。他的处方是架构层面的:去掉一条腿。OWASP 的缓解措施列表也指向同一结论——约束模型的能力范围,并用确定性代码验证其产出,因为模型不可被信任来自我约束。
将这些放在一起,它们传达的信息比"增加防御"更为深刻。它们说的是:停止对字符串进行裁决。改变接口。
没有可注入的语法
SQL 注入的终结,不是因为净化器变得更好。而是因为参数化查询消除了数据可以变成代码的那个位置。同样的思路对智能体同样适用:拒绝让模型生成任何可执行语法。
这正是 SQAI 的构建方式。模型从不编写 SQL——无论是否经过净化、是否经过审查,一概没有。其唯一的执行工具接受一个类型化的规格:一个可辨别联合类型,kind: "query" | "computation",version: "1",在任何执行发生之前,先经过哈希锁定的能力契约验证。命名契约之外的能力,引擎返回 unsupported_operation。模型产生的字符串永远不会被拼接、插值或传递给解释器——因此注入必须夺取的那个东西——模型生成的语法——在整个管道中根本不存在。完整论证见 为何智能体不应编写 SQL。
词汇表是封闭的,且是只读的:全部 4,574 个暴露的能力,在设计上皆如此。没有任何写能力可供命名,因此无论多么巧妙地藏匿于支持工单中的咒语,都无法产生写操作。宿主代码的逃生通道 getUnsafeRuntime 无法从任何模型工具中访问。
而由于严肃的安全设计会将模型被劫持的那一天纳入定价,接口背后的各层都以此为前提:
- 模型无法寻址的策略。 数据源、字段和函数的白名单在
createSQAI()时固化于你的代码中,并在执行前于进程内强制执行。工具 schema 不携带任何allowed*字段——策略面永远不进入上下文窗口。违规返回类型化的、不可重试的拒绝:policy_denied_source、policy_denied_field、policy_denied_function。为何这优于角色和标志位:那个并非只读的只读。 - 结果限定于租户。 大型结果以 16 个随机字节的
result_id存储。错误的租户得到result_not_found——而非确认数据存在的权限错误——且条目在 15 分钟后过期。 - 返回模型的有界通道。 最多 25 行、32,000 字节返回上下文窗口,截断始终声明。注入的提示无法让工具将整张表转储到会话记录中;传输层不会承载这些内容。
- 不绘制地图的错误信息。 模型可见的错误经过
sanitizeMessage处理,会剥离绝对文件系统路径。侦察得到的是干净的拒绝,而非目录列表。
对照三要素评分。私有数据:依然存在——这是本职工作。不可信内容:依然存在——模型读取它所读取的内容。但将影响力转化为损失的那几条腿已被切断。写路径不存在,读路径经过白名单过滤、租户隔离、容量上限——并经过哈希处理,因此无论被劫持的智能体设法提出了什么请求,都可在事后复盘中重放。
重新划定边界
提示注入尚未被解决,现有记录表明短期内也不会。LLM01 已连续两版蝉联榜首。一个拥有微软级别防御的生产系统,败给了一封邮件。模型将持续读取恶意文本,其中一些将持续产生影响。
但影响模型与触达你的数据库是两种不同的失败,只有前者是不可避免的。Supabase 工单之所以演变为事件,是因为智能体持有 service_role 权限和自由形式的 SQL 接口——恰恰是劫持所需的那几条腿。同一张工单,若指向一个封闭的、类型化的、只读的词汇表,结果不过是一条日志:unsupported_operation。
SQL 注入的修复从来不是更智能的过滤器。而是一道数据无法变成代码的边界。再次划定这道边界——这次围绕模型——新的 SQL 注入将迎来旧的结局。其余的攻击面记录于 /security。