Tomcat Filter型内存马
2024-12-21 20:11:41

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注明

在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>

image-20241218211935468

记得在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://mvnrepository.com/artifact/org.apache.tomcat/tomcat-catalina> -->
<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>
  • @WebFilter注解
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

image-20241218211953244

在我们自实现的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 {
// 检查Globals.IS_SECURITY_ENABLED常量 判断当前是否启动了安全功能
if (Globals.IS_SECURITY_ENABLED) {
ServletRequest req = request;
ServletResponse res = response;

try {
// 特权下执行internalDoFilter
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

image-20241218212013178

看下getter,这里判断是否为空,空的话则利用反射机制创建filter对象

image-20241218212024346

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) {
// 如果需要包装相同的对象, 当前的请求request和响应response会通过线程安全的方式存储起来
lastServicedRequest.set(request);
lastServicedResponse.set(response);
}
// 处理异步支持
if (request.isAsyncSupported() && !this.servletSupportsAsync) {
request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", Boolean.FALSE);
}

// 查看是否需要特权执行, 调用service进行响应
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

根据上面加载流程, 我们注入内存马的过程为

  1. 在 StandardContext 的 filterDefs 中添加 FilterDef (validateFilterMap 验证)
  2. 向 filterMaps 中添加 FilterMap
  3. 将对应的 FilterConfig (包含 FilterDef) 添加到 filterConfigs

Filter型内存马

基本原理就是在程序动态执行时打入新的恶意filter,然后添加filter-map,让filter中接受执行参数

容器类

来看下AI的解释:

  • 在Java中,容器类(Container Class)是指可以存储和组织对象的类,它们提供了一种高效的方式来管理、存储和检索数据,这些类大多处于java.util包中

    容器类主要分为:

    • 集合类(Collections)
    • 映射类(Maps)

管理Filter的对应容器类为StandContext

如何添加映射

而处理filter中的对应容器类为StandContext,关注下添加映射的方法

image-20241218212126617

使用类加载机制获取,直接报空指针错误。。。

image-20241218212114226

那就使用反射机制来获取该容器对象

  • ApplicationFilterConfig的结构

这即对应的一个filter的结构,我们需要完成的是filterDef部分

image-20241218212102696

EXP

image-20241218212052727

过程如下:

  • 获取StandContext

在 Java 中,如果没有显式地指定访问修饰符(如 publicprivateprotected 或默认的包访问权限),那么该成员函数或构造函数的访问权限是由其所在的类和包的关系决定的。

具体来说,如果没有指定访问修饰符,构造函数或方法的访问权限是包访问权限(package-private)。这意味着:

  • 该构造函数或方法对同一包中的其他类是可见的。
  • 但对不同包中的类是不可见的。
1

参考

  1. 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
Prev
2024-12-21 20:11:41
Next