Skip to content

JobInvokeUtil 疑似存在不安全反射调用导致的远程代码执行漏洞 (CWE-470/CWE-502) #318

@weaver4VD

Description

@weaver4VD
模块:

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 任务通常以较高权限运行,该漏洞可导致应用所在的服务器被彻底接管。

建议的修复

必须限制反射调用的范围。

推荐修复方案:

  1. 引入类名白名单:只允许反射调用特定的包或标注了特定注解的类。
  2. 禁止危险包前缀:显式禁止加载 java.lang., javax., org.springframework. 等核心系统包。
  3. 安全校验增强
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("检测到非法调用目标");
    }
    
    // ... 原有逻辑
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions