Java 内存马第二篇 - Tomcat 内存马

二、Tomcat 内存马

1. Filter 内存马

Filter 我们称之为过滤器,是 Java 中最常见也最实用的技术之一,通常被用来处理静态 web 资源、访问权限控制、记录日志等附加功能等等。一次请求进入到服务器后,将先由 Filter 对用户请求进行预处理,再交给 Servlet。

通常情况下,Filter 配置在配置文件和注解中,在其他代码中如果想要完成注册,主要有以下几种方式:

  1. 使用 ServletContext 的 addFilter/createFilter 方法注册;
  2. 使用 ServletContextListener 的 contextInitialized 方法在服务器启动时注册(将会在 Listener 中进行描述);
  3. 使用 ServletContainerInitializer 的 onStartup 方法在初始化时注册(非动态,后面会描述)。

1.1 环境创建

web.xml:

自定义一个 Filter:

img

1.2 流程分析

在访问 /filter 之后的流程分析

断点调试

img

ApplicationFilterChain.dofilter

安全检查:如果启用了安全管理器(Globals.IS_SECURITY_ENABLED),则在特权上下文中执行

img

执行:实际过滤逻辑在internalDoFilter方法中完成

img

此时的 filter 中有俩个值:

0 是我们自定义的 FilterTest

1 是 tomcat 自带的

img

img

调用了filter.doFilter(),而filter是通过filterConfig.getFilter()得到的,filterConfig定义如下

img

img

总的来说:

最后一个 filter 调用 servlet 的 service 方法

上一个 Filter.doFilter() 方法中调用 FilterChain.doFilter() 方法将调用下一个 Filter.doFilter() 方法;

最后一个 Filter.doFilter() 方法中调用的 FilterChain.doFilter() 方法将调用目标 Servlet.service() 方法。

只要 Filter 链中任意一个 Filter 没有调用 FilterChain.doFilter() 方法,则目标 Servlet.service() 方法都不会被执行。

在访问 /filter 之前的流程分析

filter是如何被创建并注册的

之前的流程:

img

进入 StandardWrapperValve.invoke

img

这个函数中有 creatFilterChain

img

一个filterConfig对应一个Filter,用于存储Filter的上下文信息。这里的filters属性是一个ApplicationFilterConfig数组。我们来寻找一下ApplicationFilterChain.filters属性在哪里被赋值。

在StandardWrapperValve#invoke()方法中,通过ApplicationFilterFactory.createFilterChain()方法初始化了一个ApplicationFilterChain类

img

小结:filterChain对象的创建过程

  1. 首先通过filterChain = new ApplicationFilterChain()创建一个空的 filterChain 对象
  2. 然后通过wrapper.getParent()函数来获取StandardContext对象
  3. 接着获取StandardContext中的FilterMaps对象,FilterMaps对象中存储的是各Filter的名称路径等信息
  4. 最后根据Filter的名称,在StandardContext中获取FilterConfig
  5. 通过filterChain.addFilter(filterConfig)将一个filterConfig添加到filterChain中

跟进到 createFilterChain 函数中,我们能看到此时的上下文对象StandardContext实际上是包含了filterConfigs、filterDefs、filterMaps

filterConfigs:包含所有与过滤器对应的filterDef信息及过滤器实例,进行过滤器进行管理

其中filterConfigs包含了当前的上下文信息StandardContext、以及filterDef等信息

filterDef

filterDef 就是对应 web.xml 中的 filter 标签

filterDefs:包含所有过滤器包括实例内部等变量

filterMaps:包含所有过滤器的URL映射关系

filterMaps中以array的形式存放各filter的路径映射信息,其对应的是web.xml中的<filter-mapping>标签

filterMaps必要的属性为dispatcherMapping、filterName、urlPatterns

1.3 攻击思路:

只需要构造含有恶意的 filter 的 filterConfig 和拦截器 filterMaps,就可以触发,而这个 filterMaps 中的数据对应 web.xml 中的 filter-mapping 标签

那么只要可以更改web.xml 的filter-mapping 标签,就可以攻击成功了

找到 FilterMaps 通过下面的俩个方法进行添加数据

img

其中的 filterConfig 可以通过 filterConfigs.put(name, filterConfig); 添加

img

动态添加恶意Filter的思路

  1. 获取StandardContext对象

这一步和 servlet 的获取 StandardContext 对象一致:

  1. 创建恶意Filter
  1. 使用FilterDef对Filter进行封装,并添加必要的属性
  1. 创建filterMap类,并将路径和Filtername绑定,然后将其添加到filterMaps中
  1. 使用ApplicationFilterConfig封装filterDef,然后将其添加到filterConfigs中

完整 POC:addFilter.jsp

(一定要注意包不能导错!)

1.4 利用:

先访问 addFilter.jsp 写入内存马:

img

img

img

img

img

img

img

img

pom.xml

替换注释:

先写一个恶意类测试:

web.xml 配置:

img

2. Servlet 内存马

Servlet(Server Applet)是 Java Servlet 的简称,称为小服务程序或服务连接器,用来读取客户端发送的数据,处理并返回结果。

2.1 环境创建:

img

img

img

img

img

pom.xml

替换注释:

先写一个恶意类测试:

web.xml 配置:

img

2.2 流程分析:

前置流程:

因为 Web 应用程序的顺序是 Listener —> Filter —> Servlet,所以我们在调用 Servlet 的时候也会看到 Listener 和 Filter 的流程

img

首先根据上图流程跟到 configureStart() 完成启动配置,解析 web.xml

**xml 赋值对象:**从 web.xml 配置文件中读取配置信息(如 Servlet、Filter 参数),并将其转化为内存中的 Java 对象(如 ServletDef、FilterDef)。

configureContext() :

用于配置 Context 对象(代表一个 Web 应用)在这个方法中,会进行一系列初始化操作,比如注册 Filter、Servlet 等。

context.addFilterDef(filter):

将解析得到的 FilterDef(Filter 定义对象)添加到 Context 中。

FilterDef 包含 Filter 的名称、类名、初始化参数等。

context.addFilterMap(filterMap): 将 Filter 的映射关系(即哪些 URL 或 Servlet 会被该 Filter 拦截)添加到 Context 中。

每个servlet包装成wrapper对象: 这是 Servlet 容器中的关键设计:每个 Servlet 都被封装为一个 Wrapper 对象。

Wrapper 是 Tomcat 容器体系中的最底层容器,专门用于管理一个 Servlet 的生命周期。

创建与装载 StandardWrapper

断点调试:

img

此处的 WebXml 和 StandardContext 是 Servlet 注册所需的配置

img

继续跟到创建 wrapper:

img

根据 StandardContext 的内容创建 StandardWrapper,将 Servlet 放入 wrapper 中

img

LoadOnStartup 是 Servlet 的启动顺序配置

在 web.xml 中:

  • 正数:启动时按数值顺序初始化
  • 0 或正数:应用启动时立即初始化
  • 负数:第一次请求时才初始化
  • 未设置:默认第一次请求时初始化

img

给 wrapper 设置一个名字

img

从创建 wrapper 时获取 servletName

servletName=“default”(这些是系统内置的,需要跳过之后,获取到我们写好的)

img

img

img

此时获取的 Name 为 ServletTest

img

获取全类名

img

将 wrapper 放入 context

这一步相当于 web.xml 中的:

img

获取 Servlet 的 URL 映射

执行 web.xml 中的:

目前依旧是系统内置的 jsp

img

获取到我们前面写的恶意类

img

总结一下注册的大致流程:

  1. 创建一个包装器 wrapper
  2. 获取 Servlet 名字
  3. 获取 Servlet 类
  4. 将 wrapper 加入 context
  5. 获取 URL 映射

2.3 写恶意类

img

2.4 注册进 tomcat

写好马后,第二步就是动态注册 servlet

第一步:获取 standardContext

在 jsp 中有一个 request 对象,这个对象中存在一个 getServletContext 方法,会获取 servletContext

动态调试,找到 request 对象获取的 servletContext 中存在 StandardContext

img

接下来就利用反射获取:

第二步:注册到 context

完整的 POC:addServlet.jsp

2.5 利用

img

先去访问传上去的 addServlet.jsp 来创建一个 Servlet

此时 MemServlet 已经写入了内存

img

通过路径 /MemShell 访问:弹出计算器

img

参考:

【原创】利用“进程注入”实现无文件不死webshell

Java安全学习——内存马 - 枫のBlog

Spring内存马 | CurlySean’s Blog

Java Agent实现反序列化注入内存shell

JavaWeb 内存马一周目通关攻略 | 素十八

JavaWeb 内存马二周目通关攻略 | 素十八

Shell中的幽灵王者—JAVAWEB 内存马 【认知篇】 - 嘶吼 RoarTalk – 网络安全行业综合服务平台,4hou.com

Java内存马——Tomcat Valve型的三种注入 - FreeBuf网络安全行业门户

Java 内存马(四):Spring Boot Controller 内存马 | 渐怀的博客

Spring型内存马分析

Servlet 简介 | 菜鸟教程

Spring内存马学习

Spring内存马——Controller/Interceptor构造

[基础篇 - Javassist 使用指南](https://changeyourway.github.io/2024/06/07/Java 安全/基础篇-javassist用法指南/)

Java安全学习——ROME反序列化 - 枫のBlog (使用Javassist缩短恶意class)

Java Agent实现反序列化注入内存shell

Agent内存马 | CurlySean’s Blog