feat(quartz): 新增Job安全验证机制

- 新增JobSecurityValidator类用于防止RCE漏洞
- 在SysJobServiceImpl中集成安全验证逻辑
- 为JavaClassTaskInvok和SpringBeanTaskInvok添加安全验证
- 在quartz-config.yml中新增安全验证开关配置
This commit is contained in:
claudecode committed 2025-10-26 21:32:32 +08:00
1 parent 99d295bfa8
commit 1bb0af7b52
5 files changed
+329

No files matched your search

@@ -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<SysJobMapper, SysJob> implements SysJobService {
private final JobSecurityValidator jobSecurityValidator;
/**
* 检查任务配置
*
@@ -55,6 +58,25 @@ public class SysJobServiceImpl extends ServiceImpl<SysJobMapper, SysJob> 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())) {
@@ -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;
@@ -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
* <p>
* 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<String> 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<String> 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<String> 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<Pattern> 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<String> 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;
}
}
@@ -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;
@@ -1,6 +1,7 @@
# 如下配置为使用数据库存储定时任务,属性不经常修改,所以不放在 application.yml 中统一管理
spring:
quartz:
security.enabled: true # 启用quartz安全验证
properties:
org:
quartz: