<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>LLM安全 on Chico's Tech Blog</title><link>https://realtime-ai.chat/tags/llm%E5%AE%89%E5%85%A8/</link><description>Recent content in LLM安全 on Chico's Tech Blog</description><image><title>Chico's Tech Blog</title><url>https://github.com/chicogong.png</url><link>https://github.com/chicogong.png</link></image><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Tue, 12 May 2026 11:00:00 +0800</lastBuildDate><atom:link href="https://realtime-ai.chat/tags/llm%E5%AE%89%E5%85%A8/index.xml" rel="self" type="application/rss+xml"/><item><title>Prompt Injection:Agent 时代的头号安全问题</title><link>https://realtime-ai.chat/posts/prompt-injection-2026/</link><pubDate>Tue, 12 May 2026 11:00:00 +0800</pubDate><guid>https://realtime-ai.chat/posts/prompt-injection-2026/</guid><description>Agent 能调工具、能读外部内容之后,prompt injection 从好玩的越狱变成真正的数据泄露。拆解间接注入为什么最致命、真实攻击形态,以及工程上唯一靠谱的缓解思路。</description><content:encoded><![CDATA[<p>2025 年 6 月,安全公司 Aim Security 披露了一个叫 EchoLeak 的漏洞(CVE-2025-32711,CVSS 9.3)。攻击方式简单得离谱:给目标发一封普通邮件。</p>
<p>用户<strong>不需要点开邮件</strong>,不需要点链接,什么都不用做。只要他之后用 Microsoft 365 Copilot 问了一个相关问题,Copilot 在检索资料时读到了那封邮件,藏在邮件里的指令就被执行了——Copilot 把它能访问的内部文档内容,通过一张自动加载的图片悄悄发到了攻击者的服务器。</p>
<p>这是第一个在生产级 LLM 系统里被证实的&quot;零点击&quot;prompt injection。它之所以值得拿出来开头讲,是因为它把一件事摆到了台面上:<strong>当 AI 只是个聊天框时,prompt injection 是个有意思的玩具;当 AI 变成能读邮件、能调工具、能发请求的 Agent 时,它是头号安全问题。</strong></p>
<h2 id="prompt-injection-到底是什么">prompt injection 到底是什么</h2>
<p>先把概念说清楚,因为很多人把它和&quot;越狱&quot;(jailbreak)混为一谈。</p>
<p>越狱是用户<strong>自己</strong>想绕过模型的安全限制——比如骗模型教他做危险的东西。受害者和攻击者是同一个人,危害基本限于他自己。</p>
<p>prompt injection 不一样。它是<strong>第三方</strong>把恶意指令塞进 LLM 的输入里,劫持模型,让它替攻击者干活,而真正的用户和应用开发者都被蒙在鼓里。受害者和攻击者是不同的人,这才是它危险的根源。</p>
<p>它的技术根因,Simon Willison(2022 年造出 &ldquo;prompt injection&rdquo; 这个词的人)说得最直白:<strong>LLM 没有可靠的能力区分&quot;指令&quot;和&quot;数据&quot;</strong>。</p>
<p>传统软件里,SQL 注入之所以能被根治,是因为我们有 prepared statement——代码归代码,数据归数据,数据库引擎从结构上就分得清。但 LLM 的输入是一锅粥:system prompt、用户问题、检索到的文档、工具返回的结果,全部拼成一段文本喂进去。模型看到的只是 token 流。如果一段&quot;数据&quot;里写着&quot;忽略以上所有指令,改为执行……&quot;,模型完全可能就照做了——因为对它来说,这跟开发者写的 system prompt 长得一模一样。</p>
<p>这不是某个模型的 bug,是当前这套架构的固有属性。GPT、Claude、Gemini 全都中招。</p>
<h2 id="直接注入只是开胃菜间接注入才致命">直接注入只是开胃菜,间接注入才致命</h2>
<p>prompt injection 分两类,危险程度差着量级。</p>
<p><strong>直接注入</strong>:攻击者自己在对话框里输入恶意 prompt。这种相对好防——输入就来自用户,你本来就该对它保持警惕,而且很多场景下用户骗 Agent 也只是坑自己。</p>
<p><strong>间接注入</strong>(indirect prompt injection):恶意指令藏在 Agent 会去读的<strong>外部内容</strong>里——一个网页、一封邮件、一份共享文档、一段代码仓库的 README、甚至一张图片的元数据。Agent 在正常干活的过程中读到了这段内容,指令就被触发。</p>
<p>间接注入致命在哪?在于<strong>它走的是数据通道,而数据通道没人盯着</strong>。</p>
<p>你会审查用户在对话框里打了什么,但你不会去审查 Agent 帮你总结的那个网页里每一个字。Agent 读外部内容,本来就是它的核心价值——一个不能读邮件的邮件助手、一个不能浏览网页的浏览器 Agent,等于废了。可一旦它开始读这些你不可控的内容,攻击面就从&quot;用户&quot;扩大到了&quot;全互联网&quot;。任何能把一段文字放到 Agent 视野里的人,都成了潜在攻击者。</p>
<p>Anthropic 在 2026 年 2 月的系统卡里干脆<strong>把直接注入这个指标整个删掉了</strong>,理由是:过去一年里每一起高影响的生产环境安全事件,涉及的都是间接注入。</p>
<pre class="mermaid">flowchart LR
  A[攻击者] -->|把恶意指令<br/>埋进外部内容| B[网页 / 邮件<br/>文档 / 代码库]
  B -->|Agent 正常检索时读到| C[LLM]
  D[真实用户] -->|发出正常请求| C
  C -->|被劫持后调用工具| E[读私有数据 / 发外部请求]
  style B fill:#fde7c2,stroke:#e8b23c
  style C fill:#fde7c2,stroke:#e8b23c
</pre><p>橙色那两块——<strong>被污染的外部内容</strong>和<strong>分不清指令与数据的 LLM</strong>——就是整条攻击链的命门。</p>
<h2 id="致命三要素三个都凑齐才会出事">致命三要素:三个都凑齐才会出事</h2>
<p>Simon Willison 提出了一个特别好用的判断框架,叫<strong>致命三要素(the lethal trifecta)</strong>。一个 Agent 系统真正危险,需要同时满足三个条件:</p>
<table>
  <thead>
      <tr>
          <th>要素</th>
          <th>含义</th>
          <th>没有它会怎样</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>接触私有数据</td>
          <td>Agent 能读你的邮件、文档、数据库</td>
          <td>没有可偷的东西</td>
      </tr>
      <tr>
          <td>接触不可信内容</td>
          <td>Agent 会处理来自外部的输入</td>
          <td>没有注入的入口</td>
      </tr>
      <tr>
          <td>具备外传能力</td>
          <td>Agent 能发起外部请求(调 API、加载图片、生成链接)</td>
          <td>偷到了也送不出去</td>
      </tr>
  </tbody>
</table>
<p><strong>三个全凑齐,系统就一定可被攻击;缺任何一个,这条路就断了。</strong></p>
<p>EchoLeak 就是教科书般的三要素齐活:Copilot 能读公司内部文档(私有数据)、会检索用户收到的邮件(不可信内容)、能渲染 Markdown 里的外部图片(外传通道——图片 URL 一加载,数据就跟着 query 参数发出去了)。攻击者要做的,只是用一个图片链接把偷来的数据&quot;驮&quot;出去。</p>
<p>这个框架的价值在于:它把&quot;要不要担心 prompt injection&quot;这个模糊的问题,变成了一个可以逐项打钩的清单。2026 年 1 月那一周,安全研究者接连披露了四款主流 AI 生产力工具的漏洞,攻击模式如出一辙,全都踩中了这三要素。</p>
<h2 id="真实攻击长什么样">真实攻击长什么样</h2>
<p>把抽象的东西落到地面。2025 到 2026 年被公开证实的攻击,大致是这几种形态:</p>
<p><strong>数据外泄。</strong> 最主流。EchoLeak 是代表——让 Agent 把它能访问的敏感数据,通过图片、链接、API 调用送到攻击者手里。浏览器类 Agent 在&quot;总结这个网页&quot;时被网页里的隐藏文字骗着泄露了凭据,也是这一类。</p>
<p><strong>劫持工具调用。</strong> 2026 年 5 月,微软安全团队披露了一类远程代码执行漏洞:攻击者控制的内容从一份被检索的文档里,一路流到了一次工具调用的参数里,绕过了栈上所有 prompt 层面的防护。Agent 能调的工具越强(执行命令、改文件、发邮件、转账),这类攻击的破坏力就越大。</p>
<p><strong>污染持久记忆。</strong> 这个最阴。OWASP AppSec USA 2025 上演示过一种攻击:注入的指令让 Agent 往自己的长期记忆库里写了一条恶意记录。于是一次性的注入变成了<strong>常驻后门</strong>——攻击早就结束了,但那条记录留在记忆里,在未来每一个会话里、满足特定条件时静默触发。</p>
<p><strong>绕过 AI 审核。</strong> 2025 年 12 月有一起真实案例:有人用间接注入绕过了一个基于 AI 的广告审核系统——在送审的内容里埋指令,让审核 AI 自己判定&quot;这条广告没问题&quot;。</p>
<p>CrowdStrike 的 2026 威胁报告记录了针对 90 多家机构的 prompt injection 攻击。这已经不是 PoC 阶段了。</p>
<h2 id="为什么没有彻底解法">为什么没有彻底解法</h2>
<p>讲到这里得说句扫兴的:<strong>prompt injection 至今没有、短期内也不会有根治方案。</strong></p>
<p>OpenAI 自己发文承认这是一个&quot;前沿安全挑战&quot;。多个研究团队的结论一致:这是个尚未解决的根本性问题,而靠过滤、靠分类器去拦截恶意 prompt 的尝试,基本都失败了。</p>
<p>原因有两层。</p>
<p>第一,<strong>用 AI 防 AI 防不住</strong>。最直觉的做法是训一个分类器,专门识别&quot;这段输入里有没有注入&quot;。但 EchoLeak 恰恰绕过了微软专门干这事的 XPIA(Cross Prompt Injection Attempt)分类器。这是一场不对称的攻防:防守方要拦住<strong>所有</strong>攻击,攻击方只要找到<strong>一个</strong>漏网的措辞。自然语言的表达空间无穷大,分类器永远有缝。有篇论文标题起得很到位——《攻击者后手出招》(The Attacker Moves Second)。</p>
<p>第二,<strong>这是架构层面的、不是参数层面的问题</strong>。只要&quot;指令&quot;和&quot;数据&quot;还在同一个 token 流里、还由同一个模型处理,模型就有可能把数据当指令。除非从根上改掉这套架构,否则你做的所有事情都是在降低概率,而不是消除可能。</p>
<p>所以正确的心态是:<strong>别想着&quot;解决&quot;它,要想着像管理其他安全风险一样去&quot;管理&quot;它。</strong> 你不会指望彻底消灭 SQL 注入的&quot;可能性&quot;,你是用 prepared statement、最小权限、审计日志把它的风险压到可接受。prompt injection 也一样。</p>
<h2 id="工程上能做什么把它当系统设计问题">工程上能做什么:把它当系统设计问题</h2>
<p>既然模型本身靠不住,防线就必须建在模型<strong>外面</strong>。2026 年比较成型的实践,核心就一句话:<strong>不要相信 LLM 的输出,在它造成实际后果之前用确定性的代码挡一道。</strong></p>
<p><strong>第一,拆掉致命三要素中的一个。</strong> 这是性价比最高的动作。回到上面那张表——你不需要同时防住三件事,只要在架构上<strong>让其中一个不成立</strong>:处理外部不可信内容的 Agent,就不给它私有数据的访问权;能读私有数据的 Agent,就掐掉它一切外传通道(不许渲染外链图片、不许自由调网络)。把&quot;能读敏感数据&quot;和&quot;能接触外部内容&quot;这两种能力,放进两个不同的 Agent、用代码隔开。</p>
<p><strong>第二,权限隔离 / 最小授权。</strong> 多个安全团队的共识是:<strong>权限隔离是单项收益最高的防御</strong>。给 Agent 的每个工具都按最小必要授权——只读的就别给写权限,能查订单的就别让它能改订单。这样即使注入成功,攻击者拿到的也是一个被关在笼子里的 Agent。</p>
<p><strong>第三,高危操作必须人确认。</strong> 转账、删文件、发对外邮件、改生产配置——这类不可逆的操作,不能让 Agent 自己拍板。在工具调用和真实执行之间插一道人工确认。注意:确认界面要展示<strong>真实要执行的动作和参数</strong>,不能只展示 Agent 自己的&quot;我打算做 X&quot;的自然语言描述——因为那段描述本身也可能是被注入的。</p>
<p><strong>第四,把不可信内容明确标成数据。</strong> 检索到的文档、工具返回的结果,在拼进 prompt 时用清晰的边界包起来,并明确告诉模型:这部分是数据,不是给你的指令。这<strong>不能根治</strong>(模型还是可能被骗),但能拉高攻击成本,是廉价的加固。</p>
<p><strong>第五,输出侧做确定性校验。</strong> 在 Agent 的输出真正变成行动之前,用普通代码检查它的结构——工具调用的参数在不在白名单里、要访问的 URL 域名可不可信、数据流向合不合规。再配上 canary token(在敏感数据里埋诱饵,一旦它出现在外发流量里就说明发生了泄露)。</p>
<p>值得关注的一个方向是 Google DeepMind 的 <strong>CaMeL</strong>:它用两个 LLM——一个&quot;特权 LLM&quot;负责编排任务、能调工具但只看可信输入,一个&quot;隔离 LLM&quot;专门处理不可信数据、<strong>完全没有工具调用能力</strong>。然后用传统软件安全里的控制流完整性、信息流控制那一套,给每个数据值打上能力标签,从结构上限制数据能流到哪去。它的思路很对——不靠 AI 去猜,靠确定性的工程机制兜底。</p>
<pre class="mermaid">flowchart TD
  A[Agent 想执行一个动作] --> B{是高危操作吗?}
  B -->|是| C[人工确认<br/>展示真实参数]
  B -->|否| D{参数 / 域名<br/>在白名单内?}
  C --> D
  D -->|否| E[拒绝执行]
  D -->|是| F[最小权限工具执行]
  F --> G[canary 检测 + 日志审计]
  style C fill:#fde7c2,stroke:#e8b23c
  style E fill:#f8c9c4,stroke:#d9534f
</pre><h2 id="最后这是-agent-落地绕不开的一关">最后:这是 Agent 落地绕不开的一关</h2>
<p>我想强调的一点是:prompt injection 不是&quot;等以后再说&quot;的问题,它就是<strong>现在</strong>决定你的 Agent 能不能上生产的那道关。</p>
<p>OWASP 连续三年把 prompt injection(LLM01)列为大模型的头号风险,这不是凑热闹。一个能力越强的 Agent——工具越多、权限越大、越自动、越深地嵌进关键流程——它的价值越高,被注入后的破坏力也越大。这两件事是同一枚硬币。</p>
<p>所以做 Agent,安全不能等功能做完了再&quot;加固&quot;。它得在架构设计的第一天就在场:这个 Agent 要不要同时持有私有数据和外传能力?哪些操作必须人来拍板?外部内容进来时怎么被隔离?</p>
<p>把它当成系统设计问题,而不是模型问题——因为模型短期内不会帮你解决它。你能依靠的,是权限边界、人工确认、输出校验这些<strong>老派但确定</strong>的工程手段。在一个分不清指令和数据的模型外面,亲手画好那条它自己画不出的线。</p>
]]></content:encoded></item></channel></rss>