细致分析Padding Oracle渗透测试全解析

大数据

作者:Sherkey

最近在研究Padding Oracle渗透测试,发现网上没有详细讲原理的文章。因此自己整理了这样一篇,希望哪怕是没有密码学基础的朋友也能看完后完全理解攻击的原理。

一、基础知识介绍

本节针对无密码学基础的读者,若均理解可直接跳过。

1.1 什么是分组密码?

以下定义来自维基百科:

在密码学中,分组加密(英语:Block cipher),又称分块加密块密码,是一种对称密钥算法。它将明文分成多个等长的模块(block),使用确定的算法和 对称密钥对每组分别加密解密。

简单来说,就是将明文进行分组,每组分别加密,最后再连在一起形成密文。AES, DES等加密方法均属于分组加密。

1.2 什么是PKCS#5?

正如1.1中所说,分组密码中,需要对明文进行分组。

但是分组要求每个块的大小都要相同。

那么问题就来了,如果说我的明文是‘testabc’,而我用的分组密码是五个一组。这样第一组内容是‘testa’,那么第二组只剩下了‘bc’,不够五个,应该怎么办呢?

这是就要用到一种填充方式,用来把最后空出来的几位填满。

而PKCS#5,就是一种由RSA信息安全公司设计的填充标准。

对于PKCS#5标准来说,一般缺少几位,就填充几位那个数字。

比方说,在上面的例子里,我们有三位空缺,那么就要在空缺处都填上3 。这样,第二组的内容就变成了‘bc333’ 。

这里需要注意的是,比如说,如果每个分组是8字节,我的明文是‘testabcd’,这样恰好是8字节了。但是按照PKCS#5的标准,我们仍然需要在后面添加一个块,块里的内容全部填充为8 (因为一个块大小为8字节,而第二个块全部为空,因此有8位需要填充)。

1.3 CBC模式

在密码学中,分组密码有许多的工作模式。

可能有人会问,我们直接分组以后加密不就好了吗,为什么需要设计模式呢?

没错,其实分组后直接加密也是一种设计模式,名为电子密码本(Electronic codebook,ECB)模式。

大数据

如图所示,其实就是每个分组的明文均利用相同的密钥进行加密。

但是这种模式明显有个缺点,那就是所有的密钥都相同,导致相同的明文,一定会被加密成相同的密文。

这样很显然是一种不安全的模式。因为它不能提供严格的数据保密性。可以联想一下最简单的凯撒密码,相同的明文加密后得到相同的密文,导致这种方式难以阻挡频率攻击。

那么为了让我们加密后的信息更加难以破解,在1976年,IBM发明了密码分组链接(CBC,Cipher-block chaining)模式。

如下图所示。

大数据

我们可以看到,这里加入了一个初始向量(IV, Initialization Vector)。在第一块明文进行加密之前,需要先与初始向量进行异或。而产生的密文,将与下一组明文进行异或。在这种方法中,每个密文块都依赖于它前面的所有明文块。

这种模式使原本独立的分组密码加密过程形成迭代,使每次加密的结果影响到下一次加密。这样无疑比ECB模式要安全了许多。

上图分别是CBC模式下加密和解密的过程。我们可以看到在中间写着Block Cipher Encryption 的方块那里即是加密算法,本文中就用AES来举例。

而Initiallzation Vector(IV)即是初始向量,也是本攻击方式所重点利用的地方。

1.4 异或

在密码学里,异或是一种很重要的运算方式。

具体表现在,一个数字连着异或两次另一个数字后,得到的值还是它本身。

比方说,65 ^ 66 = 3

3 ^ 66 = 65

在下面我们会利用到这个重要的运算。

二、破解过程详解

文章开头说了,这是一种针对CBC模式的攻击。与你具体选择的是哪种加密方式是没有关系的。

也就是说,在上面CBC加密模式的图里,不管你在‘Block Cipher Decryption’那个框里选的是AES加密,还是DES加密,都无所谓,只要服务器配置不正确,我们都有机会破解。

那么这是如何做到的呢?在说明攻击方式之前,我们先来了解一下正常的解密方式是如何的。

2.1 正常解密过程

我们先来分析一下正常状态下,服务器的解密过程。

首先,CBC模式下AES的解密需要知道IV值与密钥。这个应该很好理解。‘Block Cipher Decryption’肯定需要知道密钥来解密,而CBC模式需要IV值来解密。

而服务器端仅仅保留了密钥,没有保留IV值。因为默认情况下加密后的文件会把IV值附在文件开头,因此服务器会直接使用传给它的文件开头8或16位(视加密方式而定),作为IV值。

在此我们停一下,先举个例子,详细解释一下CBC模式下AES加密过的密文如何正常解密得到明文。

比方说,我们有密文’9F0B13944841A832B2421B9EAF6D9836813EC9D944A5C8347A7CA69AA34D8DC0DF70E343C4000A2AE35874CE75E64C31′ 。这段密文用CBC模式下AES加密,并且已知key和IV值。

接下来我们进行解密。

首先,AES是一种分组密码,每组由16字节组成。密文是用十六进制表示的,也就是相邻两位数字合在一起组成一个十六进制数,用来表示一字节。密文长度为96,按照32一组进行分组,则恰好可以分为三组。我们把这三组密文存到一个数组C里,以后用C[0], C[1], C[2]来表示这三组密文。

然后我们看一下解密用的图。

大数据

如上图所示,我已经在图中标出了C[0], C[1], C[2]的位置。我们正常解密时,先用C[0]与Key解密AES,然后得到一个中间值,这个中间值与IV值进行异或,得到第一段明文。而C[0]作为新的IV值,与C[1]在AES解密后的中间值进行异或,从而得到第二段明文。第三段以此类推。这就是一个正常解密的过程。

下图中把“中间值”这个概念用红圈表示了出来。千万千万要好好看懂解密过程以及“中间值”究竟代表什么,这是本攻击的关键。

大数据

而Padding Oracle 攻击的重点,就在于上一段提到的中间值。

我们继续聊服务器端正常情况下如何解密。其实和我们上面分析的差不多。区别在于,我们解密的时候是从左往右,也就是先从C[0]开始解密;而服务器是从右往左,也就是先解密C[2]。

那么问题又来了:服务器如何知道自己解密后得到的结果是否正确呢?

因为服务器无法判断解密后明文是否有具体含义,因此它也就不从含义上去判断。它判断的方式很简单粗暴,就是利用Padding值来进行判断。

在1.2处我们了解了PKCS#5标准下的填充方式。我们已经知道,明文加密之前肯定会在末尾附上一段Padding值用来填充。

那么接下来,服务器从C[2]进行解密。比如说,一开始明文最后Padding值为0×05,也就是最后五位应该均为5 。而服务器解密后,发现也确实如此,那么它就认为这次解密时正确的。而如果服务器发现解密后Padding值为0×05 ,但是只有最后四位的值为0×05 ;或者说它解密后得到的Padding值为0×04 ,但是最后五位都是0×04 ,那么就造成了Padding的值与数目不相符,服务器就会认为解密失败,从而报错。

我们可以从其他参考文章的描述中看到:

如果解密过程没有问题,明文验证(如用户名密码验证)也通过,则会返回正常 HTTP 200如果解密过程没有问题,但是明文验证出错(如用户名密码验证),则还是会返回 HTTP 200,只是内容上是提示用户用户名密码错误如果解密过程出问题了,比如Padding规则核对不上,则会爆出 HTTP 500错误。

其实就是我们刚才说的判断方式的更具体应用。

而知道服务器的判断方式以后,我们就可以在这里做文章了。

2.2 攻击过程

大数据

本次攻击的关键,集中在我用红圈圈起来的地方。

也就是C[0]. C[1]. C[2]分别用key对AES进行解密,但是还没有与IV值异或的那个值。本文把那个值称为中间值。

大数据

上图是正常解密的结果。而本次攻击的关键,就在被绿色标注的那一行。

本文把绿色标注那一行的值称为中间值。

根据上面的CBC模式解密的图,我们可以知道,解密会先使用AES解密得到一个中间值,然后在通过中间值与IV值的异或,最后得到明文。

而padding oracle攻击的本质,其实就是通过传递密文以及自己猜测的IV值,通过观察服务器对padding判断的结果,进而反推出中间值,最后用中间值与正确的IV异或得到明文。

也就是这个攻击直接跳过了AES,而针对CBC进行攻击。

需要注意的是,自己传递的IV值与正确的IV值不能混淆。鉴于图中自己传递的IV值为黄色,因此下称自己传递的IV值为黄IV,正确的IV值为原IV。

我们再来想想服务器是怎么解密的:从右往左。

大数据

再看看CBC模式解密的那张图,从右往左,就是说先解密C[2]。而解密时,就会用C[1]当做IV值,来与C[2]解密后得到的中间值进行异或,进而得到明文。

而我们首先需要自己构造C[1]中的值,也即在上面图片中的黄IV值,构造以后与C[2]解密得到的中间值进行异或。

并且根据上面服务器判断解密过程是否正确的条件来看,只要最后padding值与个数相对应即可。

因此,我们可以一位一位的构造,第一次先通过改变黄IV值,产生padding结果为1的情况,即可倒推出中间值。

那么我们开始进行破解。

2.3 第一次循环

还记得我们前面提到的服务器判断方式吗?就是只需要padding的值与个数相符即可。

而在第一次循环里,我们只需要构造C[1],使得与C[2]产生的中间值异或以后,得到的最后一位为0×01即可。

C[1]是一个十六位的数组(相邻两位算作一个十六进制数)。我们可以使它前15位均为随机数。因为如果是随机数的话,与中间值异或后的结果也是一个随机数,不太可能恰好是0×01,因此就避免了你最后一位得到0×01,而前一位恰好也是0×01导致个数不符,服务器判断为错的尴尬。C[1]最后一位从00到ff进行尝试。根据上面所说,会有一个值恰好与C[2]解密后的中间值异或后会得到0×01这个结果。这时服务器返回显示正确。

返回正确时,意味着如下公式成立:

C1 ^ 中间值的最后一位 = 0×01

那么按照异或运算的性质,我们不难得到:

中间值的最后一位 = C1 ^ 0×01

这样我们就成功得到了中间值的最后一位。

2.4 第二次至第N次循环

Padding值为1的情况结束后,再用同样方法尝试2 。但是要注意,为了让服务器判断正确,我们需要使最后两位结果都为2 。那么,倒数第二位是我们本次循环的尝试位,倒数第一位如何确定呢?

因为我们在padding值为1的时候,已经通过尝试得到了正确的中间值。因此,我们只需要将中间值与0×02进行异或的结果放到此处即可。

参考下式会更容易理解:

C1 = 上一步得到的中间值最后一位 ^ 0×02

这里可能有人会问,为什么这一步的C1和上一步的不一样了?

这是因为我们是在通过穷举法来破解。我们在穷举的时候,是把整个C[1]都当做了可以改变的对象,目的就是通过改变C[1]来解除C[2]的中间值。因此上一步我们求出来了中间值以后,C1就可以被我们继续改变了。

而在这一次,我们是为了让padding值为2,这就意味着最后两位都需要为0×02。而倒数第二位是我们这次穷举需要改变的值,那么我们就需要让C1与最后一位的中间值异或以后,能得到0×02。

而最后一位中间值我们已经求得了。因此在这一轮循环里,我们要让C1固定,这样异或后的结果为0×02,我们也就可以愉快的继续穷举倒数第二位了。

之后的操作也与此相同。第n轮循环的时候,只要把C[1]倒数第n-1一直到倒数第一位的值全部固定,使得他们与得到的中间值异或后得到n就可以了。

在整个C[2]破解结束后,我们会得到这个块中所有的中间值。这时,我们只需要掏出未经改变的C[1] ,也就是原IV,与中间值异或,就可以得到C[2]块的全部明文了。

之后我们再用相同的方法破解C[1],就能得到全部明文了。

极客网企业会员

免责声明:本网站内容主要来自原创、合作伙伴供稿和第三方自媒体作者投稿,凡在本网站出现的信息,均仅供参考。本网站将尽力确保所提供信息的准确性及可靠性,但不保证有关资料的准确性及可靠性,读者在使用前请进一步核实,并对任何自主决定的行为负责。本网站对有关资料所引致的错误、不确或遗漏,概不负任何法律责任。任何单位或个人认为本网站中的网页或链接内容可能涉嫌侵犯其知识产权或存在不实内容时,应及时向本网站提出书面权利通知或不实情况说明,并提供身份证明、权属证明及详细侵权或不实情况证明。本网站在收到上述法律文件后,将会依法尽快联系相关文章源头核实,沟通删除相关内容或断开相关链接。

2017-10-18
细致分析Padding Oracle渗透测试全解析
作者:Sherkey 最近在研究Padding Oracle渗透测试,发现网上没有详细讲原理的文章。因此自己整理了这样一篇,希望哪怕是没有密码学基础的朋友也能

长按扫码 阅读全文