ソースを参照

【修复】漏洞修复(CVE-2025-7787),针对 httpJobHandler 支持配置URL白名单限制,防止服务器端请求伪造(SSRF)攻击。

xuxueli 4 ヶ月 前
コミット
c741d8361e

+ 3 - 1
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字段需要调整长度,执行如下命令

+ 2 - 1
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', '',

+ 32 - 7
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<String> DOMAIN_WHITE_LIST = new HashSet<String>(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、生命周期任务示例:任务初始化与销毁时,支持自定义相关逻辑;
      */

+ 30 - 6
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<String> DOMAIN_WHITE_LIST = new HashSet<String>(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、生命周期任务示例:任务初始化与销毁时,支持自定义相关逻辑;
      */