mirror of
http://git.pig4cloud.com/pig/pigx.git
synced 2026-11-08 12:39:05 +08:00
feat(quartz): 新增Job安全验证机制
- 新增JobSecurityValidator类用于防止RCE漏洞 - 在SysJobServiceImpl中集成安全验证逻辑 - 为JavaClassTaskInvok和SpringBeanTaskInvok添加安全验证 - 在quartz-config.yml中新增安全验证开关配置
This commit is contained in:
1 parent
99d295bfa8
commit
1bb0af7b52
5 files changed
+329
No files matched your search
+22
@@ -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())) {
|
||||
|
||||
|
||||
+19
@@ -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;
|
||||
|
||||
+268
@@ -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;
|
||||
}
|
||||
}
|
||||
+19
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user