简介

如果做过SHIRO-550的复现的大佬都知道SHIRO-550是因为密钥硬编码的问题序列化可控,导致的代码执行,在SHIRO721的那个版本就将密钥硬编码的问题解决了,使用了随机密钥的但是又引出了新的攻击方式(Padding Oracle Attack)。
相比于shiro-550来说,SHIRO-721的利用条件显得更为苛刻。实战中几乎用不到。

环境搭建

将之前我们复现SHIRO-550的shiro1.2.4改为1.2.5版本。

漏洞前提

该分析的在shiro550的时候分析过了,shiro721中padding oracle attack是重点。

该漏洞的利用条件

1.攻击者能够能够获取到密文,以及IV向量(复现shiro550的时候我们就知道,shiro的密文前六位就为iv).

2.攻击者能够修改密文,然后触发解密过程。解密成功和解密失败存在差异性

原理介绍

常用的对称密码,3DES,AES在加密的时候一般采用分组密码,将明文进行分组。如64bit,128bit,256bit.
分组带来的问题就是明文不可能是分组块(block)的整数倍,那么这时候就不能整除剩余的部分数据就涉及到填充操作,常见的填充操作有PKCS#5,PKCS#7.
在最后一个block中将不足的bit位数作为bit值进行填充,例如最后一个分组快缺少三个bit,那么就会填充三个0x03位结尾,缺N个bit,就填充n个0x0n,在解密时会校验明文的填充是否满足该规则,如果是以N个0X0N结束,则意味着解密操作执行成功,否则解密失败。

PKCS#5是8字节填充,有一种情况,如果字符正好是8的整数倍,按照PKCS#5的一个规则,任然需要在尾部填充8个字符,0X08,目的是为了解密时统一处理填充。
shiro用的是PKCS#5。

CBC模式密码算法

分组密码算法有四种模式,分别是ECB、CBC、CFB和OFB,其中CBC是IPSEC的标准做法,主要是引入一个IV(初始化向量)来加强密文的随机性。保证相同明文通过相同的密钥加密的结果不一样。

CBC的加密过程


1.明文经过填充后,会被分成N个组,以组的方式对数据进行处理。
2.将第一组明文,通过和IV(初始化向量)进行异或,然后拿到中间值。
3.用密钥对中间值进行加密
4.然后将第一组加密的密文作为第二组的IV,然后与第二组明文进行异或获取第二组的中间值
5.用密钥对中间值进行加密,获得第二组密文。依次进行下去。
6.最后将每一块的密文拼接起来就成了密文。
因为IV每一次加密都是随机,所以IV经常会放在密文的前面,解密时会先获取到前面的iv,在对后面的密文进行解密。shiro就是放在密文的前面。

CBC的解密过程


1.对密文进行分组(按照加密采用的分组大小),注意第一组是IV,从第二组才开始是真正的密文。
2.使用加密密钥对第一组密文进行解密,得到中间值
3.将中间值与IV进行异或,获得第一组的明文。
4使用加密密钥对第二组密文进行解密,得到中间值。
5.将第一组密文作为IV,通过与中间值异或,得到明文
6.依次解密,最后拼接得到明文,密码算法校验明文的格式
7.校验通过获得明文,校验失败获得密文。

padding oracle attack

padding oracle attack分为三种情况

1.数据没有被修改,且解密成功,业务校验成功则返回200
2.如果数据被修改了,且没有解密成功,业务校验失败,则返回500
3.如果数据被修改了,解密成功,业务校验失败,则返回302 or 200。
故padding oracle attack 并不是解密失败,解密成功这两种所谓的“非黑即白”的死板操作。有时候我们解密成功,但是校验失败了,也正常。
我之前陷入了多个误区,就是认为padding oracle attack是为了拿到服务器的key
其实是在不需要密钥的情况下,攻击者不断提供密文,让解密程序(服务器)通过响应的不同,不断修正,最后得到需要的结果。
我们看这张图,我们输入密文,然后用key对其进行解密获得中间值。将中间值与初始IV进行xor,获得明文。这是正确的解密流程。
image.png
我们这里获取到了密文和IV,能够知道shiro那端的服务器的不同相应就可以构造密文。
在前面,我们说过IV一般会在密文的前八位,前八位可能是IV,后八位是密文。
那假设我们现在有一个环境是这样的
UID=6D367076036E2239F851D6CC68FC9537。
6D367076036E2239就为IV,F8至37就为密文。
根据上面我们所述的CBC的解密过程.
我们现在有IV,有密文,key和明文我们不知道。按照PCCS#5的机制,当我们IV全为0的时候。(0000000000000000F851D6CC68FC9537)
就会爆500.(也就是我们上述说的第二种情况。)
当IV全为0的时候最后一位的明文通过XOR 就变成了0X3D
image.png
我们知道PCS#5的机制,填充的话,要么最后一位为0X01 要么就在多加一个padding全为0X08。
那我们让最后明文的字节为0X01 就符合了PKCS#5的padding机制。
那我们的密文就为000000000000003CF851D6CC68FC9537
image.png
这段属于第三钟情况。解密成功,但业务逻辑判断失败,302 or 200.
也就是说我们通过枚举IV的最后一位为0X0C的时候解密成功了,也就是说,在没有0X08填充的情况下,最后一位只能是0X01 如果不为0X01就不满足PKCS#5的机制,就会爆500,不然就200.
在这种情况下,我们就可以知道中间值的最后一位 XOR 0X3C=0X01
那么我们来一个换算
在Intermediary Value(中间值)未知的情况下
∵[Intermediary Byte] ^ 0x3C == 0x01
得到 [Intermediary Byte] == 0x3C ^ 0x01
∴[Intermediary Byte] == 0x3D
也就是说,我们在中间值未知的情况下,通过我们爆破出来的IV值通过PKCS#5机制的判断200的情况下,通过换算拿到中间值。
然后我们可以知道CBC的解密过程是中间值与IV进行XOR 就可以获取到明文。
我们通过推算。当我们的明文全为0X08的时候满足PKCS#5机制。
以此类推。
image.png

也就是说我们在中间值不知的情况下。我们拿第一个byte来举例
∵[Intermediary Byte] ^ 0x31 == 0x08
得到 [Intermediary Byte] == 0x31 ^ 0x08
∴[Intermediary Byte] == 0x39
这就拿到了第一位的中间值。
依次类推,我们可以获取到所有的中间值,
那么按照CBC的解密方式,我们只需要用中间值与原本的IV进行一个XOR就获取到了明文。
image.png
那么第二段的密文就是我们爆破IV使其明文全部成为0X08,那么就可以知道
∵[Intermediary Byte] ^ 我们爆破的IV的第一个字节 == 0x08
得到 [Intermediary Byte] == 我们爆破IV的第一个字节 ^ 0x08
那么这样我们就得到了中间值,我们将中间值与第一段的密文进行XOR,就获取到了明文,每个块都依次爆破,获取到最终结果。
这就是padding oracle attack.
回到shiro

SHIRO漏洞复现

shiro 721的版本rememberme使用了AES-128-CBC模式加密,且IV已知的情况下。在shiro550的时候我就分析过,我们通过CookieRememberMeManager类,我们可以判断密文的可行性。
padding 失败,返回cookie=rememberme=deleteme。
padding成功,返回正常的响应数据。
最后payload的构造就是不断的用两个块去爆破IV 使其解密为0X08,得到中间值之后,构造密文使得解密后得到指定明文,最后拼接到原有的cookie上(脏数据)。
也就是说,我们通过明文去推密文。然后拼接到原本的cookie后面。
∵上一块的密文xor 中间值=明文。
中间值=爆破的IV xor 明文。
得到中间值xor明文=(IV)上一块的密文
然后
也就是说,中间值与明文块xor得到IV,也就是上一块加密块的密文。
∴我们的密文要满足得到的中间值xor明文。
我们现在有明文块。然后爆破iv,拿到中间值。将中间值与明文块进行一个XOR,得到上一个密文。以此类推。
倒数第二块的明文,也去爆破IV,拿到中间值,将中间值与明文块进行一个XOR,拿到原本的IV也就是前一段的密文。
拼接起来就是密文了。将原有的cookie和脏数据进行拼接得到最后的密文。
第一个块是随机的,然后padding oracle attack ,拿到中间值,然后明文块与中间值进行异或,拿到上一块密文。然后依次推导。组合。
由于这个漏洞在实战中作用较低,故不在加深了。