0%

shiro反序列化

shiro550

利用条件

  1. shiro<=1.2.4

shiro550这个反序列化漏洞整体逻辑比较简单。详细分析参考H0t-A1r-B4llo0n师傅的文章,写得非常通俗易懂,非常详细。

这里进行简单总结

shiro在用户成功登录并且要求rememberMe的情况下,会将principals(可看作是用户名)记录到cookie中。在记录时,并非明文存储,而是经过了序列化->aes加密->base64编码然后再存到cookie中。

问题就出在aes加密,因为它的密钥是以静态变量的形式定义在了代码中,是写死的,如果用户不进行修改,那么就可以伪造rememberMe这个cookie。

然后,在服务器接收到请求时,会先检查rememberMe,如果不为空且不等于”deleteMe”,则会尝试base64解码->aes解密->反序列化。前面我们已经能拿到aes密钥,能伪造rememberMe,那这里就相当于拥有了一个反序列化的入口了,下面就可以尝试各种反序列化poc了。

shiro721

利用条件

  1. shiro<=1.4.1

721生成密钥的方式

首先看看721和550有什么区别,前面我们知道550的密钥是以硬编码的形式写在了代码中。那么721的做法是怎样的呢?

根据如下的调用栈可以看出,721的密钥采用了随机函数生成,修复了550的漏洞。

engineGenerateKey:115, AESKeyGenerator (com.sun.crypto.provider)
generateKey:546, KeyGenerator (javax.crypto)
generateNewKey:62, AbstractSymmetricCipherService (org.apache.shiro.crypto)
generateNewKey:43, AbstractSymmetricCipherService (org.apache.shiro.crypto)
<init>:99, AbstractRememberMeManager (org.apache.shiro.mgt)
<init>:87, CookieRememberMeManager (org.apache.shiro.web.mgt)
<init>:76, DefaultWebSecurityManager (org.apache.shiro.web.mgt)

image-20210816143634402

具体流程

721相对550来说涉及到的知识点更广,需要对密码学知识有一定的了解理解起来才会比较轻松。由于我们不再能像550那样直接得到密钥,所以要想伪造cookie就需要使用更复杂的攻击手段,其中涉及到了padding oracle和cbc翻转攻击。核心的公式就如下两个,不过理解起来还是比较费劲的。

∵  FuzzIV[8]   ^   MediumValue[8]   =   PlainText[8]   =   0x01
∴  MediumValue[8]   =   FuzzIV[8]   ^   0x01
∵  PlainText(n+1) = CipherText(n) ^ Block_Cipher_Decryption(C(n+1))
∴  CipherText(n) = PlainText(n+1) ^ Block_Cipher_Decryption(C(n+1))

具体的流程参考H0t-A1r-B4llo0n师傅,分为两篇。

这里仅记录一些我认为有问题的点。

上篇

首先是在上中padding oracle的分析中,对于每一个分组的最后一个字节的爆破(即MediumValue[8],假设每组大小为8)。根据下面这个公式,IV是已知的,我们只需要得到正确的FuzzIV[8]即可得到MediumValue[8]

∵   FuzzIV[8]   ^   MediumValue[8]   =   PlainText[8]   =   0x01
∴   MediumValue[8]   =   FuzzIV[8]   ^   0x01

但是实际上并没有这么简单,因为我们无法确保padding正确时,PlainText[8]一定是0x01。就比如如下测试,我们按照从0到255的顺序遍历FuzzIV[8]

def xor(a,b):
    a=[int(i,16) for i in a.split(" ")]
    b=[int(i,16) for i in b.split(" ")]
    s=""
    for i in range(len(a)):
        s+=hex(a[i]^b[i])+" "
    return s

fuzziv="0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x01"

iv="0x39 0x73 0x23 0x32 0x5A 0x3B 0x00 0x04"
MediumValue="0x29 0x34 0x5A 0x6B 0x07 0x00 0x02 0x06"

print "plain: "+xor(iv,MediumValue)


for i in range(0,256):
    fuzz = "0x00 0x00 0x00 0x00 0x00 0x00 0x00 %s"%hex(i)
    print xor(fuzz,MediumValue)

当FuzzIV[8]=0x04时,计算结果如下。很明显,这是正确的padding。然而FuzzIV[8] ^ MediumValue[8] = 0x02而不是0x01

FuzzIV = "0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x04"
MediumValue = "0x29 0x34 0x5A 0x6B 0x07 0x00 0x02 0x06"
FuzzIV ^ MediumValue = 0x29 0x34 0x5a 0x6b 0x7 0x0 0x2 0x2

如果此时,我们使用如下公式来计算MediumValue[8],那就出大错了。

MediumValue[8]   =   FuzzIV[8]   ^   0x01

那么正确的做法应该是怎样的呢?查看python paddingoracle模块作者的做法就明白了,在paddingoracle.py的bust函数中。作者使用了重试的做法来解决这个问题,只要某一个FuzzIV[8]可以使padding正确,就尝试继续往下爆破,如果走不通,再回来重新爆破FuzzIV[8],重新爆破时,是从上一次错误的位置开始的。这里有一个疑问,如果FuzzIV[8]错误了,并且继续爆破的时候一错再错,会不会导致算错误?这个问题暂且放这,不在深究。

下篇

作者此处的计算是有问题的,正确算法如下

pad = block_size - (len(plaintext) % block_size)
pad = 16-(279%16)=16=7=9

image-20210816141235550

参考文章

CVE-2016-4437 Shiro550 ( Apache Shiro RememberMe 1.2.4 反序列化漏洞 ) 分析

CVE-2019-12422 Shiro721 ( Apache Shiro RememberMe Padding Oracle 1.4.1 反序列化漏洞) 分析-上

CVE-2019-12422 Shiro721 ( Apache Shiro RememberMe Padding Oracle 1.4.1 反序列化漏洞) 分析-下