CVE-2024-42681
Description
XXL-JOB v2.4.1 lacks permission checks on sub-task IDs, allowing ordinary users to execute arbitrary tasks on unassigned executors.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
XXL-JOB v2.4.1 lacks permission checks on sub-task IDs, allowing ordinary users to execute arbitrary tasks on unassigned executors.
Vulnerability
Overview
CVE-2024-42681 is an insecure permissions vulnerability in XXL-JOB v2.4.1, a distributed task scheduling framework. The root cause is that the sub-task ID feature does not validate whether the referenced task belongs to an executor the current user is authorized to access. The fix, introduced in commit a2dc9011310628f3e18c3a5095e7e6a946d017bd, moves permission filtering for job groups into the PermissionInterceptor class, ensuring that the sub-task ID is resolved within the user's authorized scope [1][2].
Exploitation
An unprivileged (ordinary) user can exploit this by creating a task on an executor they are assigned to (e.g., executor A) and then specifying a sub-task ID that belongs to a task on an executor they are not assigned to (e.g., executor B). When the ordinary user's task runs, the sub-task on executor B is also executed, despite the user lacking explicit permission to access that executor. The attack requires network access to the XXL-JOB admin interface and a valid account, but no special privileges beyond that [4].
Impact
A remote attacker with a low-privileged account can execute arbitrary pre-defined tasks on any executor managed by the XXL-JOB cluster, bypassing the intended authorization boundaries. This can lead to unauthorized data processing, resource consumption, or execution of sensitive business logic, depending on the tasks configured on the target executor [3][4].
Mitigation
The vulnerability is patched in the commit referenced above. Users should upgrade to a version that includes the fix (v2.4.2 or later). As of publication, no workarounds have been documented. The CVE is not listed on CISA's Known Exploited Vulnerabilities (KEV) catalog [1][2][3].
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
com.xuxueli:xxl-job-coreMaven | < 2.4.2 | 2.4.2 |
Affected products
2- xxl-job/xxl-jobdescription
Patches
1a2dc90113106【修复】"CVE-2024-42681" 子任务越权漏洞修复;
11 files changed · +104 −53
doc/XXL-JOB官方文档.md+2 −0 modified@@ -2368,6 +2368,8 @@ public void execute() { ### 7.35 版本 v2.4.2 Release Notes[规划中] - 1、【升级】多个项目依赖升级至较新稳定版本,涉及netty、groovy、gson、springboot、mybatis等; +- 2、【修复】"CVE-2024-42681" 子任务越权漏洞修复; + - 2、[规划中]登陆态Token声称逻辑优化,混淆登陆时间属性,降低token泄漏风险。 ### TODO LIST
xxl-job-admin/src/main/java/com/xxl/job/admin/controller/IndexController.java+1 −1 modified@@ -1,7 +1,7 @@ package com.xxl.job.admin.controller; import com.xxl.job.admin.controller.annotation.PermissionLimit; -import com.xxl.job.admin.service.LoginService; +import com.xxl.job.admin.service.impl.LoginService; import com.xxl.job.admin.service.XxlJobService; import com.xxl.job.core.biz.model.ReturnT; import org.springframework.beans.propertyeditors.CustomDateEditor;
xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/PermissionInterceptor.java+62 −2 modified@@ -1,16 +1,20 @@ package com.xxl.job.admin.controller.interceptor; import com.xxl.job.admin.controller.annotation.PermissionLimit; +import com.xxl.job.admin.core.model.XxlJobGroup; import com.xxl.job.admin.core.model.XxlJobUser; import com.xxl.job.admin.core.util.I18nUtil; -import com.xxl.job.admin.service.LoginService; +import com.xxl.job.admin.service.impl.LoginService; import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.AsyncHandlerInterceptor; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; /** * 权限拦截 @@ -50,10 +54,66 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons if (needAdminuser && loginUser.getRole()!=1) { throw new RuntimeException(I18nUtil.getString("system_permission_limit")); } - request.setAttribute(LoginService.LOGIN_IDENTITY_KEY, loginUser); + request.setAttribute(LoginService.LOGIN_IDENTITY_KEY, loginUser); // set loginUser, with request } return true; // proceed with the next interceptor } + + + // -------------------- permission tool -------------------- + + /** + * get loginUser + * + * @param request + * @return + */ + public static XxlJobUser getLoginUser(HttpServletRequest request){ + XxlJobUser loginUser = (XxlJobUser) request.getAttribute(LoginService.LOGIN_IDENTITY_KEY); // get loginUser, with request + return loginUser; + } + + /** + * valid permission by JobGroup + * + * @param request + * @param jobGroup + */ + public static void validJobGroupPermission(HttpServletRequest request, int jobGroup) { + XxlJobUser loginUser = getLoginUser(request); + if (!loginUser.validPermission(jobGroup)) { + throw new RuntimeException(I18nUtil.getString("system_permission_limit") + "[username="+ loginUser.getUsername() +"]"); + } + } + + /** + * filter XxlJobGroup by role + * + * @param request + * @param jobGroupList_all + * @return + */ + public static List<XxlJobGroup> filterJobGroupByRole(HttpServletRequest request, List<XxlJobGroup> jobGroupList_all){ + List<XxlJobGroup> jobGroupList = new ArrayList<>(); + if (jobGroupList_all!=null && jobGroupList_all.size()>0) { + XxlJobUser loginUser = PermissionInterceptor.getLoginUser(request); + if (loginUser.getRole() == 1) { + jobGroupList = jobGroupList_all; + } else { + List<String> groupIdStrs = new ArrayList<>(); + if (loginUser.getPermission()!=null && loginUser.getPermission().trim().length()>0) { + groupIdStrs = Arrays.asList(loginUser.getPermission().trim().split(",")); + } + for (XxlJobGroup groupItem:jobGroupList_all) { + if (groupIdStrs.contains(String.valueOf(groupItem.getId()))) { + jobGroupList.add(groupItem); + } + } + } + } + return jobGroupList; + } + }
xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobCodeController.java+2 −1 modified@@ -1,5 +1,6 @@ package com.xxl.job.admin.controller; +import com.xxl.job.admin.controller.interceptor.PermissionInterceptor; import com.xxl.job.admin.core.model.XxlJobInfo; import com.xxl.job.admin.core.model.XxlJobLogGlue; import com.xxl.job.admin.core.util.I18nUtil; @@ -43,7 +44,7 @@ public String index(HttpServletRequest request, Model model, int jobId) { } // valid permission - JobInfoController.validPermission(request, jobInfo.getJobGroup()); + PermissionInterceptor.validJobGroupPermission(request, jobInfo.getJobGroup()); // Glue类型-字典 model.addAttribute("GlueTypeEnum", GlueTypeEnum.values());
xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobInfoController.java+9 −34 modified@@ -1,5 +1,6 @@ package com.xxl.job.admin.controller; +import com.xxl.job.admin.controller.interceptor.PermissionInterceptor; import com.xxl.job.admin.core.exception.XxlJobException; import com.xxl.job.admin.core.model.XxlJobGroup; import com.xxl.job.admin.core.model.XxlJobInfo; @@ -10,7 +11,6 @@ import com.xxl.job.admin.core.thread.JobScheduleHelper; import com.xxl.job.admin.core.util.I18nUtil; import com.xxl.job.admin.dao.XxlJobGroupDao; -import com.xxl.job.admin.service.LoginService; import com.xxl.job.admin.service.XxlJobService; import com.xxl.job.core.biz.model.ReturnT; import com.xxl.job.core.enums.ExecutorBlockStrategyEnum; @@ -56,7 +56,7 @@ public String index(HttpServletRequest request, Model model, @RequestParam(requi List<XxlJobGroup> jobGroupList_all = xxlJobGroupDao.findAll(); // filter group - List<XxlJobGroup> jobGroupList = filterJobGroupByRole(request, jobGroupList_all); + List<XxlJobGroup> jobGroupList = PermissionInterceptor.filterJobGroupByRole(request, jobGroupList_all); if (jobGroupList==null || jobGroupList.size()==0) { throw new XxlJobException(I18nUtil.getString("jobgroup_empty")); } @@ -67,33 +67,6 @@ public String index(HttpServletRequest request, Model model, @RequestParam(requi return "jobinfo/jobinfo.index"; } - public static List<XxlJobGroup> filterJobGroupByRole(HttpServletRequest request, List<XxlJobGroup> jobGroupList_all){ - List<XxlJobGroup> jobGroupList = new ArrayList<>(); - if (jobGroupList_all!=null && jobGroupList_all.size()>0) { - XxlJobUser loginUser = (XxlJobUser) request.getAttribute(LoginService.LOGIN_IDENTITY_KEY); - if (loginUser.getRole() == 1) { - jobGroupList = jobGroupList_all; - } else { - List<String> groupIdStrs = new ArrayList<>(); - if (loginUser.getPermission()!=null && loginUser.getPermission().trim().length()>0) { - groupIdStrs = Arrays.asList(loginUser.getPermission().trim().split(",")); - } - for (XxlJobGroup groupItem:jobGroupList_all) { - if (groupIdStrs.contains(String.valueOf(groupItem.getId()))) { - jobGroupList.add(groupItem); - } - } - } - } - return jobGroupList; - } - public static void validPermission(HttpServletRequest request, int jobGroup) { - XxlJobUser loginUser = (XxlJobUser) request.getAttribute(LoginService.LOGIN_IDENTITY_KEY); - if (!loginUser.validPermission(jobGroup)) { - throw new RuntimeException(I18nUtil.getString("system_permission_limit") + "[username="+ loginUser.getUsername() +"]"); - } - } - @RequestMapping("/pageList") @ResponseBody public Map<String, Object> pageList(@RequestParam(required = false, defaultValue = "0") int start, @@ -105,14 +78,16 @@ public Map<String, Object> pageList(@RequestParam(required = false, defaultValue @RequestMapping("/add") @ResponseBody - public ReturnT<String> add(XxlJobInfo jobInfo) { - return xxlJobService.add(jobInfo); + public ReturnT<String> add(HttpServletRequest request, XxlJobInfo jobInfo) { + XxlJobUser loginUser = PermissionInterceptor.getLoginUser(request); + return xxlJobService.add(jobInfo, loginUser); } @RequestMapping("/update") @ResponseBody - public ReturnT<String> update(XxlJobInfo jobInfo) { - return xxlJobService.update(jobInfo); + public ReturnT<String> update(HttpServletRequest request, XxlJobInfo jobInfo) { + XxlJobUser loginUser = PermissionInterceptor.getLoginUser(request); + return xxlJobService.update(jobInfo, loginUser); } @RequestMapping("/remove") @@ -137,7 +112,7 @@ public ReturnT<String> start(int id) { @ResponseBody public ReturnT<String> triggerJob(HttpServletRequest request, int id, String executorParam, String addressList) { // login user - XxlJobUser loginUser = (XxlJobUser) request.getAttribute(LoginService.LOGIN_IDENTITY_KEY); + XxlJobUser loginUser = PermissionInterceptor.getLoginUser(request); // trigger return xxlJobService.trigger(loginUser, id, executorParam, addressList); }
xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java+4 −3 modified@@ -1,5 +1,6 @@ package com.xxl.job.admin.controller; +import com.xxl.job.admin.controller.interceptor.PermissionInterceptor; import com.xxl.job.admin.core.complete.XxlJobCompleter; import com.xxl.job.admin.core.exception.XxlJobException; import com.xxl.job.admin.core.model.XxlJobGroup; @@ -56,7 +57,7 @@ public String index(HttpServletRequest request, Model model, @RequestParam(requi List<XxlJobGroup> jobGroupList_all = xxlJobGroupDao.findAll(); // filter group - List<XxlJobGroup> jobGroupList = JobInfoController.filterJobGroupByRole(request, jobGroupList_all); + List<XxlJobGroup> jobGroupList = PermissionInterceptor.filterJobGroupByRole(request, jobGroupList_all); if (jobGroupList==null || jobGroupList.size()==0) { throw new XxlJobException(I18nUtil.getString("jobgroup_empty")); } @@ -73,7 +74,7 @@ public String index(HttpServletRequest request, Model model, @RequestParam(requi model.addAttribute("jobInfo", jobInfo); // valid permission - JobInfoController.validPermission(request, jobInfo.getJobGroup()); + PermissionInterceptor.validJobGroupPermission(request, jobInfo.getJobGroup()); } return "joblog/joblog.index"; @@ -94,7 +95,7 @@ public Map<String, Object> pageList(HttpServletRequest request, int jobGroup, int jobId, int logStatus, String filterTime) { // valid permission - JobInfoController.validPermission(request, jobGroup); // 仅管理员支持查询全部;普通用户仅支持查询有权限的 jobGroup + PermissionInterceptor.validJobGroupPermission(request, jobGroup); // 仅管理员支持查询全部;普通用户仅支持查询有权限的 jobGroup // parse param Date triggerTimeStart = null;
xxl-job-admin/src/main/java/com/xxl/job/admin/controller/UserController.java+4 −4 modified@@ -1,12 +1,12 @@ package com.xxl.job.admin.controller; import com.xxl.job.admin.controller.annotation.PermissionLimit; +import com.xxl.job.admin.controller.interceptor.PermissionInterceptor; import com.xxl.job.admin.core.model.XxlJobGroup; import com.xxl.job.admin.core.model.XxlJobUser; import com.xxl.job.admin.core.util.I18nUtil; import com.xxl.job.admin.dao.XxlJobGroupDao; import com.xxl.job.admin.dao.XxlJobUserDao; -import com.xxl.job.admin.service.LoginService; import com.xxl.job.core.biz.model.ReturnT; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -112,7 +112,7 @@ public ReturnT<String> add(XxlJobUser xxlJobUser) { public ReturnT<String> update(HttpServletRequest request, XxlJobUser xxlJobUser) { // avoid opt login seft - XxlJobUser loginUser = (XxlJobUser) request.getAttribute(LoginService.LOGIN_IDENTITY_KEY); + XxlJobUser loginUser = PermissionInterceptor.getLoginUser(request); if (loginUser.getUsername().equals(xxlJobUser.getUsername())) { return new ReturnT<String>(ReturnT.FAIL.getCode(), I18nUtil.getString("user_update_loginuser_limit")); } @@ -140,7 +140,7 @@ public ReturnT<String> update(HttpServletRequest request, XxlJobUser xxlJobUser) public ReturnT<String> remove(HttpServletRequest request, int id) { // avoid opt login seft - XxlJobUser loginUser = (XxlJobUser) request.getAttribute(LoginService.LOGIN_IDENTITY_KEY); + XxlJobUser loginUser = PermissionInterceptor.getLoginUser(request); if (loginUser.getId() == id) { return new ReturnT<String>(ReturnT.FAIL.getCode(), I18nUtil.getString("user_update_loginuser_limit")); } @@ -166,7 +166,7 @@ public ReturnT<String> updatePwd(HttpServletRequest request, String password){ String md5Password = DigestUtils.md5DigestAsHex(password.getBytes()); // update pwd - XxlJobUser loginUser = (XxlJobUser) request.getAttribute(LoginService.LOGIN_IDENTITY_KEY); + XxlJobUser loginUser = PermissionInterceptor.getLoginUser(request); // do write XxlJobUser existUser = xxlJobUserDao.loadByUserName(loginUser.getUsername());
xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/LoginService.java+7 −3 renamed@@ -1,12 +1,12 @@ -package com.xxl.job.admin.service; +package com.xxl.job.admin.service.impl; import com.xxl.job.admin.core.model.XxlJobUser; import com.xxl.job.admin.core.util.CookieUtil; import com.xxl.job.admin.core.util.I18nUtil; import com.xxl.job.admin.core.util.JacksonUtil; import com.xxl.job.admin.dao.XxlJobUserDao; import com.xxl.job.core.biz.model.ReturnT; -import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Service; import org.springframework.util.DigestUtils; import javax.annotation.Resource; @@ -17,7 +17,7 @@ /** * @author xuxueli 2019-05-04 22:13:264 */ -@Configuration +@Service public class LoginService { public static final String LOGIN_IDENTITY_KEY = "XXL_JOB_LOGIN_IDENTITY"; @@ -26,6 +26,8 @@ public class LoginService { private XxlJobUserDao xxlJobUserDao; + // ---------------------- token tool ---------------------- + private String makeToken(XxlJobUser xxlJobUser){ String tokenJson = JacksonUtil.writeValueAsString(xxlJobUser); String tokenHex = new BigInteger(tokenJson.getBytes()).toString(16); @@ -41,6 +43,8 @@ private XxlJobUser parseToken(String tokenHex){ } + // ---------------------- login tool, with cookie and db ---------------------- + public ReturnT<String> login(HttpServletRequest request, HttpServletResponse response, String username, String password, boolean ifRemember){ // param
xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java+10 −2 modified@@ -61,7 +61,7 @@ public Map<String, Object> pageList(int start, int length, int jobGroup, int tri } @Override - public ReturnT<String> add(XxlJobInfo jobInfo) { + public ReturnT<String> add(XxlJobInfo jobInfo, XxlJobUser loginUser) { // valid base XxlJobGroup group = xxlJobGroupDao.load(jobInfo.getJobGroup()); @@ -131,6 +131,10 @@ public ReturnT<String> add(XxlJobInfo jobInfo) { return new ReturnT<String>(ReturnT.FAIL_CODE, MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_not_found")), childJobIdItem)); } + if (!loginUser.validPermission(childJobInfo.getJobGroup())) { + return new ReturnT<String>(ReturnT.FAIL_CODE, + MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_permission_limit")), childJobIdItem)); + } } else { return new ReturnT<String>(ReturnT.FAIL_CODE, MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_unvalid")), childJobIdItem)); @@ -169,7 +173,7 @@ private boolean isNumeric(String str){ } @Override - public ReturnT<String> update(XxlJobInfo jobInfo) { + public ReturnT<String> update(XxlJobInfo jobInfo, XxlJobUser loginUser) { // valid base if (jobInfo.getJobDesc()==null || jobInfo.getJobDesc().trim().length()==0) { @@ -223,6 +227,10 @@ public ReturnT<String> update(XxlJobInfo jobInfo) { return new ReturnT<String>(ReturnT.FAIL_CODE, MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_not_found")), childJobIdItem)); } + if (!loginUser.validPermission(childJobInfo.getJobGroup())) { + return new ReturnT<String>(ReturnT.FAIL_CODE, + MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_permission_limit")), childJobIdItem)); + } } else { return new ReturnT<String>(ReturnT.FAIL_CODE, MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_unvalid")), childJobIdItem));
xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java+2 −2 modified@@ -34,15 +34,15 @@ public interface XxlJobService { * @param jobInfo * @return */ - public ReturnT<String> add(XxlJobInfo jobInfo); + public ReturnT<String> add(XxlJobInfo jobInfo, XxlJobUser loginUser); /** * update job * * @param jobInfo * @return */ - public ReturnT<String> update(XxlJobInfo jobInfo); + public ReturnT<String> update(XxlJobInfo jobInfo, XxlJobUser loginUser); /** * remove job
xxl-job-admin/src/test/java/com/xxl/job/admin/controller/JobInfoControllerTest.java+1 −1 modified@@ -1,6 +1,6 @@ package com.xxl.job.admin.controller; -import com.xxl.job.admin.service.LoginService; +import com.xxl.job.admin.service.impl.LoginService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.slf4j.Logger;
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
0No linked articles in our index yet.