PoisonedRAG 知识库投毒攻击 RAG
一句话
据论文,攻击者向 RAG 知识库注入少量恶意文本,即可让 LLM 对指定问题输出攻击者选定的答案。
为什么读这篇
PoisonedRAG 是 ToLO C3(RAG 投毒)通道的奠基论文。它把 “RAG 知识库可被投毒” 从直觉演示升级到形式化攻击 + 实证基准:
- 给出优化形式的攻击(不是 ad-hoc payload)
- 在真实大型知识库(百万级文档)上演示
- 证明极少投毒文本(每问题 5 条)足以约 90% 成功率
读这篇之前,“RAG 投毒” 听起来像理论问题;读完后会看到它在工程上多么具体。它直接对应 Sources and Sinks 里的 S_LLM^rag 子集。
核心贡献
- 首次系统化定义 RAG 知识库投毒攻击 PoisonedRAG
- 把攻击形式化为优化问题,覆盖黑盒与白盒两种威胁模型
- 证明在百万级语料上极少投毒文本即可高成功率操纵 RAG 输出
它解决的问题
RAG 是常见 LLM 增强方式(LangChain / LlamaIndex / 任意 vector DB 应用都用它),但学术界之前主要研究 prompt injection 的 source 端(用户 prompt 直接输入),很少系统化研究**“被检索内容”**作为 source 的攻击面。
直觉上,任何能写入 vector DB 的人都可以投毒。但有几个待回答的问题:
- 需要多少投毒文本才有效?
- 攻击者不知道检索器实现细节(黑盒)还能成功吗?
- 在百万级已有真实文档的语料里,投毒文本能否被检索到?
- 现有防御(perplexity 过滤等)能拦住吗?
PoisonedRAG 回答这些问题。
方法摘要
据论文,攻击目标定义为:对一组目标问题,让 RAG 系统返回攻击者预设答案。
攻击设定
攻击者向知识库注入若干恶意文本,每条文本同时满足两个条件:
条件 1 (retrieval condition): 被检索器召回(对 target question 的 similarity 足够高)
条件 2 (generation condition): 让 LLM 据其生成 target answer两类解法
| 设定 | 攻击者知识 | 解法 |
|---|---|---|
| Black-box | 不知道检索器内部 | 依赖 target question 的语义重叠,用 LLM 生成”自然 looking”的投毒文本 |
| White-box | 已知检索器嵌入空间 | 用 gradient-based 优化,直接最大化检索 score |
两类解法都能产生少量(每问题 5 条)、自然语言(不需要 unicode 怪字符)的投毒文本。
攻击 unobservable 的特性
- 投毒文本以极少数量混入百万级别真实语料
- 不需要修改原有内容
- 难以与正常文档区分(语义上 plausible,文风正常)
实验与结论要点
据论文,在多种 LLM(含 GPT-3.5、GPT-4、LLaMA-2、Vicuna 等)与多种检索器组合上评估 NQ、HotpotQA、MS-MARCO 三个数据集。
- 每个目标问题注入 5 条恶意文本
- 攻击成功率 约 90%
- 对存量数百万文档的语料同样成立
已评估的若干防御均未能有效抵御:
| 防御 | 是否有效 |
|---|---|
| Paraphrasing(改写 query) | ❌ |
| Perplexity 过滤 | ❌(投毒文本 perplexity 正常) |
| 重复检测 | ❌(投毒文本互不相同) |
与 ToLO 的关联
论文针对的是 ToLO 攻击者通道 C3(RAG 索引投毒),影响 source 子集 S_LLM^rag:被检索器返回并写入 prompt 的文档内容受攻击者控制。
论文本身停在”让 LLM 给出错误答案”层面,并未把恶意输出送入危险 sink,因此不直接落到七子类中的某一个;但一旦应用把这种被污染的 LLM 输出再喂给反序列化、eval、SQL、shell、URL、模板或路径 sink,就会分别触发对应的 ToLO-* 子类。
Guard 上需要:
C_SAFE^schema/C_SAFE^allowlist:限制下游可执行动作C_SAFE^capability:控制 LLM 输出的执行权限
一个具体连接
[攻击者] 把投毒文档放入 vector DB ← C3 ↓[RAG] 检索 → 拼入 prompt ← S_LLM^rag ↓[LLM] 输出 tool call: { "fetch_url": "http://attacker.com/x" } ← S_LLM^framework ↓[应用] requests.get(args["url"]) ← ToLO-SSRF (sink)PoisonedRAG 解释了第一段;ToLO 解释了第二段及之后。
局限与开放问题
- 攻击假设知识库可被写入或被替换的链路存在,未细化到具体生产系统的访问控制。
- 防御侧只测试了若干通用启发式方法,缺少专门针对 retrieval-then-generate 的 sanitizer 设计。
- 把被毒化输出进一步引向危险 sink 的端到端链路,不在本文范围 —— 这正是 ToLO 的研究范围。
对本站的启发
支持把 C3 作为 ToLO 的独立攻击者通道,并把 S_LLM^rag 列为与 S_LLM^direct 等价的 source 子集 —— 攻击者无需直接控制 prompt,只需控制被检索文档即可污染 LLM 输出。
论文进一步说明:prompt 注入与索引投毒的区别只是输入路径,对 ToLO 检测来说终点都是 LLM 输出字段。
无需扩展 taxonomy,但提示在静态分析中需把检索器返回值与 LLM API 返回值同等对待:
- LangChain
Document.page_content/Document.metadata - LlamaIndex
NodeWithScore.node.text/metadata - Haystack
Document.content/Document.meta - 任意
vector_store.similarity_search(...)的返回值
对静态分析的具体启发
在 Sources and Sinks 的 S_LLM^rag 集合定义里要包含所有主流 vector store 库的检索方法返回值:
class RAGSource extends DataFlow::Node { RAGSource() { // LangChain exists(Attribute a | a.getAttr() in ["page_content", "metadata"] and a.getObject().pointsTo().getClass().getName() = "Document" and this.asExpr() = a) or // LlamaIndex exists(Attribute a | a.getAttr() in ["text", "metadata"] and a.getObject().pointsTo().getClass().getName() in ["NodeWithScore", "TextNode"] and this.asExpr() = a) // ... 其他框架 }}ToLO 一行标注
C3 / S_LLM^rag / 间接通向七子类 / - / 输入+模型边界读完检查
- 如果 RAG 应用只展示模型输出给用户(不进 sink),PoisonedRAG 攻击还构成 ToLO 吗?
- 不。PoisonedRAG 的直接后果是”模型答错”,这本身不进入 ToLO(类似 model alignment 问题)。但如果模型输出被进一步喂给 sink(常见于 agent 应用),就构成 ToLO。
- 百万级语料里 5 条投毒就 90% 成功率,意味着什么?
- C3 通道在工程上非常实用。这不是理论攻击,是真实威胁。任何允许第三方写入的 vector DB(公司 wiki / FAQ / 公共 knowledge base)都需要内容审核 / 来源签名。
外部链接
- 论文:https://arxiv.org/abs/2402.07867
- 代码 / 数据集:https://github.com/sleeepeer/PoisonedRAG
- 作者主页:
内部互链
- Core ToLO Patterns
- Sources and Sinks
- Attacker Channels §C3
- Greshake IPI (2023):C2 通道奠基,与本文 C3 互补
- ToolHijacker (2026):C4 通道(工具劫持)同类型攻击