shiro近期纰漏了一个漏洞,定级为Critical,是利用Padding Oracle Vulnerability破解rememberMe Cookie,达到反序列化漏洞的利用,攻击者无需知道rememberMe的加密密钥(绕过之前的漏洞修复)。
针对Padding Oracle Attack(填充提示攻击)做个详细的分析和验证。
1. Padding Oracle Attack(填充提示攻击)
Padding Oracle Attack是比较早的一种漏洞利用方式了,在20111年的Pwnie Rewards中被评为”最具有价值的服务器漏洞“。该漏洞主要是由于设计使用的场景不当,导致可以利用密码算法通过”旁路攻击“被破解,并不是对算法的破解。
利用该漏洞可以破解出密文的明文以及将明文加密成密文,该漏洞存在条件如下:
- 攻击者能够获取到密文(基于分组密码模式),以及IV向量(通常附带在密文前面,初始化向量)
- 攻击者能够修改密文触发解密过程,解密成功和解密失败存在差异性
例如请求http://www.example.com/decrypt.jsp?data=0000000000000000EFC2807233F9D7C097116BB33E813C5E,当攻击者在篡改data值时会有以下不同的响应:
1. 如果data值没有被篡改,则解密成功,并且业务校验成功,响应200
2. 如果data值被篡改,服务端无法完成解密,解密校验失败,则响应500
3. 如果data值被篡改,但是服务端解密成功,但业务逻辑校验失败,则可能返回200或302等响应码,而不是响应500
攻击者只需要关注解密成功和解密失败的响应即可(第三种属于解密成功的响应),即可完成攻击。
2. 原理介绍
1. 分组密码的填充
常用的对称算法,如3DES、AES在加密时一般采用分组密码(Block Cipher),将明文进行分组,如常见的64bit、128bit、256bit。
分组带来一个问题,就是明文不可能恰好是block的整数倍,对于不能整除剩余的部分数据就涉及到填充操作。常用的填充操作有PKCS#5和PKCS#7,在最后一个block中将不足的bit位数作为bit值进行填充,例如最后一个分组(block)缺少3个bit,就填充3个0x03到结尾,缺少n个bit,就填充n个0x0n。在解密时会校验明文的填充是否满足该规则,如果是以N个0x0N结束,则意味着解密操作执行成功,否则解密操作失败。
2. CBC模式密码算法
分组密码算法有四种模式,分别是ECB、CBC、CFB和OFB,其中CBC是IPSEC的标准做法,CBC主要是引入一个初始化向量(IV)来加强密文的随机性,保证相同明文通过相同的密钥加密的结果不一样。
CBC加密过程:
1. 明文经过填充后,分为不同的组block,以组的方式对数据进行处理
2. 初始化向量(IV)首先和第一组明文进行XOR(异或)操作,得到”中间值“
3. 采用密钥对中间值进行块加密,删除第一组加密的密文 (加密过程涉及复杂的变换、移位等)
4. 第一组加密的密文作为第二组的初始向量(IV),参与第二组明文的异或操作
5. 依次执行块加密,最后将每一块的密文拼接成密文
由于初始化向量(IV)每次加密都是随机的,所以IV经常会被放在密文的前面,解密时先获取前面的IV,再对后面的密文进行解密
CBC解密过程
1. 会将密文进行分组(按照加密采用的分组大小),前面的第一组是初始化向量,从第二组开始才是真正的密文
2. 使用加密密钥对密文的第一组进行解密,得到”中间值“
3. 将中间值和初始化向量进行异或,得到该组的明文
4. 前一块密文是后一块密文的IV,通过异或中间值,得到明文
5. 块全部解密完成后,拼接得到明文,密码算法校验明文的格式(填充格式是否正确)
6. 校验通过得到明文,校验失败得到密文
3. padding oracle attack破解密文的明文
此处以一个例子进行猜解,假设有这样一个应用,请求如下:
http://www.example.com/decrypt.jsp?data=7B216A634951170FF851D6CC68FC9537858795A28ED4AAC6
现在让我们来看看在不知道明文的情况下,如何猜解处明文。首先我们将密文分组,前面8个字节为初始化向量,后面16个字节为加密后的数据:
初始化向量: 7B 21 6A 63 49 51 17 0F
第一组密文: F8 51 D6 CC 68 FC 95 37
第二组密文: 85 87 95 A2 8E D4 AA C6
首先我们破解第一组密文:http://www.example.com/decrypt.jsp?data=7B216A634951170FF851D6CC68FC9537,通过构造前面初始向量即可破解出第一组密文的明文 ;
-
将初始化向量全部设置为0,提交如下请求http://www.example.com/decrypt.jsp?data=00000000000000000F851D6CC68FC9537 ,服务器势必会解密失败,返回HTTP 500,那是因为在对数据进行解密的时候,明文最后一个字节的填充是Ox3D,不满足填充规则,校验失败,此时示意图如下:
-
依次将初始化向量最后一个字节从0x01~0xFF递增,直到解密的明文最后一个字节为0x01,成为一个正确的padding,当初始化向量为000000000000003C时,成功了,服务器返回HTTP 200,解密示意图如下:
我们已知构造成功的IV最后一个字节为0x3C,最后一个填充字符为0x01,则我们能通过异或XOR计算出,第一组密文解密后的中间值最后一个字节:0x01 xor 0x3C = 0x3D;
重点:第一组密文解密的中间值是一直不变的,同样也是正确的,我们通过构造IV值,使得最后一位填充值满足0x01,符合padding规则,则意味着程序解密成功(当前解密的结果肯定不是原来的明文),通过循环测试的方法,猜解出中间值得最后一位,再利用同样的方式猜解前面的中间值,直到获取到完整的中间值-
下面我们将构造填充值为0x02 0x02的场景,即存在2个填充字节,填充值为0x02,此时我们已经知道了中间值得最后一位为0x3D,计算出初始向量的最后一位为 0x3D xor 0x02 = 0x3F, 即初始向量为0000000000000003F,遍历倒数第二个字节从0x00~0xFF,直到响应成功,示例图如下:
此时,我们猜解出中间值得后两个字节分别为 0x26 0x3D
-
通过同样的方式,完成第一组密文中间值得猜解
-
当第一组密文的中间值猜解成功后,我们将中间值和已知的IV做异或,则得到第一组密文的明文
0x39 0x73 0x23 0x22 0x07 0x6A 0x26 0x3D xor 0x7B 0x21 0x6A 0x63 0x49 0x51 0x17 0x0F = BRIAN;12
继续破解第二组密文,第二组密文的IV向量是第一组密文,随意按照上述的逻辑构造第一组密文,即可破解出第二组明文。
4. 伪造明文的密文
在不知道密钥的情况下,完成数据的加密,绕过服务端的校验(解密成功+明文有效),达到攻击的目的;
伪造明文其实就是上面猜解逆向的过程,有兴趣的朋友可以自己想一想,是不是这么一回事?
2. 验证示例
1. 搭建存在漏洞的环境
https://www.hackingarticles.in/hack-padding-oracle-lab/
下载padding_oracle.iso,安装在VM中即可(linux 32),访问地址完成注册:
我们打开源代码简单看一下,加解密和登录的逻辑:
2. 破解auth cookie
利用padbuster进行破解,工具获取连接https://github.com/AonCyberLabs/PadBuster
破解密文:
伪造密文,登录其它账号: