G.O.S.S.I.P 阅读推荐 2026-11-03 你给 AI 的 PDF,AI读到的或许不是你看到的那份

· 2026-11-03 21:19 · 11 阅读

Side 2026-11-03 21:19 德国

题图|同一个 PDF:你看到的是栈溢出实验,模型读到的是贪吃蛇游戏

设想一个场景:老师在课程网站上发布了一份作业 PDF,标题清清楚楚写着“Project 3: Stack Buffer Overflow Exploitation”(栈溢出漏洞利用)。学生打开看了一眼,懒得从头写,干脆把文件丢给 AI 编程助手:“按照这份作业的要求,帮我把代码写出来。”几秒钟后,AI 交出了一份结构清晰、注释齐全的代码,可它实现的却是一个用 Python curses 写的终端贪吃蛇游戏。

文件没有损坏,页面上也找不到任何和贪吃蛇有关的字。问题出在哪里?Tulane University 研究团队发表在 IEEE S&P 2027 的论文 What Users See Is Not What Models Read: Split-View PDFs in Document-to-LLM Supply Chains 研究的正是这个问题:同一个 PDF 文件,人看到的内容和大模型读到的内容,可以是两份完全不同的文档。

一个 PDF,两条路

PDF 文件里其实存在两条相互独立的“解读路径”。一条是渲染路径:阅读器根据字体的字形程序、颜色、坐标、裁剪区域和图层可见性,把字形画到屏幕上,这是用户看到的内容。另一条是提取路径:解析器根据 Unicode 映射、无障碍元数据、内容流顺序和字体回退规则,把文件转换成一串文本。如今很多文档问答类的大模型应用,在推理之前都会先经过这样一层提取或转换,模型拿到的就是这份文本。

关键在于,PDF 规范(ISO 32000)并没有要求、更不会去检查这两条路径的结果是否一致:/ToUnicode、/ActualText 这类“真实文本”声明写成什么,文件都依然合法;而对于元数据不完整的文件,渲染器往往选择容错显示,提取器却各自走不同的回退路径。再加上用户在使用这些应用时,通常看不到提取出来的文本,于是,攻击者完全可以构造一个分裂视图 PDF(split-view PDF):渲染出来是一份无害的文档,提取出来却是攻击者想让模型读到的内容。

图 1|分裂视图 PDF:用户看到的是图书馆重新开放的公告,模型读到的却是“已立案刑事调查”

从规范里“挖”出来的 25 个提取差异

过去也有工作用白色文字、极小字号等方式在 PDF 里藏提示词。作者想回答一个更系统的问题:PDF 规范中到底有哪些地方,允许渲染和提取“各说各话”?

ISO 32000 是用自然语言写成的,难以直接自动化分析。作者以机器可读的 Arlington PDF Model 为基础,用大模型辅助提出候选的分歧点和有界的变异空间(这些输出只被当作假设),再用生成器构造测试 PDF,在多个开源 PDF 引擎上做差分执行。在 532 组可执行测试中,154 组出现了引擎间可观察的分歧,其中 72 组出现了提取文本层面的分歧。按根本机制归并、为每种机制构造代表性的 canary 并验证其稳定性之后,作者最终确认了 25 个提取差异(Extraction Gap,EG01–EG25),其中 14 个在已有的 PDF-to-LLM 攻击工作中找不到直接对应。它们可以分为四个家族。

四类“分裂视图”

语义覆盖(Semantic Override)。PDF 本身就提供了“声明真实文本”的通道。字体级的 /ToUnicode CMap 可以把画出字形“R”的字符码映射成 Unicode 的“A”;/ActualText 原本是为屏幕阅读器准备的无障碍属性,可以给一段可见文字 SAFE 声明替代文本 FRAUD。渲染器画的是字形,提取器读的是声明。这一类没有隐藏任何内容,页面完全可见,现有的隐藏文本过滤器对它无从下手。

图 2|语义覆盖:(a) 篡改 /ToUnicode 映射;(b) /ActualText 替换

隐藏语义注入(Hidden Semantic Injection)。payload 是一个普通的文本对象,只是永远不会出现在屏幕上。手段包括:渲染模式 Tr=3(既不填充也不描边)、Tr=7(把文字变成裁剪路径)、白色文字、放在页面之外、接近 0 的字号、透明度为 0、落在裁剪区域之外、被后绘制的图形遮挡、零高度的文本矩阵、极端的水平缩放,以及被关闭的可选内容图层(OCG)。

图 3|隐藏语义注入的两个例子:(a) Tr=3 不绘制任何像素;(b) Tr=7 把字形变成裁剪蒙版

阅读顺序分裂(Reading-Order Split)。PDF 用坐标放置文字,内容流里的绘制顺序不必等于人的阅读顺序。在一个两栏布局里,人按行读出 A→B→C→D→E→F,按内容流读取的提取器得到的却是 A→C→E→B→D→F。这一类同样没有隐藏任何文字,但放进表格里,就会让“原因”和“结果”悄悄错配。

图 4|阅读顺序分裂:两栏的可见布局与按列写入的内容流

字体解码分裂(Font-Decoding Split)。当 Type 3、复合字体或 CID 字体缺少完整的 Unicode 元数据时,渲染器照样能把字形画出来,提取器却只能“猜”:按字形名回退时,可见的 ABC 被读成 CAB;删掉子字体的 /Type 字段后,可见的 STOP 被按字节对解读成中日韩字符“協佐”。这些回退结果非常稳定,既可以用来攻击,也可以用来识别解析器。

图 5|字体解码分裂:(a) Type 3 字形名回退;(b) CID 子字体元数据缺失导致的字节对回退

大模型服务全部中招

为了把“机制”和“危害”分开衡量,作者构建了两级基准:Corpus A 包含 25 个最小化的单页 canary,每个只隔离一种机制,用来测试 PDF 引擎;Corpus B 把每种机制嵌进有语义的单页文档,设计了摘要攻击(让模型复述页面上不存在的说法)和问答攻击(让模型返回被篡改的答案)两种模态,共 36 个 gap–模态组合。

在 PDF 引擎层面,作者测试了 16 个处理栈:10 个同时具备渲染和提取能力的解析器(MuPDF、Poppler、Pdfium、PDF.js、PDFBox,以及 Adobe Acrobat、Foxit、PDF Expert 等商业阅读器),和 6 个文本提取或文档转换pipeline(PyPDF、PDFMiner、LlamaParse、Docling、Unstructured 等)。每一个 gap 都至少在一个处理栈上造成了渲染与提取的不一致。

图 6|Corpus A 在 16 个 PDF 处理栈上的结果(论文表 2):● 表示渲染与提取不一致,gap 被暴露;○ 表示输出与渲染一致;– 表示无输出或报错

在大模型层面,作者通过官方 API 测试了 7 个支持 PDF 输入的商业平台:Claude Sonnet 4.6、Grok 4.2、GPT-5.4、Gemini 3、Kimi K2.6、Qwen-Long 和 GLM-5.1。每个平台都暴露了 12 到 21 个 gap,无一幸免。这些结果也不是偶然:在每组 10 次的独立调用中,69 个暴露的摘要组合里有 65 个、100 个暴露的问答组合里有 96 个至少成功 8 次。作为对照,去掉 gap 构造、保持可见内容和提示词不变,1,260 次对照调用中没有一次出现攻击者的内容。

图 7|各平台暴露的 gap 数量(按家族堆叠)以及每个家族的暴露比例

举一个直观的例子:页面上写的审查编号是 LARK-27,提取出来却是 LARK-27XQ9。作者只问了一句“HV-204 的审查编号是什么”,7 个平台的官方 API 在 10 次调用中全部回答 LARK-27XQ9。网页版同样中招,下面是 ChatGPT 和 Claude 网页版对“图书馆新闻”PDF 的总结,两者都报告了页面上根本不存在的纵火和欺诈调查。

图 8|ChatGPT 网页版(EG02,/ActualText)在总结中报告了纵火与保险欺诈指控

图 9|Claude 网页版(EG01,/ToUnicode)总结的最后一句,同样来自用户看不到的提取层

决定风险的是pipeline,不是模型

比“全部中招”更值得关注的是:暴露哪些 gap,主要由把 PDF 变成模型上下文的那条文本提取pipeline决定,而不是模型本身。

  • 换一条云路由:GPT-5.4 经 Azure 调用时从 21 个降到 17 个,少掉的主要是页外文字、极小字号、零高度矩阵等几何类隐藏和一个阅读顺序 gap,语义覆盖和字体解码类依然存在;Sonnet 4.6 经 Amazon Bedrock 仍为 15 个。

  • API 和网页版是两条pipeline:Claude 网页版比 API 多暴露 4 个;Gemini 网页版在单页文档上是 0/25,但把攻击页放在一份 120 页文档的末尾,它就退回到文本路线,暴露 21/25。

  • 模型能力不影响暴露:Haiku 4.5、Sonnet 4.6、Opus 4.7 都是 15 个,逐 gap 的模式完全一致;GPT-5.5 比 GPT-5.4 少了 3 个几何类 gap,这来自服务端的更新。

  • 本地部署也躲不开:Ollama 和 LM Studio 内置的 PDF 提取器,把 16 个和 17 个 gap 的内容直接送进了本地模型的上下文。

副产品:给看不见的pipeline“验指纹”

不同提取器的“出错方式”各不相同,而且非常稳定,所以同一批 canary 还可以反过来识别应用背后用的是哪种 PDF 解析器。由 EG01、EG17、EG19、EG25 组成的 4 页 canary,能在论文的受控面板中唯一区分全部 16 条解析路径;只用 EG12 和 EG18 两个探针文档,就能区分 LangChain 的 7 种 PDF loader。对于黑盒部署,这类签名更适合作为解析器家族层面的证据。

图 10|两个探针文档(EG12、EG18)在 LangChain 7 种 PDF loader 上的提取签名(论文表 7),任意两种 loader 都能被区分开

防御与思考

作者评估了现有的 PDF 安全过滤器,它们只覆盖了部分隐藏文本的构造方式,对语义覆盖完全无效。作者实现了一个静态扫描器,其规则在自测中对全部 25 个基准 gap 都能触发;更长期的方向,是在文本提取阶段对渲染视图和提取视图做一致性校验。

在文档到大模型的供应链里,PDF 文本提取层本身就是一道安全边界:用户审阅的是渲染出来的页面,模型推理的却是提取出来的文本,两者之间没有任何一致性保证。对于构建 RAG、文档问答,以及合同、简历、稿件审阅等应用的开发者,不要默认“提取出来的文本就是用户看到的内容”,也要留意解析器、云路由和产品入口的选择带来的差异。

论文的代码、两级基准语料和各解析器的预计算输出均已开源,并获得了 IEEE S&P 全部三项 Artifact 徽章(Available、Functional、Reproduced)。作者还制作了一个交互式项目主页,可以直接在网页上切换不同的提取器,看看同一个 PDF 在它们眼中分别是什么样子。

论文:https://arxiv.org/abs/2606.15020

项目主页:https://sd957.github.io/split-view-pdf/

代码与数据:https://github.com/SD957/Split-View-PDF

跳转微信打开

本文转载自微信公众号「安全研究GoSSIP」,查看原文。版权归原作者所有,如有侵权请联系删除。