-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Open
Description
模块:
ruoyi-quartz
核心类:
com.ruoyi.quartz.util.JobInvokeUtil
问题描述:
核心问题:JobInvokeUtil.invokeMethod 方法在执行定时任务时,直接使用 SysJob 对象中的 invokeTarget 字符串来动态加载类并调用方法。
由于 invokeTarget 通常由管理员在后台界面配置并存储于数据库,且系统对该字符串的校验仅限于长度和非空(@Size, @NotBlank),未对目标类名(beanName)进行任何白名单限制。攻击者可以构造包含恶意类名和方法的字符串(例如 java.lang.Runtime.getRuntime().exec(...)),利用 Class.forName().newInstance() 触发任意代码执行。
调用过程分析
AbstractQuartzJob.execute(context)
⬇
BeanUtils.copyBeanProp(sysJob, ...) // 从 Quartz 上下文加载 job 数据
⬇
JobInvokeUtil.invokeMethod(sysJob)
⬇
String beanName = getBeanName(invokeTarget) // 提取类名/Bean名
⬇
if (isValidClassName(beanName)) // 仅检查 '.' 的数量是否 > 1
⬇
Class.forName(beanName).getDeclaredConstructor().newInstance() // 漏洞点:任意类实例化
⬇
invokeMethod(bean, methodName, methodParams) // 漏洞点:反射执行任意方法
相关代码分析
漏洞的根本原因在于防御性逻辑过于薄弱:
// JobInvokeUtil.java
public static void invokeMethod(SysJob sysJob) throws Exception {
String invokeTarget = sysJob.getInvokeTarget();
String beanName = getBeanName(invokeTarget);
// ...
if (!isValidClassName(beanName)) {
// 从 Spring 容器获取 Bean
Object bean = SpringUtils.getBean(beanName);
invokeMethod(bean, methodName, methodParams);
} else {
// 缺陷点:这里没有任何类名白名单检查
// 只要满足 xxx.xxx.xxx 格式(isValidClassName 返回 true),就能加载任意类
Object bean = Class.forName(beanName).getDeclaredConstructor().newInstance();
invokeMethod(bean, methodName, methodParams);
}
}
// 极其脆弱的校验逻辑
public static boolean isValidClassName(String invokeTarget) {
// 仅仅检查点号数量,这无法阻止攻击者加载 java.lang.Runtime 等危险类
return StringUtils.countMatches(invokeTarget, ".") > 1;
}可能的危害
- 远程代码执行 (RCE):攻击者可以调用系统命令、读写敏感文件、开启反弹 Shell。
- 完全控制服务器:由于 Quartz 任务通常以较高权限运行,该漏洞可导致应用所在的服务器被彻底接管。
建议的修复
必须限制反射调用的范围。
推荐修复方案:
- 引入类名白名单:只允许反射调用特定的包或标注了特定注解的类。
- 禁止危险包前缀:显式禁止加载
java.lang.,javax.,org.springframework.等核心系统包。 - 安全校验增强:
public static void invokeMethod(SysJob sysJob) throws Exception {
String invokeTarget = sysJob.getInvokeTarget();
String beanName = getBeanName(invokeTarget);
// 新增:黑名单检查
if (StringUtils.containsAnyIgnoreCase(beanName, new String[]{"LDAP://", "http://", "https://", "java.lang.Runtime"})) {
throw new Exception("检测到非法调用目标");
}
// ... 原有逻辑
}Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels