Padding Oracle Attack
起因是整理CTF题目时,发现了一道题,所使用的知识点是padding oracle attack,这个攻击以前就听说过,只是还没有学习过,便想借此机会学习一番。奈何readme文档写的太过简单,而其中所给出的脚本使用之后也并不能得到flag。没有办法,只能花费了两天时间从网上搜找相关资料,慢慢研究学习,完善脚本与writeup。
下面步入正题
一、基础知识
故名思义,Padding Oracle Attack背后的关键性概念便是加/解密时的填充(Padding)。明文信息可以是任意长度,但是块状加密算法需要所有的信息都由一定数量的数据块组成。为了满足这样的需求,便需要对明文进行填充,这样便可以将它分割为完整的数据块。
CBC密码分组模式
我们来看一下CBC(密码分组链接)模式。在CBC模式中,每个明文块(PlainText)先与前一个密文块(CipherText)进行异或,再进行加密(使用key)。在这种方法中,每个密文块都依赖于前面的明文块。同时,为了保证每条消息的唯一性,在第一个块中需要使用初始化向量(IV)进行异或。最后将这n块密文连接起来。
CBC的解密方法则反了过来。先将密文进行解密,然后与前一个密文块进行异或(第一个用IV),得到对应分组的明文。最后再将这n块明文连接起来。
填充规则
加密时可以使用多种填充规则,但最常见的填充方式之一是在PKCS#5标准中定义的规则。
PCKS#5的填充方式为:明文的最后一个数据块包含N个字节的填充数据(N取决于明文最后一块的数据长度)。
下面是不同算法所对应的block长度
下图是一些示例,展示了不同长度的单词(FIG、BANANA、AVOCADO、PLANTAIN、PASSIONFRUIT)以及它们使用PKCS#5填充后的结果(在这里,我们假设每个数据块为8字节长)
过程巩固
我们来巩固一遍正常情况下的加密和解密过程
加密:先根据所使用的加密算法判断block的大小(一般是8或16字节),根据block的大小来进行分组,最后一个block使用PKCS#5标准填充,这样得到每一块PlainText。然后使用前一块明文(第一个需使用IV)异或,异或后,再使用key和规定的加密算法进行加密,加密完成后,得到CipherText,最后将所有的CipherText连接起来,便是返回的密文了。
解密:根据加密算法得到block的大小,然后进行分组解密得到中间值,中间值与前一组密文(第一个使用IV)异或,异或后得到PlainText,将所有的PlainText连接起来,便是返回的明文了。
二、Padding Oracle Attack攻击原理
Padding Oracle Attack是针对CBC链接模式的攻击,和具体的加密算法无关,换句话说,这种攻击方式不是对加密算法的攻击,而是针对算法的使用不当进行的攻击。
攻击者可以根据返回的密文长度来猜测block大小。例:如果返回的长度是24字节,那么block一定不会是16字节,而应该是8字节;如果知晓了加密算法,可以根据上面的对照表来得到block的大小
攻击前提:
- 攻击者能得到IV和CipherText,并能提交CipherText
- 攻击者能够触发密文的解密过程,且服务器根据解密结果不同会返回不同的信息
- 攻击者若能提交IV,便可篡改明文。
服务器的处理与返回
假设我们向服务器提交了正确的密码,我们的密码在经过CBC模式加密后传给了服务器,这时服务器会对我们传来的信息尝试解密,如果可以正常解密会返回一个值表示正确,如果不能正常解密则会返回错误。而事实上,判断提交的密文能不能正常解密,第一步就是判断密文最后一组的填充值是否正确,也就是观察最后一组解密得到的结果的最后几位是否符合规范,如果错误将直接返回错误,如果正确,再将解密后的结果与服务器存储的结果比对,判断是不是正确的明文。也就是说服务器一共可能有三种判断结果:
- 密文不能正常解密(填充错误);
- 密文可以正常解密但解密结果不对(填充正确,但得到的明文错误);
- 密文可以正常解密并且解密结果比对正确(填充正确,明文正确);
其中第一种情况与第二三种情况的返回值一定不一样,这就给了我们可乘之机——我们可以利用服务器的返回值判断我们提交的内容能不能正常解密,即判断我们提交的最后一组密文的填充是否符合规范。下面给出了正确与错误的图示
再看一次CBC的解密过程:将CipherText使用key和解密算法解密得到中间值,将中间值与前一组密文(或IV)进行异或得到PlainText。而攻击的前提就是服务器提供CipherText和IV,所以只要我们知道了中间值,便可知道PlainText。这就是Padding Oracle Attack的核心——找出正确的中间值
如何才能找出正确的中间值
前面我们说过服务器会根据我们提交的CipherText的解密结果返回不同的三种情况,我们就依据这三种情况(实际上是两种,填充正确和填充不正确)来得到中间值。
我们还是来看上图的例子,假设只有一个block,中间值是不变的,如果我们将IV修改为全0,返回的明文则与中间值完全一样,但这样基本不可能填充正确。我们需要的是最后一位填充0x01,那么经过遍历最后一个字节后发现当最后一字节为0x3C时,页面返回填充正确的结果,即 中间值^0x3C=0x01,中间值=0x01^0x3C=0x3D。
经过上面的步骤便能得到最后一个字节的中间值。得到之后,需要得到倒数第二个字节的中间值,此时将IV的最后一字节变为0x3D^0x02=0x3F,IV的倒数第二个字节进行遍历,发现当倒数第二个字节为0x24时,页面返回填充正确的结果,即 中间值=0x02^0x24=0x26……以此类推,得到完整的中间值。最后将中间值与IV异或,便可得到明文。
上面说的是最简单的情况:只有一个block。当有多个block时,我们从最后一个block入手(因为该算法要先经过key解密得到中间值,而中间值在同一个CipherText下是一定不变的,后面的PlainText受前一个CipherText影响,而前面的PlainText与后面无关,所以需要从后往前走)。单独截取最后一个block,前面一个block填充全0并遍历得到中间值(若有长度限制,必须与正常密文长度相同,则只将到倒数第二个block修改即可);得到最后一个block的中间值后,将最后一个block舍弃,截取倒数第二个block,前面一个block填充全0并遍历得到中间值(若有长度限制,将倒数第二个block与最后一个block互换位置,互换后的倒数第二个block填充全0并遍历得到中间值);以此类推,得到所有中间值
我们得到中间值之后,便可得到明文。如果我们还可以控制IV,那么将IV与原明文异或后,再与想得到的明文异或得到新的IV,然后将新的IV提交,便可得到想要的明文。
|
|
三、实例
为了更好的理解和利用Padding Oracle Attack,我自己编写了一个简单的PHP页面。
主要功能:
页面显示加密算法、IV、CipherText,并提供两个提交表单;第一个表单根据提交的cipher返回解密成功或失败;第二个表单要求提交正确的PlainText,然后通过修改iv让PlainText的值变为admin。
流程是先判断提交的plainText是否正确,然后将提交的iv和cipher进行解密,如果解密后得到admin则返回PlainText Changed Success!
页面代码
|
|
页面显示效果
使用的加密模式是aes-128-cbc,而aes的block大小是16字节,这一点与上面原理部分不同,需要稍稍注意一下。
攻击脚本
|
|
脚本运行结果:
参考资料
http://blog.zhaojie.me/2010/10/padding-oracle-attack-in-detail.html
https://www.freebuf.com/articles/web/15504.html
https://www.freebuf.com/articles/database/151167.html
https://www.csdn.net/article/1970-01-01/289154