diff --git a/doc/XXL-JOB官方文档.md b/doc/XXL-JOB官方文档.md index 6e550095..6a3602e5 100644 --- a/doc/XXL-JOB官方文档.md +++ b/doc/XXL-JOB官方文档.md @@ -2851,13 +2851,16 @@ alter table xxl_job_log - 5、【升级】调度中心UI交互优化,任务及日志列表下拉框支持模糊搜索,提升交互体验; - 6、【修复】XxlJobFileAppender自定义地址callbackLogPath设置无效问题修复;合并ISSUS-3963; - 7、【优化】调度组件守护线程代码重构,提升稳定性以及可维护性; +- 8、【修复】XSS攻击防护增强,ISSUS-3964类似问题批量解决,安全性提升; ### 7.46 版本 v3.5.0 Release Notes[ING] - 1、【TODO】AccessToken线上化管理:执行期维度,限制操作当前执行期; - 2、【TODO】调度中心OpenAPI增强:提供任务管理能力;封装Agent Skill并推送ClawHub; - 3、【TODO】配置线上化:发送邮箱配置线上管理、线程池配置调整; -- 4、【TODO】任务告警:支持Webhook、邮箱多种方式; +- 4、【TODO】任务告警:拆分“告警类型、告警配置”属性,支持Webhook、邮箱多种方式; +- 5、【TODO】任务说明:拆分“任务名称、任务描述”属性,前者用于任务检索,后者用于补充任务描述,如参数说明、功能详细介绍等。 +- 6、【TODO】GLUE模式开关:执行器新增GLUE模式配置,支持任务维度启停,满足差异化场景需求; ### TODO LIST diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/business/controller/JobCodeController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/business/controller/JobCodeController.java index f4fbec1d..08ea22a5 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/business/controller/JobCodeController.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/business/controller/JobCodeController.java @@ -6,6 +6,7 @@ import com.xxl.job.admin.business.model.XxlJobInfo; import com.xxl.job.admin.business.model.XxlJobLogGlue; import com.xxl.job.admin.framework.util.I18nUtil; import com.xxl.job.admin.framework.util.JobGroupPermissionUtil; +import com.xxl.job.admin.framework.util.XssUtil; import com.xxl.job.core.glue.GlueTypeEnum; import com.xxl.sso.core.model.LoginInfo; import com.xxl.tool.core.StringTool; @@ -79,6 +80,9 @@ public class JobCodeController { if (glueRemark.length()<4 || glueRemark.length()>100) { return Response.ofFail(I18nUtil.getString("jobinfo_glue_remark_limit")); } + if (XssUtil.hasXss(glueRemark)) { + return Response.ofFail(I18nUtil.getString("jobinfo_glue_remark") + I18nUtil.getString("system_invalid")); + } XxlJobInfo existsJobInfo = xxlJobInfoMapper.loadById(id); if (existsJobInfo == null) { return Response.ofFail( I18nUtil.getString("jobinfo_glue_jobid_invalid")); diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/business/controller/JobGroupController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/business/controller/JobGroupController.java index bfe9ca11..ffc59efd 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/business/controller/JobGroupController.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/business/controller/JobGroupController.java @@ -7,6 +7,7 @@ import com.xxl.job.admin.framework.util.I18nUtil; import com.xxl.job.admin.business.mapper.XxlJobGroupMapper; import com.xxl.job.admin.business.mapper.XxlJobInfoMapper; import com.xxl.job.admin.business.mapper.XxlJobRegistryMapper; +import com.xxl.job.admin.framework.util.XssUtil; import com.xxl.job.core.constant.Const; import com.xxl.job.core.constant.RegistTypeEnum; import com.xxl.sso.core.annotation.XxlSso; @@ -71,27 +72,31 @@ public class JobGroupController { @XxlSso(role = Consts.ADMIN_ROLE) public Response insert(XxlJobGroup xxlJobGroup){ - // valid + // valid appname if (StringTool.isBlank(xxlJobGroup.getAppname())) { return Response.ofFail((I18nUtil.getString("system_please_input")+"AppName") ); } if (xxlJobGroup.getAppname().length()<4 || xxlJobGroup.getAppname().length()>64) { return Response.ofFail( I18nUtil.getString("jobgroup_field_appname_length") ); } - if (xxlJobGroup.getAppname().contains(">") || xxlJobGroup.getAppname().contains("<")) { + if (XssUtil.hasXss(xxlJobGroup.getAppname())) { return Response.ofFail( "AppName"+I18nUtil.getString("system_invalid") ); } + + // valid title if (StringTool.isBlank(xxlJobGroup.getTitle())) { return Response.ofFail((I18nUtil.getString("system_please_input") + I18nUtil.getString("jobgroup_field_title")) ); } - if (xxlJobGroup.getTitle().contains(">") || xxlJobGroup.getTitle().contains("<")) { - return Response.ofFail(I18nUtil.getString("jobgroup_field_title")+I18nUtil.getString("system_invalid") ); + if (XssUtil.hasXss(xxlJobGroup.getTitle())) { + return Response.ofFail(I18nUtil.getString("jobgroup_field_title") + I18nUtil.getString("system_invalid")); } - if (xxlJobGroup.getAddressType()!=0) { + if (xxlJobGroup.getAddressType() != 0) { + + // valid addressList if (StringTool.isBlank(xxlJobGroup.getAddressList())) { return Response.ofFail( I18nUtil.getString("jobgroup_field_addressType_limit") ); } - if (xxlJobGroup.getAddressList().contains(">") || xxlJobGroup.getAddressList().contains("<")) { + if (XssUtil.hasXss(xxlJobGroup.getAddressList())) { return Response.ofFail(I18nUtil.getString("jobgroup_field_registryList")+I18nUtil.getString("system_invalid") ); } @@ -117,16 +122,26 @@ public class JobGroupController { @ResponseBody @XxlSso(role = Consts.ADMIN_ROLE) public Response update(XxlJobGroup xxlJobGroup){ - // valid + + // valid appname if (StringTool.isBlank(xxlJobGroup.getAppname())) { return Response.ofFail((I18nUtil.getString("system_please_input")+"AppName") ); } if (xxlJobGroup.getAppname().length()<4 || xxlJobGroup.getAppname().length()>64) { return Response.ofFail( I18nUtil.getString("jobgroup_field_appname_length") ); } + if (XssUtil.hasXss(xxlJobGroup.getAppname())) { + return Response.ofFail( "AppName"+I18nUtil.getString("system_invalid") ); + } + + // valid title if (StringTool.isBlank(xxlJobGroup.getTitle())) { - return Response.ofFail( (I18nUtil.getString("system_please_input") + I18nUtil.getString("jobgroup_field_title")) ); + return Response.ofFail((I18nUtil.getString("system_please_input") + I18nUtil.getString("jobgroup_field_title")) ); } + if (XssUtil.hasXss(xxlJobGroup.getTitle())) { + return Response.ofFail(I18nUtil.getString("jobgroup_field_title") + I18nUtil.getString("system_invalid")); + } + if (xxlJobGroup.getAddressType() == 0) { // 0=自动注册 List registryList = findRegistryByAppName(xxlJobGroup.getAppname()); @@ -138,9 +153,15 @@ public class JobGroupController { xxlJobGroup.setAddressList(addressListStr); } else { // 1=手动录入 + + // valid addressList if (StringTool.isBlank(xxlJobGroup.getAddressList())) { return Response.ofFail( I18nUtil.getString("jobgroup_field_addressType_limit") ); } + if (XssUtil.hasXss(xxlJobGroup.getAddressList())) { + return Response.ofFail(I18nUtil.getString("jobgroup_field_registryList")+I18nUtil.getString("system_invalid") ); + } + String[] addresss = xxlJobGroup.getAddressList().split(","); for (String item: addresss) { if (StringTool.isBlank(item)) { diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/framework/util/XssUtil.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/framework/util/XssUtil.java new file mode 100644 index 00000000..ae260946 --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/framework/util/XssUtil.java @@ -0,0 +1,102 @@ +package com.xxl.job.admin.framework.util; + +import java.util.regex.Pattern; + +public class XssUtil { + + /** + * 定义常见的 XSS 攻击正则模式 + * + * 1. "; + String xssInput2 = ""; + String xssInput3 = "javascript:alert(1)"; + + System.out.println("Safe Input Has XSS: " + hasXss(safeInput)); // false + System.out.println("XSS Input 1 Has XSS: " + hasXss(xssInput1)); // true + System.out.println("XSS Input 2 Has XSS: " + hasXss(xssInput2)); // true + System.out.println("XSS Input 3 Has XSS: " + hasXss(xssInput3)); // true + + System.out.println("Cleaned XSS 1: " + cleanXss(xssInput1)); // alert('XSS') (script tags removed, content escaped if needed) + System.out.println("Cleaned XSS 2: " + cleanXss(xssInput2)); // (img tag and event handler removed) + }*/ + +} diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/executor/XxlJobExecutor.java b/xxl-job-core/src/main/java/com/xxl/job/core/executor/XxlJobExecutor.java index 12fc371c..42c3bd53 100644 --- a/xxl-job-core/src/main/java/com/xxl/job/core/executor/XxlJobExecutor.java +++ b/xxl-job-core/src/main/java/com/xxl/job/core/executor/XxlJobExecutor.java @@ -37,6 +37,9 @@ public class XxlJobExecutor { private static XxlJobExecutor xxlJobExecutor = null; public static XxlJobExecutor getInstance() { + if (xxlJobExecutor == null) { + throw new RuntimeException(">>>>>>>>>>> xxl-job load executor instance fail, please initialize it."); + } return xxlJobExecutor; } @@ -367,7 +370,7 @@ public class XxlJobExecutor { public JobThread registJobThread(int jobId, IJobHandler handler, String removeOldReason){ JobThread newJobThread = new JobThread(jobId, handler); newJobThread.start(); - logger.info(">>>>>>>>>>> xxl-job regist JobThread success, jobId:{}, handler:{}", new Object[]{jobId, handler}); + logger.info(">>>>>>>>>>> xxl-job register JobThread success, jobId:{}, handler:{}", new Object[]{jobId, handler}); JobThread oldJobThread = jobThreadRepository.put(jobId, newJobThread); // putIfAbsent | oh my god, map's put method return the old value!!! if (oldJobThread != null) {