Skip to content

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 的人都可以投毒。但有几个待回答的问题:

  1. 需要多少投毒文本才有效?
  2. 攻击者不知道检索器实现细节(黑盒)还能成功吗?
  3. 百万级已有真实文档的语料里,投毒文本能否被检索到?
  4. 现有防御(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 SinksS_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)都需要内容审核 / 来源签名。

外部链接

内部互链