Fickle PDFs: exploiting browser rendering discrepancies
想象一下,某公司CEO收到一封包含PDF发票文件的邮件。在Safari和MacOS Preview中显示的总价为399英镑。经批准后,发票发送到使用Windows操作系统的会计部门。然而,当同一个PDF文件在Google Chrome或Google Drive中打开时,价格变成了999英镑。
在本文中,我们将展示如何创建滥用小部件注解的混合PDF来制造渲染差异,并分享代码以便您生成自己的PDF。这项研究受到Konstantin Weddige的博客文章"Kobold Letters"的启发。
浏览器PDF渲染差异
每个主流浏览器都有自己渲染PDF文件的方法。Google Chrome使用名为PDFium的集成PDF查看器,Safari使用自己的PDF渲染引擎,而Firefox使用PDF.js。由于PDF渲染差异,同一个PDF文件在不同浏览器中可能显示不同的内容。例如,交互式表单字段的外观在不同浏览器之间存在差异。
Google Chrome全面支持交互式表单字段和小部件注解,在用户交互时会动态将显示的文本从注解值更新为表单字段的默认值。然而,Firefox和Google Drive预览都优先考虑小部件注解,完全忽略默认值。相比之下,Safari的PDF渲染引擎完全绕过小部件注解,只显示默认值。
构建概念验证
为了构建概念验证,我们将使用org.apache.pdfbox Java库。请注意,即使通过手动文件修改也可以实现相同的结果。我们的交互式表单应至少包含一个输入文本字段及其注解。该字段的纯文本值可以是任何字符串,例如£399。这个值将在不支持表单的PDF阅读器(如Safari和MacOS Preview)中显示。
有趣的是,org.apache.pdfbox.pdmodel.interactive.form.PDTextField#setValue方法也会尝试更新视觉外观,除非PDAcroForm.getNeedAppearances()为true。但是,我们不会使用默认外观,而是使用小部件注解来呈现我们自己的外观。
小部件注解是添加到PDF文档中的对象,用于提供附加信息或交互元素而不改变原始内容。小部件注解表示交互式PDF表单中表单字段的外观。它将显示文本£999而不是原始值。伪代码可能如下所示:
|
|
请注意,注解可以包含任何文本,理论上没有什么能阻止您覆盖整个页面。完整文本可以在https://github.com/PortSwigger/research-labs/tree/main/pdf-rendering-discrepancies找到
浏览器渲染对比
Safari渲染PDF: Google Chrome和Drive预览渲染不同的总价: Firefox与Google Chrome一致:
有趣的是,ChatGPT不支持注解。如果您要求它分析发票,它将返回以下内容:
|
|
最终说明
PDF文件渲染过程复杂且模糊。在将文件发送给会计部门付款或授予聊天助手访问邮箱权限时请务必谨慎。您可以在Github上找到Fickle pdf文件。