Tomcat Filter型内存马
Filter使用记录
如Servlet一样,也是需要写代码的
重写doFilter,对HTTP请求和响应做一层处理即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package com.example.javaweb3;
import javax.servlet.*; import java.io.IOException; import java.util.logging.LogRecord;
public class HelloFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException { System.out.println("Filter init"); }
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("doFilter"); filterChain.doFilter(servletRequest,servletResponse); }
public void destroy(){ System.out.println("Filter destroy"); }
}
|
Filter注册
在web.xml中进行filter的注册
绑定到指定的Servlet服务或者URL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="<http://xmlns.jcp.org/xml/ns/javaee>" xmlns:xsi="<http://www.w3.org/2001/XMLSchema-instance>" xsi:schemaLocation="<http://xmlns.jcp.org/xml/ns/javaee> <http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd>" version="4.0">
<filter> <filter-name>hello</filter-name> <filter-class>com.example.javaweb3.HelloFilter</filter-class> </filter> <filter-mapping> <filter-name>hello</filter-name> <servlet-name>helloServlet</servlet-name> <url-pattern>/</url-pattern> </filter-mapping>
</web-app>
|
记得在porm.xml中引入tomcat依赖,调试的时候step into有用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| <?xml version="1.0" encoding="UTF-8"?> <project xmlns="<http://maven.apache.org/POM/4.0.0>" xmlns:xsi="<http://www.w3.org/2001/XMLSchema-instance>" xsi:schemaLocation="<http://maven.apache.org/POM/4.0.0> <https://maven.apache.org/xsd/maven-4.0.0.xsd>"> <modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId> <artifactId>javaweb3</artifactId> <version>1.0-SNAPSHOT</version> <name>javaweb3</name> <packaging>war</packaging>
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.source>1.8</maven.compiler.source> <junit.version>5.9.2</junit.version> </properties>
<dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency>
<!-- <https: <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-catalina</artifactId> <version>9.0.91</version> <scope>provided</scope> </dependency>
<dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-websocket</artifactId> <version>9.0.91</version> </dependency>
</dependencies>
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>3.3.2</version> </plugin> </plugins> </build> </project>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException;
@WebFilter(filterName = "TestFilter", urlPatterns = "/test") public class TestFilter implements Filter { public void init(FilterConfig config) throws ServletException{ }
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("test"); filterChain.doFilter(servletRequest, servletResponse); }
public void destroy(){
} }
|
Filter调试
可以看下函数调用栈,最后一步是一个ApplicationFilterChain
在我们自实现的Filter中,下断点到doFilter
1 2 3 4
| public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("doFilter"); filterChain.doFilter(servletRequest,servletResponse); }
|
然后进入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if (Globals.IS_SECURITY_ENABLED) { ServletRequest req = request; ServletResponse res = response; try { AccessController.doPrivileged(() -> { this.internalDoFilter(req, res); return null; }); } catch (PrivilegedActionException var7) { Exception e = var7.getException(); if (e instanceof ServletException) { throw (ServletException)e; }
if (e instanceof IOException) { throw (IOException)e; }
if (e instanceof RuntimeException) { throw (RuntimeException)e; }
throw new ServletException(e.getMessage(), e); } } else { this.internalDoFilter(request, response); }
}
|
判断是否启用安全模式,然后调用internalDoFilter
在里面判断了Filters组的索引关系,然后决定是否要调用下一个Filter,否则直接进入else代码块
在先前的Filter中,首先通过ApplicationFilterConfig对象获取filters数组中的特定filter
再通过一个ApplicationFilterConfig的getter方法获取filter
看下getter,这里判断是否为空,空的话则利用反射机制创建filter对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { if (this.pos < this.n) { ApplicationFilterConfig filterConfig = this.filters[this.pos++];
try { Filter filter = filterConfig.getFilter(); if (request.isAsyncSupported() && !filterConfig.getFilterDef().getAsyncSupportedBoolean()) { request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", Boolean.FALSE); }
if (Globals.IS_SECURITY_ENABLED) { Principal principal = ((HttpServletRequest)request).getUserPrincipal(); Object[] args = new Object[]{request, response, this}; SecurityUtil.doAsPrivilege("doFilter", filter, classType, args, principal); } else { filter.doFilter(request, response, this); }
} catch (ServletException | RuntimeException | IOException var15) { throw var15; } catch (Throwable var16) { Throwable e = ExceptionUtils.unwrapInvocationTargetException(var16); ExceptionUtils.handleThrowable(e); throw new ServletException(sm.getString("filterChain.filter"), e); } } else { try { if (ApplicationDispatcher.WRAP_SAME_OBJECT) { lastServicedRequest.set(request); lastServicedResponse.set(response); } if (request.isAsyncSupported() && !this.servletSupportsAsync) { request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", Boolean.FALSE); } if (request instanceof HttpServletRequest && response instanceof HttpServletResponse && Globals.IS_SECURITY_ENABLED) { Principal principal = ((HttpServletRequest)request).getUserPrincipal(); Object[] args = new Object[]{request, response}; SecurityUtil.doAsPrivilege("service", this.servlet, classTypeUsedInService, args, principal); } else { this.servlet.service(request, response); } } catch (ServletException | RuntimeException | IOException var17) { throw var17; } catch (Throwable var18) { Throwable e = ExceptionUtils.unwrapInvocationTargetException(var18); ExceptionUtils.handleThrowable(e); throw new ServletException(sm.getString("filterChain.servlet"), e); } finally { if (ApplicationDispatcher.WRAP_SAME_OBJECT) { lastServicedRequest.set((Object)null); lastServicedResponse.set((Object)null); }
}
} }
|
然后最终调用service进行响应
Filter 相关对象和属性
在编写内存马之前, 我们先梳理一下之前出现的各种以 Filter 开头的对象
- FilterMap: 存在 filterName 和 urlPatterns 属性, 对应 Filter 的名称和匹配规则
- FilterConfig: 这里具体指 ApplicationFilterConfig, 存在 Filter 和 FilterDef 属性, 其中 Filter 在构造函数中通过 filterDef.getFilter() 取得
- FilterDef: 存在 filter filterClass filterName 属性, 其中 filter 为被调用的 Filter 实例
然后是位于 StandardContext 中的以 filter 开头的属性
- filterMaps: 本质为 Array, 存放 FilterMap
- filterConfigs: 本质为 HashMap, key 为 filterMap 的 filterName, value 为对应的 FilterConfig
- filterDefs: 本质为 HashMap, key 为 filterMap 的 filterName, value 为对应的 FilterDef
根据上面加载流程, 我们注入内存马的过程为
- 在 StandardContext 的 filterDefs 中添加 FilterDef (validateFilterMap 验证)
- 向 filterMaps 中添加 FilterMap
- 将对应的 FilterConfig (包含 FilterDef) 添加到 filterConfigs
Filter型内存马
基本原理就是在程序动态执行时打入新的恶意filter,然后添加filter-map,让filter中接受执行参数
容器类
来看下AI的解释:
管理Filter的对应容器类为StandContext
如何添加映射
而处理filter中的对应容器类为StandContext,关注下添加映射的方法
使用类加载机制获取,直接报空指针错误。。。
那就使用反射机制来获取该容器对象
- ApplicationFilterConfig的结构
这即对应的一个filter的结构,我们需要完成的是filterDef部分
EXP
过程如下:
在 Java 中,如果没有显式地指定访问修饰符(如 public
、private
、protected
或默认的包访问权限),那么该成员函数或构造函数的访问权限是由其所在的类和包的关系决定的。
具体来说,如果没有指定访问修饰符,构造函数或方法的访问权限是包访问权限(package-private)。这意味着:
- 该构造函数或方法对同一包中的其他类是可见的。
- 但对不同包中的类是不可见的。
参考
- https://exp10it.io/2022/11/tomcat-filter-%E5%9E%8B%E5%86%85%E5%AD%98%E9%A9%AC%E5%88%86%E6%9E%90/#filter-%E7%9B%B8%E5%85%B3%E5%AF%B9%E8%B1%A1%E5%92%8C%E5%B1%9E%E6%80%A7