diff --git a/pigx-visual/pigx-daemon-quartz/src/main/java/com/pig4cloud/pigx/daemon/quartz/service/impl/SysJobServiceImpl.java b/pigx-visual/pigx-daemon-quartz/src/main/java/com/pig4cloud/pigx/daemon/quartz/service/impl/SysJobServiceImpl.java index 62e57b45f..74a88dd03 100644 --- a/pigx-visual/pigx-daemon-quartz/src/main/java/com/pig4cloud/pigx/daemon/quartz/service/impl/SysJobServiceImpl.java +++ b/pigx-visual/pigx-daemon-quartz/src/main/java/com/pig4cloud/pigx/daemon/quartz/service/impl/SysJobServiceImpl.java @@ -27,6 +27,7 @@ import com.pig4cloud.pigx.daemon.quartz.constants.JobTypeQuartzEnum; import com.pig4cloud.pigx.daemon.quartz.entity.SysJob; import com.pig4cloud.pigx.daemon.quartz.mapper.SysJobMapper; import com.pig4cloud.pigx.daemon.quartz.service.SysJobService; +import com.pig4cloud.pigx.daemon.quartz.util.JobSecurityValidator; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.Nullable; @@ -46,6 +47,8 @@ import java.util.Objects; @AllArgsConstructor public class SysJobServiceImpl extends ServiceImpl implements SysJobService { + private final JobSecurityValidator jobSecurityValidator; + /** * 检查任务配置 * @@ -55,6 +58,25 @@ public class SysJobServiceImpl extends ServiceImpl impleme */ @Override public R checkJob(String field, SysJob sysJob) { + // Security validation for reflection-based job types + // 对基于反射的任务类型进行安全验证 + if (JobTypeQuartzEnum.SPRING_BEAN.getType().equals(sysJob.getJobType()) || + JobTypeQuartzEnum.JAVA.getType().equals(sysJob.getJobType())) { + + // Comprehensive security check + // 全面的安全检查 + String securityError = jobSecurityValidator.validateJobConfig( + sysJob.getClassName(), + sysJob.getMethodName(), + sysJob.getMethodParamsValue() + ); + + if (securityError != null) { + log.error("Security validation failed for job: {}, error: {}", sysJob.getJobName(), securityError); + return R.ok(securityError); + } + } + // 如果是 spring bean 类型任务 if (JobTypeQuartzEnum.SPRING_BEAN.getType().equals(sysJob.getJobType())) { diff --git a/pigx-visual/pigx-daemon-quartz/src/main/java/com/pig4cloud/pigx/daemon/quartz/util/JavaClassTaskInvok.java b/pigx-visual/pigx-daemon-quartz/src/main/java/com/pig4cloud/pigx/daemon/quartz/util/JavaClassTaskInvok.java index a6e2ad75f..461c1f149 100644 --- a/pigx-visual/pigx-daemon-quartz/src/main/java/com/pig4cloud/pigx/daemon/quartz/util/JavaClassTaskInvok.java +++ b/pigx-visual/pigx-daemon-quartz/src/main/java/com/pig4cloud/pigx/daemon/quartz/util/JavaClassTaskInvok.java @@ -36,8 +36,27 @@ import java.lang.reflect.Method; @Slf4j public class JavaClassTaskInvok implements ITaskInvok { + private final JobSecurityValidator jobSecurityValidator; + + public JavaClassTaskInvok(JobSecurityValidator jobSecurityValidator) { + this.jobSecurityValidator = jobSecurityValidator; + } + @Override public void invokMethod(SysJob sysJob) throws TaskException { + // Security validation before reflection + // 在执行反射之前进行安全验证 + String securityError = jobSecurityValidator.validateJobConfig( + sysJob.getClassName(), + sysJob.getMethodName(), + sysJob.getMethodParamsValue() + ); + + if (securityError != null) { + log.error("Security validation failed during job execution: {}", securityError); + throw new TaskException("安全验证失败: " + securityError); + } + Object obj; Class clazz; Method method; diff --git a/pigx-visual/pigx-daemon-quartz/src/main/java/com/pig4cloud/pigx/daemon/quartz/util/JobSecurityValidator.java b/pigx-visual/pigx-daemon-quartz/src/main/java/com/pig4cloud/pigx/daemon/quartz/util/JobSecurityValidator.java new file mode 100644 index 000000000..04a9f05d8 --- /dev/null +++ b/pigx-visual/pigx-daemon-quartz/src/main/java/com/pig4cloud/pigx/daemon/quartz/util/JobSecurityValidator.java @@ -0,0 +1,268 @@ +/* + * Copyright (c) 2018-2025, lengleng All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the pig4cloud.com developer nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * Author: lengleng (wangiegie@gmail.com) + */ + +package com.pig4cloud.pigx.daemon.quartz.util; + +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; + +/** + * Job Security Validator + *

+ * Validates job configurations to prevent Remote Code Execution (RCE) + * vulnerabilities + * by blocking dangerous classes and methods that could be exploited via + * reflection. + * + * @author Security Team + * @date 2025-01-22 + */ +@Slf4j +@Component +public class JobSecurityValidator { + + /** + * Dangerous classes that should never be allowed for reflection-based job + * execution + * These classes can be exploited to execute arbitrary system commands + */ + private static final Set DANGEROUS_CLASSES = new HashSet<>(Arrays.asList( + // Expression Language classes - can evaluate arbitrary code + "jakarta.el.ELProcessor", + "javax.el.ELProcessor", + "org.apache.el.ExpressionFactoryImpl", + + // Runtime execution classes + "java.lang.Runtime", + "java.lang.ProcessBuilder", + "java.lang.ProcessImpl", + + // Script engines - can execute arbitrary scripts + "javax.script.ScriptEngine", + "javax.script.ScriptEngineManager", + + // Class loading - can load malicious classes + "java.net.URLClassLoader", + "java.lang.ClassLoader", + + // File operations - can be used for data exfiltration + "java.io.FileOutputStream", + "java.io.FileWriter", + "java.nio.file.Files", + + // Network operations - can be used for reverse shells + "java.net.Socket", + "java.net.ServerSocket", + "java.net.URLConnection", + + // Reflection utilities that bypass security + "java.lang.reflect.Constructor", + "java.lang.reflect.Proxy", + "sun.misc.Unsafe")); + + /** + * Dangerous package prefixes that should be blocked + */ + private static final Set DANGEROUS_PACKAGE_PREFIXES = new HashSet<>(Arrays.asList( + "sun.misc.", + "com.sun.jndi.", + "javax.naming.", + "org.apache.commons.collections.functors.", + "org.apache.xalan.xsltc.trax.", + "javassist.", + "org.codehaus.groovy.", + "ognl.", + "freemarker.template.")); + + /** + * Dangerous method names that should never be invoked + */ + private static final Set DANGEROUS_METHODS = new HashSet<>(Arrays.asList( + "eval", + "exec", + "execute", + "loadClass", + "defineClass", + "getRuntime", + "getConstructor", + "newInstance", + "invoke", + "forName", + "getMethod", + "getDeclaredMethod")); + + /** + * Patterns for detecting dangerous method parameter values + */ + private static final List DANGEROUS_PARAM_PATTERNS = Arrays.asList( + Pattern.compile(".*Runtime.*", Pattern.CASE_INSENSITIVE), + Pattern.compile(".*ProcessBuilder.*", Pattern.CASE_INSENSITIVE), + Pattern.compile(".*\\.exec\\(.*", Pattern.CASE_INSENSITIVE), + Pattern.compile(".*ScriptEngine.*", Pattern.CASE_INSENSITIVE), + Pattern.compile(".*\\.getClass\\(\\).*", Pattern.CASE_INSENSITIVE), + Pattern.compile(".*\\.forName\\(.*", Pattern.CASE_INSENSITIVE)); + + @Value("${spring.quartz.security.enabled:true}") + private boolean securityEnabled; + + /** + * Validates if a class name is safe to use in reflection-based job execution + * + * @param className The fully qualified class name to validate + * @return true if the class is safe to use, false otherwise + */ + public boolean isClassSafe(String className) { + if (!securityEnabled) { + log.warn("Quartz security validation is DISABLED. This is a security risk!"); + return true; + } + + if (StrUtil.isBlank(className)) { + return false; + } + + // Check exact match against dangerous classes + if (DANGEROUS_CLASSES.contains(className)) { + log.error("Security violation: Attempt to use dangerous class: {}", className); + return false; + } + + // Check dangerous package prefixes + for (String prefix : DANGEROUS_PACKAGE_PREFIXES) { + if (className.startsWith(prefix)) { + log.error("Security violation: Attempt to use class from dangerous package: {}", className); + return false; + } + } + + return true; + } + + /** + * Validates if a method name is safe to invoke + * + * @param methodName The method name to validate + * @return true if the method is safe to invoke, false otherwise + */ + public boolean isMethodSafe(String methodName) { + if (!securityEnabled) { + return true; + } + + if (StrUtil.isBlank(methodName)) { + return false; + } + + if (DANGEROUS_METHODS.contains(methodName.toLowerCase())) { + log.error("Security violation: Attempt to invoke dangerous method: {}", methodName); + return false; + } + + return true; + } + + /** + * Validates if method parameters are safe + * + * @param paramValue The parameter value to validate + * @return true if the parameters are safe, false otherwise + */ + public boolean isParameterSafe(String paramValue) { + if (!securityEnabled) { + return true; + } + + if (StrUtil.isBlank(paramValue)) { + return true; + } + + for (Pattern pattern : DANGEROUS_PARAM_PATTERNS) { + if (pattern.matcher(paramValue).matches()) { + log.error("Security violation: Dangerous pattern detected in parameter: {}", paramValue); + return false; + } + } + + return true; + } + + /** + * Checks if a class belongs to an allowed package in strict mode + * + * @param className The class name to check + * @return true if in allowed package, false otherwise + */ + private boolean isInAllowedPackage(String className) { + // In strict mode, only allow application-specific packages + // Customize this list based on your application's safe job classes + List allowedPackages = Arrays.asList( + "com.pig4cloud.pigx.daemon.quartz.task.", // Application-specific task package + "com.pig4cloud.pigx.common.job.", // Common job utilities + "org.springframework.scheduling." // Spring scheduling utilities + ); + + for (String allowedPackage : allowedPackages) { + if (className.startsWith(allowedPackage)) { + return true; + } + } + + log.warn("Security violation (strict mode): Class {} not in allowed packages", className); + return false; + } + + /** + * Comprehensive validation of a job configuration + * + * @param className Class name to execute + * @param methodName Method name to invoke + * @param paramValue Method parameter value + * @return Validation error message, or null if validation passes + */ + public String validateJobConfig(String className, String methodName, String paramValue) { + if (!securityEnabled) { + return null; + } + + // Validate class name + if (!isClassSafe(className)) { + return "安全检查失败"; + } + + // Validate method name + if (!isMethodSafe(methodName)) { + return "安全检查失败"; + + } + + // Validate parameters + if (!isParameterSafe(paramValue)) { + return "安全检查失败"; + + } + + return null; + } +} diff --git a/pigx-visual/pigx-daemon-quartz/src/main/java/com/pig4cloud/pigx/daemon/quartz/util/SpringBeanTaskInvok.java b/pigx-visual/pigx-daemon-quartz/src/main/java/com/pig4cloud/pigx/daemon/quartz/util/SpringBeanTaskInvok.java index 0ec04a490..c717f5b65 100644 --- a/pigx-visual/pigx-daemon-quartz/src/main/java/com/pig4cloud/pigx/daemon/quartz/util/SpringBeanTaskInvok.java +++ b/pigx-visual/pigx-daemon-quartz/src/main/java/com/pig4cloud/pigx/daemon/quartz/util/SpringBeanTaskInvok.java @@ -38,8 +38,27 @@ import java.lang.reflect.Method; @Slf4j public class SpringBeanTaskInvok implements ITaskInvok { + private final JobSecurityValidator jobSecurityValidator; + + public SpringBeanTaskInvok(JobSecurityValidator jobSecurityValidator) { + this.jobSecurityValidator = jobSecurityValidator; + } + @Override public void invokMethod(SysJob sysJob) throws TaskException { + // Security validation before reflection + // 在执行反射之前进行安全验证 + String securityError = jobSecurityValidator.validateJobConfig( + sysJob.getClassName(), + sysJob.getMethodName(), + sysJob.getMethodParamsValue() + ); + + if (securityError != null) { + log.error("Security validation failed during job execution: {}", securityError); + throw new TaskException("安全验证失败: " + securityError); + } + Object target; Method method; Object returnValue; diff --git a/pigx-visual/pigx-daemon-quartz/src/main/resources/quartz-config.yml b/pigx-visual/pigx-daemon-quartz/src/main/resources/quartz-config.yml index 9a28ca024..9887d2e7f 100644 --- a/pigx-visual/pigx-daemon-quartz/src/main/resources/quartz-config.yml +++ b/pigx-visual/pigx-daemon-quartz/src/main/resources/quartz-config.yml @@ -1,6 +1,7 @@ # 如下配置为使用数据库存储定时任务,属性不经常修改,所以不放在 application.yml 中统一管理 spring: quartz: + security.enabled: true # 启用quartz安全验证 properties: org: quartz: