CodeQL Study 之 RuoYi-4.7.8 漏洞分析
字数 1637 2025-08-22 22:47:39
CodeQL 分析 RuoYi-4.7.8 漏洞研究
1. 背景介绍
RuoYi 是一个基于 Spring Boot 和 Apache Shiro 的权限管理系统,4.7.8 版本中存在一些安全漏洞。本文使用 CodeQL 对这些漏洞进行分析。
2. 漏洞分析环境搭建
2.1 创建 CodeQL 数据库
codeql database create ruoyi-database --language=java --command='mvn clean package -f "pom.xml"'
注意:路径不能包含中文
3. 定时任务功能分析
3.1 调用方式
后台定时任务支持两种调用方式:
- Bean 调用:需要添加对应 Bean 注解
@Component或@Service - Class 类调用:添加类和方法指定包即可,调用目标字符串
3.2 安全限制
黑名单限制
在 com.ruoyi.quartz.controller.SysJobController#addSave 中定义:
public static final String LOOKUP_RMI = "rmi:";
public static final String LOOKUP_LDAP = "ldap:";
public static final String LOOKUP_LDAPS = "ldaps:";
public static final String HTTP = "http://";
public static final String HTTPS = "https://";
public static final String[] JOB_ERROR_STR = {
"java.net.URL",
"javax.naming.InitialContext",
"org.yaml.snakeyaml",
"org.springframework",
"org.apache",
"com.ruoyi.common.utils.file",
"com.ruoyi.common.config"
};
白名单限制
public static final String[] JOB_WHITELIST_STR = {
"com.ruoyi"
};
3.3 定时任务执行逻辑
位于 com.ruoyi.quartz.util.JobInvokeUtil#invokeMethod:
- 获取 beanName、methodName、methodParams 等值
- 反射调用方法
关键限制条件:
- 若是 Spring 容器中注册过的 bean,可直接从容器中取出
- 若是指定 class 名称,会通过反射
newInstance()创建对象,因此必须保证 class 中存在无参构造函数 - 方法必须是 public 修饰的方法
- 方法参数类型只能为 String、Boolean、Long、Double、Integer
4. CodeQL 查询分析
4.1 查询语句
import java
from Method m
where
m.getDeclaringType().getAConstructor().hasNoParameters() and
m.isPublic() and
m.getAParamType() instanceof TypeString and
m.getDeclaringType().getPackage().getName().matches("com.ruoyi%") and
not m.isAbstract() and
not m.getDeclaringType().getPackage().getName().matches("com.ruoyi.common.config%") and
not m.getDeclaringType().getPackage().getName().matches("com.ruoyi.common.utils.file%") and
not m.getName().matches("get%") and
not m.getName().matches("is%") and
not m.getName().matches("has%")
select m.getDeclaringType().getPackage().getName(), m.getDeclaringType(), m
4.2 查询结果分析
4.2.1 SSRF 漏洞
com.ruoyi.common.utils.http.HttpUtils#sendGet 是一个无回显的 SSRF:
- 可以使用 file、ftp、jar 等协议
- 由于禁用了 http 和 https,利用价值有限
示例利用:
com.ruoyi.common.utils.http.HttpUtils.sendGet("ftp://d8lmq0s1.dnslog.pw")
4.2.2 SQL 注入漏洞
com.ruoyi.generator.service.impl.GenTableServiceImpl#createTable:
- 跟进到文件
GenTableMapper.xml - MyBatis 支持两种参数符号:
#:使用预编译向占位符中设置值,可有效防止 SQL 注入$:拼接 SQL,是触发 SQL 注入的关键
利用方式:
genTableServiceImpl.createTable('UPDATE sys_job SET invoke_target = 0x6a61... WHERE job_id = 1;')
这样可以绕过新建任务的限制,直接修改数据库中的内容。
5. RCE 利用分析
5.1 黑名单类分析
黑名单中禁止的类和方法:
javax.naming.InitialContext#lookuporg.yaml.snakeyaml.Yaml#loadorg.springframework.jndi.JndiLocatorDelegate#lookuporg.springframework.jdbc.datasource.lookup.JndiDataSourceLookup#getDataSourceorg.apache.velocity.runtime.RuntimeInstance#init
5.2 可利用的攻击方式
- SnakeYAML 反序列化:虽然
org.yaml.snakeyaml在黑名单中,但可能有绕过方式 - JNDI 注入:通过其他方式触发 JNDI 查找,结合反序列化
6. 防御建议
- 加强黑名单过滤,考虑使用更严格的正则表达式
- 对 SQL 操作强制使用预编译参数
- 限制反射调用的范围
- 对定时任务的目标方法进行更严格的权限控制
- 定期更新依赖库,修复已知漏洞
7. 总结
通过 CodeQL 分析,我们发现了 RuoYi 4.7.8 版本中存在的多个安全问题,包括 SSRF、SQL 注入和潜在的 RCE 漏洞。这些漏洞主要源于不安全的反射调用、不充分的输入过滤和 SQL 拼接等问题。开发者应当重视这些安全问题,采取相应的防护措施。