定时任务白名单机制防止代码注入
定时任务白名单机制防止代码注入
引言
定时任务是后端系统中常见的功能模块,用于执行数据同步、报表生成、缓存刷新等周期性操作。在低代码平台中,用户可能通过管理界面动态创建和管理定时任务,这带来了一个严重的安全隐患:代码注入攻击。
如果定时任务框架允许执行任意类和方法,攻击者可以通过创建恶意任务来执行系统命令、读取敏感文件、甚至获取服务器控制权。本文分享一种基于白名单机制的定时任务安全防护方案,从数据库配置、创建校验到运行时检查,全链路保障定时任务的执行安全。
核心内容
一、安全威胁分析
flowchart TB
A[定时任务安全威胁] --> B[任意类执行]
A --> C[系统命令注入]
A --> D[敏感信息泄露]
A --> E[权限提升攻击]
B --> B1["Runtime.exec('rm -rf /')"]
C --> C1["ProcessBuilder 执行 Shell"]
D --> D1["读取 /etc/passwd"]
E --> E1["通过反射调用特权方法"]
style A fill:#ff6b6b,color:#fff
style B fill:#ffa07a
style C fill:#ffa07a
style D fill:#ffa07a
style E fill:#ffa07a
常见的攻击场景:
// 攻击示例:通过定时任务执行系统命令
// 任务配置:beanName=java.lang.Runtime, methodName=exec, params=rm -rf /
// 如果没有白名单限制,这种任务会被直接执行
// 攻击示例:通过反射获取敏感信息
// 任务配置:beanName=java.lang.Class, methodName=getRuntime
二、白名单机制整体设计
flowchart TB
subgraph 任务创建阶段
A[用户提交任务配置] --> B{beanName+method 在白名单中?}
B -->|否| C[拒绝创建,返回错误提示]
B -->|是| D[保存任务到数据库]
end
subgraph 任务执行阶段
E[调度器触发任务] --> F{运行时校验白名单}
F -->|不在白名单| G[拒绝执行,记录安全日志]
F -->|在白名单| H[反射调用目标方法]
H --> I[执行结果记录]
end
subgraph 白名单管理
J[数据库配置白名单] --> K[应用启动时加载缓存]
K --> L[管理界面维护]
L --> M[动态刷新缓存]
end
style C fill:#ff6b6b,color:#fff
style G fill:#ff6b6b,color:#fff
三、白名单数据模型
3.1 数据库表设计
-- 定时任务白名单表
CREATE TABLE sys_job_whitelist (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
bean_name VARCHAR(200) NOT NULL COMMENT '允许执行的Bean名称(全限定类名或Spring Bean名)',
method_name VARCHAR(100) NOT NULL COMMENT '允许执行的方法名',
description VARCHAR(500) COMMENT '方法功能描述',
params_schema JSON COMMENT '参数格式说明(JSON Schema)',
status TINYINT DEFAULT 1 COMMENT '状态:0-禁用 1-启用',
create_by VARCHAR(64) COMMENT '创建人',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_by VARCHAR(64) COMMENT '更新人',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
UNIQUE KEY uk_bean_method (bean_name, method_name)
) COMMENT '定时任务白名单配置表';
3.2 白名单初始数据
-- 预置常用任务白名单
INSERT INTO sys_job_whitelist (bean_name, method_name, description) VALUES
('dataSyncTask', 'syncUserData', '同步用户数据'),
('dataSyncTask', 'syncDeptData', '同步部门数据'),
('reportTask', 'generateDailyReport', '生成日报表'),
('reportTask', 'generateMonthlyReport', '生成月报表'),
('cacheTask', 'refreshDictCache', '刷新字典缓存'),
('cacheTask', 'refreshConfigCache', '刷新配置缓存'),
('logTask', 'cleanExpiredLogs', '清理过期日志'),
('logTask', 'archiveOldLogs', '归档历史日志');
四、白名单校验实现
4.1 白名单实体与缓存
/**
* 定时任务白名单实体
*/
@Data
@TableName("sys_job_whitelist")
public class JobWhitelist {
@TableId(type = IdType.AUTO)
private Long id;
/** 允许执行的Bean名称 */
private String beanName;
/** 允许执行的方法名 */
private String methodName;
/** 方法功能描述 */
private String description;
/** 参数格式说明 */
private String paramsSchema;
/** 状态:0-禁用 1-启用 */
private Integer status;
}
/**
* 白名单缓存服务
* 应用启动时加载到内存,变更时动态刷新
*/
@Component
public class JobWhitelistCache {
/** 白名单缓存 key: beanName#methodName */
private volatile Set<String> whitelistSet = new HashSet<>();
@Autowired
private JobWhitelistMapper whitelistMapper;
/**
* 初始化加载白名单
*/
@PostConstruct
public void init() {
refreshCache();
}
/**
* 刷新缓存
*/
public void refreshCache() {
List<JobWhitelist> list = whitelistMapper.selectList(
new LambdaQueryWrapper<JobWhitelist>().eq(JobWhitelist::getStatus, 1)
);
Set<String> newSet = list.stream()
.map(w -> w.getBeanName() + "#" + w.getMethodName())
.collect(Collectors.toSet());
this.whitelistSet = newSet;
}
/**
* 校验是否在白名单中
* @param beanName Bean名称
* @param methodName 方法名
* @return true-在白名单中 false-不在白名单
*/
public boolean isAllowed(String beanName, String methodName) {
String key = beanName + "#" + methodName;
return whitelistSet.contains(key);
}
}
4.2 任务创建时校验
/**
* 定时任务服务
*/
@Service
public class JobService {
@Autowired
private JobWhitelistCache whitelistCache;
@Autowired
private JobMapper jobMapper;
/**
* 创建定时任务
* 创建前校验 beanName + methodName 是否在白名单中
*/
public void createJob(JobDTO jobDTO) {
// 1. 白名单校验
if (!whitelistCache.isAllowed(jobDTO.getBeanName(), jobDTO.getMethodName())) {
throw new BusinessException(
"不允许执行该任务:[%s.%s] 不在白名单中,请联系管理员添加",
jobDTO.getBeanName(), jobDTO.getMethodName()
);
}
// 2. 参数格式校验(如果白名单配置了参数Schema)
validateParams(jobDTO);
// 3. Cron 表达式校验
validateCron(jobDTO.getCronExpression());
// 4. 保存任务
Job job = new Job();
BeanUtils.copyProperties(jobDTO, job);
jobMapper.insert(job);
}
}
4.3 任务执行时校验
即使数据库中的任务被篡改(如直接修改数据库),执行时仍需二次校验:
/**
* 定时任务执行器
* 运行时二次校验白名单,防止数据库被直接篡改
*/
@Component
public class JobExecutor {
@Autowired
private JobWhitelistCache whitelistCache;
@Autowired
private ApplicationContext applicationContext;
/**
* 执行定时任务
*/
public void execute(Job job) {
String beanName = job.getBeanName();
String methodName = job.getMethodName();
// 运行时白名单校验(双重保障)
if (!whitelistCache.isAllowed(beanName, methodName)) {
logSecurityEvent(job, "任务不在白名单中,拒绝执行");
throw new SecurityException("非法任务执行被拦截");
}
try {
// 通过 Spring 容器获取 Bean 实例
Object target = applicationContext.getBean(beanName);
// 反射调用方法(仅允许无参或简单参数方法)
Method method = findMethod(target.getClass(), methodName, job.getParams());
method.setAccessible(true);
Object result = method.invoke(target, parseParams(job.getParams()));
log.info("任务执行成功: {}.{}", beanName, methodName);
} catch (Exception e) {
log.error("任务执行失败: {}.{}", beanName, methodName, e);
throw new RuntimeException("任务执行异常", e);
}
}
/**
* 记录安全事件
*/
private void logSecurityEvent(Job job, String reason) {
log.warn("【安全告警】定时任务被拦截 - 任务ID:{}, Bean:{}, Method:{}, 原因:{}",
job.getId(), job.getBeanName(), job.getMethodName(), reason);
// 可接入安全审计系统
}
}
五、白名单管理界面
flowchart TB
subgraph 白名单管理
A[管理员进入白名单管理页] --> B[查看当前白名单列表]
B --> C{操作}
C -->|新增| D[填写Bean名+方法名+描述]
D --> E[保存并刷新缓存]
C -->|编辑| F[修改描述/参数Schema]
F --> E
C -->|禁用| G[设置status=0]
G --> E
C -->|删除| H[确认后删除记录]
H --> E
end
subgraph 缓存同步
E --> I[更新数据库]
I --> J[刷新内存缓存]
J --> K[集群节点同步通知]
end
5.1 白名单管理接口
/**
* 白名单管理控制器
*/
@RestController
@RequestMapping("/api/job/whitelist")
public class JobWhitelistController {
@Autowired
private JobWhitelistService whitelistService;
/** 查询白名单列表 */
@GetMapping("/list")
public Result<List<JobWhitelist>> list() {
return Result.success(whitelistService.list());
}
/** 新增白名单 */
@PostMapping
public Result<Void> add(@RequestBody JobWhitelistDTO dto) {
// 校验 Bean 是否存在于 Spring 容器中
whitelistService.validateBeanExists(dto.getBeanName(), dto.getMethodName());
whitelistService.add(dto);
return Result.success();
}
/** 刷新缓存 */
@PostMapping("/refresh")
public Result<Void> refreshCache() {
whitelistService.refreshCache();
return Result.success();
}
}
六、集群环境缓存同步
在多节点部署时,白名单缓存需要保持一致:
/**
* 基于Redis Pub/Sub的缓存同步
*/
@Component
public class WhitelistCacheSync {
private static final String CHANNEL = "job:whitelist:refresh";
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
private JobWhitelistCache whitelistCache;
/**
* 发布缓存刷新通知
*/
public void publishRefresh() {
redisTemplate.convertAndSend(CHANNEL, "refresh");
}
/**
* 订阅缓存刷新通知
*/
@RedisListener(channel = CHANNEL)
public void onRefreshMessage(String message) {
if ("refresh".equals(message)) {
whitelistCache.refreshCache();
log.info("白名单缓存已刷新");
}
}
}
七、安全纵深防护
白名单是核心防线,但还需配合其他安全措施:
flowchart LR
A[安全防护体系] --> B[白名单校验]
A --> C[参数格式校验]
A --> D[执行超时控制]
A --> E[安全审计日志]
A --> F[数据库防篡改]
B --> B1[创建时校验]
B --> B2[执行时校验]
C --> C1[参数Schema验证]
D --> D1[超时自动终止]
E --> E1[操作全量记录]
F --> F1[关键字段签名校验]
7.1 任务参数格式校验
/**
* 参数格式校验
* 防止通过参数注入恶意内容
*/
private void validateParams(JobDTO jobDTO) {
String params = jobDTO.getParams();
if (StringUtils.isBlank(params)) return;
// 1. 检查参数长度
if (params.length() > 1000) {
throw new BusinessException("任务参数长度不能超过1000字符");
}
// 2. 检查是否包含危险字符
String[] dangerousPatterns = {
"Runtime", "ProcessBuilder", "exec",
"getClass", "forName", "invoke",
"../", "::", "$$"
};
for (String pattern : dangerousPatterns) {
if (params.contains(pattern)) {
throw new BusinessException("任务参数包含非法内容");
}
}
// 3. 检查参数是否为合法JSON
try {
JsonParser.parseString(params);
} catch (Exception e) {
throw new BusinessException("任务参数格式错误,必须为合法JSON");
}
}
结论与建议
核心要点
- 白名单是定时任务安全的核心防线:只允许执行预先审核通过的类和方法,从根本上杜绝代码注入
- 双重校验机制:创建时校验 + 执行时校验,防止绕过管理界面直接篡改数据库
- 动态缓存管理:白名单变更后实时刷新,集群环境下通过消息通知同步
- 纵深防护:白名单 + 参数校验 + 超时控制 + 审计日志,多层安全防护
最佳实践建议
| 建议 | 说明 |
|---|---|
| 最小权限原则 | 白名单只添加确实需要定时执行的任务,宁少勿多 |
| 定期审计 | 定期审查白名单,移除不再使用的任务 |
| 安全审计日志 | 所有任务创建、修改、执行操作全量记录 |
| 参数白名单 | 不仅限制类和方法,参数格式也要严格校验 |
| 集群同步 | 多节点部署时确保白名单缓存一致性 |
| 数据库权限 | 限制直接修改任务表的数据库账号权限 |