内存马学习之SpringBoot Interceptor内存马
2022-10-17 14:41:35 Author: 伟盾网络安全(查看原文) 阅读量:16 收藏

0x01 前言

内存马系列他又来了,之前就分析过了,一直没有记笔记,可能很多大佬都学过了,并不是为了重复造轮子,只是为了能搞懂轮子的结构,如果以后轮子坏了,也可以自己修,写的不好的地方多多包涵。

0x02 漏洞分析

01 springboot Interceptor介绍

拦截器(Interceptor)同 Filter 过滤器一样,可以使用 Interceptor 来执行某些任务,例如在 Controller 处理请求之前编写日志,添加或更新配置。

在 Spring中,当请求发送到 Controller 时,在被Controller处理之前,它必须经过 Interceptors(0或多个)。Spring Interceptor是一个非常类似于Servlet Filter 的概念 。

如何自定义Interceptor:

如果你需要自定义 Interceptor 的话必须实现 org.springframework.web.servlet.HandlerInterceptor接口或继承 org.springframework.web.servlet.handler.HandlerInterceptorAdapter类,并且需要重写下面下面 3 个方法:

preHandler(HttpServletRequest request, HttpServletResponse response, Object handler) 方法在请求处理之前被调用。该方法在 Interceptor 类中最先执行,用来进行一些前置初始化操作或是对当前请求做预处理,也可以进行一些判断来决定请求是否要继续进行下去。该方法的返回的是 Boolean 类型,当它返回 false 时,表示请求结束,后续的 Interceptor 和 Controller 都不会再执行;当它返回为 true 时会继续调用下一个 Interceptor 的 preHandle 方法,如果已经是最后一个 Interceptor 的时候就会调用当前请求的 Controller 方法。

postHandler(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) 方法在当前请求处理完成之后,也就是 Controller 方法调用之后执行,但是它会在 DispatcherServlet 进行视图返回渲染之前被调用,所以我们可以在这个方法中对 Controller 处理之后的 ModelAndView 对象进行操作。

afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex) 方法需要在当前对应的 Interceptor 类的 postHandler 方法返回值为 true 时才会执行。顾名思义,该方法将在整个请求结束之后,也就是在 DispatcherServlet 渲染了对应的视图之后执行。此方法主要用来进行资源清理。

02 拦截器调用过程分析

为了分析调用过程,我们这里需要自己写一个springboot demo,先是写一个拦截器类

package com.interceptor;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;
public class DemoInterceptor extends HandlerInterceptorAdapter {
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { response.getWriter().write("interceptor..."); return false; }
@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterCompletion..."); }}

然后写一个配置类,将拦截器注册到拦截器列表中

package com.config;
import com.interceptor.DemoInterceptor;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configurationpublic class webConfig implements WebMvcConfigurer {
@Override public void addInterceptors(InterceptorRegistry registry){ registry.addInterceptor(new DemoInterceptor()).addPathPatterns("/mem123"); }}

然后在 org.springframework.web.servlet.DispatcherServlet#doService 方法中打上断点,如图

以debug模式启动springboot主程序,程序启动后访问我们定义的拦截器路由 /mem123,触发debug

跟入 org.springframework.web.servlet.DispatcherServlet#doDispatch 方法

我们主要关注重要部分,其余的可以自行跟一下代码,跟入 org.springframework.web.servlet.DispatcherServlet#getHandler 方法

这里是通过迭代器循环遍历handlerMappings集合的值,这里有五个值,然后调用每个对象的getHandler方法,不同对象对应的方法是不一样的,然后判断handler是否不为null,如果不为null就返回handler,否则返回null

我们就不一一跟入了,这里主要分析下当mapping为 SimpleUrlHandlerMapping 时程序的执行过程,因为只有mapping为 SimpleUrlHandlerMapping 时,getHandler 才会返回我们需要的值

跟入 org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler 方法

跟入 org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#getHandlerInternal 方法

这里先获取到我们请求的路径 /mem123 ,然后调用 org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#lookupHandler 方法,查找对应的处理器,跟入

因为handlerMap中不存在/mem123,所以返回的是空,继续往下走

这里通过正则匹配,匹配到/**,然后将/**添加到matchingPatterns集合中,继续往下走到关键的一个点

在这里会生成一个 HandlerExecutionChain 对象,这个就是拦截器链式执行的核心类,跟入 org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#buildPathExposingHandler 方法

实例化一个 HandlerExecutionChain 对象,然后添加对应的拦截器,最后返回 HandlerExecutionChain 对象,然后根据调用栈依次返回该对象,回到 org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler 方法,往下走

跟入 org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandlerExecutionChain 方法

对传入的handler对象进行类型转换,然后获取我们访问的路由 /mem123,之后遍历 adaptedInterceptors 集合,该集合内容如图

继续往下走

当集合中的值为MappedInterceptor类型时,对其进行类型转换,然后进行路由匹配,主要就是比对includePatterns的值是否和我们访问的路由一致,匹配上的话就会获取MappedInterceptor的interceptor成员对象,也就是定义的拦截器,然后添加到拦截器链中,这里很显然是可以匹配上 /mem123 的,接着往下走

回到 org.springframework.web.servlet.DispatcherServlet#getHandler 方法,返回handler对象

可以看到拦截器链中已经添加了我们自定义的拦截器,继续往下走

回到 org.springframework.web.servlet.DispatcherServlet#doDispatch 方法中

这里就是调用拦截器的核心代码部分,调用 org.springframework.web.servlet.HandlerExecutionChain#applyPreHandle 方法,跟入

跟入

将所有拦截器转换为HandlerInterceptor对象数组,返回,继续往下走

通过for循环遍历,取到我们自定义的拦截器,最后调用preHandler方法,跟入

至此拦截器调用过程就结束了

03 构造自己的拦截器

和分析Filter内存马一样,我会选择逆着分析,找到每个需要赋值的对象,然后编写对应的实现代码

这里我们回到  org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandlerExecutionChain 方法

这里前面我们分析过,这里是通过访问路由去查询对应的MappedInterceptor,然后将其添加到拦截器链中,以供后面遍历使用。

MappedInterceptor

带有路径模式匹配的HandlerInterceptor,也提供了匹配逻辑来测试拦截器是否适用于一个给定的请求路径,其实也很好理解,就是根据请求路径来匹配对应的拦截器,做了一层封装 。

这里已知我们的拦截器是通过while循环添遍历adaptedInterceptors成员变量,当其中值的类型为MappedInterceptor时,进行路由匹配,如果匹配上那么就会将MappedInterceptor封装的interceptor放入拦截器链中,那么我们就可以反推,只要我们提前将我们自定义的拦截器通过MappedInterceptor进行一层封装,然后通过反射的方式存放到adaptedInterceptors成员变量中,那么再下一次访问的时候是不是就可以匹配到我们自定义的路由以及自定义的拦截器了呢,这是一个点我们要记一下。

那么我们就要看一下MappedInterceptor构造方法是什么样的以及每个属性表达的含义

MappedInterceptor类中共有四个成员变量,每个成员变量对应的含义如下

  • includePatterns:匹配路由,设置为我们自定义的路由

  • excludePatterns:不匹配路由,不需要添加为null即可

  • interceptor:拦截器对象,存放我们自定义拦截器

  • pathMatcher:路径匹配器

我们重点要设置的就是两个,includePatterns以及interceptor成员变量,通过第一个构造方法即可进行赋值设置

接下来我们要做的就是,获取当前运行环境的 org.springframework.web.servlet.handler.AbstractHandlerMapping 对象,然后通过反射获取到adaptedInterceptors成员变量,之后将封装好的 MappedInterceptor 对象存放到集合中,这样就可以完成我们自定义的拦截器的添加,是不是很简单

如何获取到 AbstractHandlerMapping 对象呢,这里就涉及到spring的ioc容器相关知识了

IOC:

Spring 启动时读取应用程序提供的Bean配置信息,并在Spring容器中生成一份相应的Bean配置注册表,然后根据这张注册表实例化Bean,装配好Bean之间的依赖关系,为上层应用提供准备就绪的运行环境。

其实就是将我们定义好的类实例化存放到容器中,我们可以根据Bean的名称取出对应的对象,这个是在程序启动时就注册好的,所以取出来的对象也就是当前运行环境的对象,我们只要对其进行修改即可立刻生效。

想要从IOC中获取对象,我们需要先获取到一个上下文对象,那就是 org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext 对象,该对象可以通过 org.springframework.web.context.support.WebApplicationContextUtils#getRequiredWebApplicationContext 方法获取,如图

返回值类型是其父类 WebApplicationContext ,使用多态完成子类AnnotationConfigServletWebServerApplicationContext 调用,该方法中需要传入一个参数 ServletContext,这个也难不倒我们,我们可以先获取request,然后再调用 org.apache.catalina.connector.Request#getServletContext 方法获取ServletContext对象,这样就可以实现一套完整流程

在springboot中获取request有一个便捷的方法

ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();HttpServletRequest request = requestAttributes.getRequest();

当然也可以通过当前线程从中一步一步反射获取

获取 AnnotationConfigServletWebServerApplicationContext 对象的完整写法如下

ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();HttpServletRequest request = requestAttributes.getRequest();ServletContext servletContext = request.getServletContext();WebApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);

这里有一个小插曲,本来我是想直接获取 AbstractHandlerMapping 对象的,然后修改 adaptedInterceptors 集合的值插入我们自定义的拦截器,但是后面没有在IOC容器中找到该对象,有点尴尬,所以我利用了多态的特性,获取到其子类 SimpleUrlHandlerMapping ,然后反射获取到 adaptedInterceptors 成员变量,最后修改集合的值,还有一点就是 adaptedInterceptors 成员变量是final修饰的,我们需要反射修改修饰符的值,再去修改集合的值,所以完整代码如下

try {    ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();    HttpServletRequest request = requestAttributes.getRequest();    ServletContext servletContext = request.getServletContext();    // 2. 使用WebApplicationContextUtils.getRequiredWebApplicationContext获取ApplicationContext    WebApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);    System.out.println(applicationContext);    AbstractHandlerMapping abstractHandlerMapping = (AbstractHandlerMapping) applicationContext.getBean(SimpleUrlHandlerMapping.class);    MappedInterceptor mappedInterceptor = new MappedInterceptor(new String[]{"/mem456"}, new DemoInterceptor());    Field adaptedInterceptors = abstractHandlerMapping.getClass().getSuperclass().getSuperclass().getDeclaredField("adaptedInterceptors");    adaptedInterceptors.setAccessible(true);    Field modifiers = adaptedInterceptors.getClass().getDeclaredField("modifiers");    modifiers.setAccessible(true);    modifiers.set(adaptedInterceptors,adaptedInterceptors.getModifiers() & ~Modifier.FINAL);    ArrayList InterceptorhandlerArr = (ArrayList) adaptedInterceptors.get(abstractHandlerMapping);    InterceptorhandlerArr.add(0,mappedInterceptor);    System.out.println(InterceptorhandlerArr);} catch (NoSuchFieldException e) {    e.printStackTrace();} catch (IllegalAccessException e) {    e.printStackTrace();}

执行效果如图

0x03 总结

通过这段时间对内存马的研究,发现核心就是三个点,以拦截器为例:一是拦截器如何链式执行,完整执行流程、二是链式执行过程中需要哪些变量,是否可以反射获取/修改、三就是构造自己的拦截器内存马,知识的综合运用,可能只代表个人观点,供大家学习参考。

0x04 参考文章

Spring IOC原理(https://zhuanlan.zhihu.com/p/29344811)

Spring Boot中的HandlerMapping(https://blog.csdn.net/kaerbuka/article/details/105399821)


文章来源: http://mp.weixin.qq.com/s?__biz=MzkwOTIxNzQ5OA==&mid=2247484545&idx=1&sn=5655ec36a4c2e9ac0816acac0a810cab&chksm=c13f5f34f648d622bc47a1998276b196e82954eac0459dbe9c4dc8f2ac4a0b9d71984b79275e#rd
如有侵权请联系:admin#unsafe.sh