From c741d8361ee91910c4198a70c646e92a70db7c45 Mon Sep 17 00:00:00 2001 From: xuxueli <931591021@qq.com> Date: Sat, 23 Aug 2025 16:02:51 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=90=E4=BF=AE=E5=A4=8D=E3=80=91=E6=BC=8F?= =?UTF-8?q?=E6=B4=9E=E4=BF=AE=E5=A4=8D=EF=BC=88CVE-2025-7787=EF=BC=89?= =?UTF-8?q?=EF=BC=8C=E9=92=88=E5=AF=B9=20httpJobHandler=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E9=85=8D=E7=BD=AEURL=E7=99=BD=E5=90=8D=E5=8D=95?= =?UTF-8?q?=E9=99=90=E5=88=B6=EF=BC=8C=E9=98=B2=E6=AD=A2=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=99=A8=E7=AB=AF=E8=AF=B7=E6=B1=82=E4=BC=AA=E9=80=A0=EF=BC=88?= =?UTF-8?q?SSRF=EF=BC=89=E6=94=BB=E5=87=BB=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/XXL-JOB官方文档.md | 4 +- doc/db/tables_xxl_job.sql | 3 +- .../frameless/jobhandler/SampleXxlJob.java | 39 +++++++++++++++---- .../job/executor/jobhandler/SampleXxlJob.java | 36 ++++++++++++++--- 4 files changed, 67 insertions(+), 15 deletions(-) diff --git a/doc/XXL-JOB官方文档.md b/doc/XXL-JOB官方文档.md index 917b7836..b0937549 100644 --- a/doc/XXL-JOB官方文档.md +++ b/doc/XXL-JOB官方文档.md @@ -2545,7 +2545,9 @@ public void execute() { ### 7.40 版本 v3.2.0 Release Notes[规划中] - 1、【强化】AI任务(ollamaJobHandler)优化:针对 “model” 模型配置信息,从执行器侧文件类配置调整至调度中心“任务参数”动态配置,支持集成多模型、并结合任务动态配置切换。 -- 2、【升级】升级多项maven依赖至较新版本,如 spring-ai、dify 等; +- 2、【修复】漏洞修复(CVE-2025-7787),针对 httpJobHandler 支持配置URL白名单限制,防止服务器端请求伪造(SSRF)攻击。 +- 3、【升级】升级多项maven依赖至较新版本,如 spring-ai、dify 等; + - 3、【规划中】登录安全升级,密码加密处理算法从Md5改为Sha256; ``` // 1、用户表password字段需要调整长度,执行如下命令 diff --git a/doc/db/tables_xxl_job.sql b/doc/db/tables_xxl_job.sql index f7c05eed..f3522f79 100644 --- a/doc/db/tables_xxl_job.sql +++ b/doc/db/tables_xxl_job.sql @@ -149,7 +149,8 @@ VALUES (1, 1, '示例任务01', now(), now(), 'XXL', '', 'CRON', '0 0 0 * * ? *' (2, 2, 'Ollama示例任务01', now(), now(), 'XXL', '', 'NONE', '', 'DO_NOTHING', 'FIRST', 'ollamaJobHandler', '{ "input": "慢SQL问题分析思路", - "prompt": "你是一个研发工程师,擅长解决技术类问题。" + "prompt": "你是一个研发工程师,擅长解决技术类问题。", + "model": "qwen3:0.6b" }', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', now(), ''), (3, 2, 'Dify示例任务', now(), now(), 'XXL', '', 'NONE', '', diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-frameless/src/main/java/com/xxl/job/executor/sample/frameless/jobhandler/SampleXxlJob.java b/xxl-job-executor-samples/xxl-job-executor-sample-frameless/src/main/java/com/xxl/job/executor/sample/frameless/jobhandler/SampleXxlJob.java index 3d8ffe77..cc985b1a 100644 --- a/xxl-job-executor-samples/xxl-job-executor-sample-frameless/src/main/java/com/xxl/job/executor/sample/frameless/jobhandler/SampleXxlJob.java +++ b/xxl-job-executor-samples/xxl-job-executor-sample-frameless/src/main/java/com/xxl/job/executor/sample/frameless/jobhandler/SampleXxlJob.java @@ -12,8 +12,11 @@ import java.io.DataOutputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; /** @@ -83,7 +86,7 @@ public class SampleXxlJob { BufferedReader bufferedReader = null; try { // valid - if (command==null || command.trim().length()==0) { + if (command==null || command.trim().isEmpty()) { XxlJobHelper.handleFail("command empty."); return; } @@ -168,15 +171,18 @@ public class SampleXxlJob { } // param valid - if (url==null || url.trim().length()==0) { + if (url==null || url.trim().isEmpty()) { XxlJobHelper.log("url["+ url +"] invalid."); - + XxlJobHelper.handleFail(); + return; + } + if (!isValidDomain( url)) { + XxlJobHelper.log("url["+ url +"] not allowed."); XxlJobHelper.handleFail(); return; } if (method==null || !Arrays.asList("GET", "POST").contains(method.toUpperCase())) { XxlJobHelper.log("method["+ method +"] invalid."); - XxlJobHelper.handleFail(); return; } @@ -206,9 +212,9 @@ public class SampleXxlJob { connection.connect(); // data - if (isPostMethod && data!=null && data.trim().length()>0) { + if (isPostMethod && data!=null && !data.trim().isEmpty()) { DataOutputStream dataOutputStream = new DataOutputStream(connection.getOutputStream()); - dataOutputStream.write(data.getBytes("UTF-8")); + dataOutputStream.write(data.getBytes(StandardCharsets.UTF_8)); dataOutputStream.flush(); dataOutputStream.close(); } @@ -220,7 +226,7 @@ public class SampleXxlJob { } // result - bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8")); + bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8)); StringBuilder result = new StringBuilder(); String line; while ((line = bufferedReader.readLine()) != null) { @@ -251,6 +257,25 @@ public class SampleXxlJob { } + // domain white-list, for httpJobHandler + private static Set DOMAIN_WHITE_LIST = new HashSet(Arrays.asList( + "http://www.baidu.com", + "http://cn.bing.com" + )); + // valid if domain is in white-list + private boolean isValidDomain(String url) { + if (url == null || DOMAIN_WHITE_LIST.isEmpty()) { + return false; + } + for (String prefix : DOMAIN_WHITE_LIST) { + if (url.startsWith(prefix)) { + return true; + } + } + return false; + } + + /** * 5、生命周期任务示例:任务初始化与销毁时,支持自定义相关逻辑; */ diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/java/com/xxl/job/executor/jobhandler/SampleXxlJob.java b/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/java/com/xxl/job/executor/jobhandler/SampleXxlJob.java index 76e15c72..55d3b5a3 100644 --- a/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/java/com/xxl/job/executor/jobhandler/SampleXxlJob.java +++ b/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/java/com/xxl/job/executor/jobhandler/SampleXxlJob.java @@ -13,8 +13,11 @@ import java.io.DataOutputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; /** @@ -170,15 +173,18 @@ public class SampleXxlJob { } // param valid - if (url==null || url.trim().length()==0) { + if (url==null || url.trim().isEmpty()) { XxlJobHelper.log("url["+ url +"] invalid."); - + XxlJobHelper.handleFail(); + return; + } + if (!isValidDomain( url)) { + XxlJobHelper.log("url["+ url +"] not allowed."); XxlJobHelper.handleFail(); return; } if (method==null || !Arrays.asList("GET", "POST").contains(method.toUpperCase())) { XxlJobHelper.log("method["+ method +"] invalid."); - XxlJobHelper.handleFail(); return; } @@ -208,9 +214,9 @@ public class SampleXxlJob { connection.connect(); // data - if (isPostMethod && data!=null && data.trim().length()>0) { + if (isPostMethod && data!=null && !data.trim().isEmpty()) { DataOutputStream dataOutputStream = new DataOutputStream(connection.getOutputStream()); - dataOutputStream.write(data.getBytes("UTF-8")); + dataOutputStream.write(data.getBytes(StandardCharsets.UTF_8)); dataOutputStream.flush(); dataOutputStream.close(); } @@ -222,7 +228,7 @@ public class SampleXxlJob { } // result - bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8")); + bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8)); StringBuilder result = new StringBuilder(); String line; while ((line = bufferedReader.readLine()) != null) { @@ -253,6 +259,24 @@ public class SampleXxlJob { } + // domain white-list, for httpJobHandler + private static Set DOMAIN_WHITE_LIST = new HashSet(Arrays.asList( + "http://www.baidu.com", + "http://cn.bing.com" + )); + // valid if domain is in white-list + private boolean isValidDomain(String url) { + if (url == null || DOMAIN_WHITE_LIST.isEmpty()) { + return false; + } + for (String prefix : DOMAIN_WHITE_LIST) { + if (url.startsWith(prefix)) { + return true; + } + } + return false; + } + /** * 5、生命周期任务示例:任务初始化与销毁时,支持自定义相关逻辑; */