初识内存马

学习内存马之前你必须知道的知识

JavaWeb三大件

当JavaWeb接收到请求时会依次经过 Listener —> Filter —> Servlet

Listener

简介

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

ServletContextListener:对Servlet上下文的创建和销毁进 行监听;ServletContextAttributeListener:监听 Servlet 上下文属性的添加、删除和替换;

HttpSessionListener:对 Session 的创建和销毁进行监听。Session 的销毁有两种情况,一个中 Session 超时,还有一种是通过调用 Session 对象的 invalidate() 方法使 session 失效。

HttpSessionAttributeListener:对 Session 对象中属性的添加、删除和替换进行监听;

ServletRequestListener:对请求对象的初始化和销毁进行监听;ServletRequestAttributeListener:对请求对象属性的添加、删除和替换进行监听。

用途

可以使用监听器监听客户端的请求、服务端的操作等。通过监听器,可以自动出发一些动作,比如监听在线的用户数量,统计网站访问量、网站访问监控等。

Filter

简介

filter 也称之为过滤器,是对 Servlet 技术的一个强补充,其主要功能是在 HttpServletRequest 到达 Servlet 之前,拦截客户的 HttpServletRequest ,根据需要检查 HttpServletRequest,也可以修改 HttpServletRequest 头和数据;在 HttpServletResponse 到达客户端之前,拦截 HttpServletResponse ,根据需要检查 HttpServletResponse,也可以修改 HttpServletResponse 头和数据。

基本工作原理

1、Filter 程序是一个实现了特殊接口的 Java 类,与 Servlet 类似,也是由 Servlet 容器进行调用和执行的。

2、当在 web.xml 注册了一个 Filter 来对某个 Servlet 程序进行拦截处理时,它可以决定是否将请求继续传递给 Servlet 程序,以及对请求和响应消息是否进行修改。

3、当 Servlet 容器开始调用某个 Servlet 程序时,如果发现已经注册了一个 Filter 程序来对该 Servlet 进行拦截,那么容器不再直接调用 Servlet 的 service 方法,而是调用 Filter 的 doFilter 方法,再由 doFilter 方法决定是否去激活 service 方法。

4、但在 Filter.doFilter 方法中不能直接调用 Servlet 的 service 方法,而是调用 FilterChain.doFilter 方法来激活目标 Servlet 的 service 方法,FilterChain 对象时通过 Filter.doFilter 方法的参数传递进来的。

5、只要在 Filter.doFilter 方法中调用 FilterChain.doFilter 方法的语句前后增加某些程序代码,这样就可以在 Servlet 进行响应前后实现某些特殊功能。

6、如果在 Filter.doFilter 方法中没有调用 FilterChain.doFilter 方法,则目标 Servlet 的 service 方法不会被执行,这样通过 Filter 就可以阻止某些非法的访问请求。

Filter的实现

Filter是一个接口,实现一个Filter只需要重写initdoFilterdestroy方法即可,其中过滤逻辑都在doFilter方法中实现。

Filter的配置类似于Servlet,由<filter><filter-mapping>两组标签组成,如果Servlet版本大于3.0同样可以使用注解的方式配置Filter。

Filter 的生命周期

与 servlet 一样,Filter 的创建和销毁也由 Web 容器负责。Web 应用程序启动时,Web 服务器将创建 Filter 的实例对象,并调用其 init() 方法,读取 web.xml 配置,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作(filter 对象只会创建一次,init 方法也只会执行一次)。开发人员通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。Filter 对象创建后会驻留在内存,当 Web 应用移除或服务器停止时才销毁。在 Web 容器卸载 Filter 对象之前被调用。该方法在 Filter 的生命周期中仅执行一次。在这个方法中,可以释放过滤器使用的资源。

Filter链

当多个 Filter 同时存在的时候,组成了 Filter 链。Web 服务器根据 Filter 在 web.xml 文件中的注册顺序,决定先调用哪个 Filter。当第一个 Filter 的 doFilter 方法被调用时,web服务器会创建一个代表 Filter 链的 FilterChain 对象传递给该方法,通过判断 FilterChain 中是否还有 Filter 决定后面是否还调用 Filter。

image-20231025195002798

Servlet

简介

Servlet是在 Java Web容器中运行的小程序,通常我们用Servlet来处理一些较为复杂的服务器端的业务逻辑。ServletJava EE的核心,也是所有的MVC框架的实现的根本。

请求的处理过程

客户端发起一个 http 请求,比如 get 类型。

Servlet 容器接收到请求,根据请求信息,封装成 HttpServletRequest 和HttpServletResponse 对象。这步也就是我们的传参。

Servlet容器调用 HttpServlet 的 init() 方法,init 方法只在第一次请求的时候被调用。

Servlet 容器调用 service() 方法。

service() 方法根据请求类型,这里是get类型,分别调用doGet或者doPost方法,这里调用doGet方法。

doXXX 方法中是我们自己写的业务逻辑。

业务逻辑处理完成之后,返回给 Servlet 容器,然后容器将结果返回给客户端。

容器关闭时候,会调用 destory 方法。

Servlet的定义

web.xml的配置:

Servlet3.0 之前的版本都需要在web.xml 中配置servlet标签servlet标签是由servletservlet-mapping标签组成的,两者之间通过在servletservlet-mapping标签中同样的servlet-name名称来实现关联的。

注解配置:

值得注意的是在 Servlet 3.0 之后( Tomcat7+)可以使用注解方式配置 Servlet 了,在任意的Java类添加javax.servlet.annotation.WebServlet注解即可。

基于注解的方式配置Servlet实质上是对基于web.xml方式配置的简化,极大的简化了Servlet的配置方式,但是也提升了对Servlet配置管理的难度,因为我们不得不去查找所有包含了@WebServlet注解的类来寻找Servlet的定义,而不再只是查看web.xml中的servlet标签配置。

实现:

定义一个 Servlet 很简单,只需要继承javax.servlet.http.HttpServlet类并重写doXXX(如doGet、doPost)方法或者service方法就可以了,其中需要注意的是重写HttpServlet类的service方法可以获取到上述七种Http请求方法的请求。(doGet/doPost/doDelete/doHead/doPut/doOptions/doTrace)

servlet生命周期

1)服务器启动时 (web.xml 中配置 load-on-startup=1,默认为 0)或者第一次请求该 servlet 时,就会初始化一个 Servlet 对象,也就是会执行初始化方法 init(ServletConfig conf)。

2)servlet 对象去处理所有客户端请求,在 service(ServletRequest req,ServletResponse res) 方法中执行

3)服务器关闭时,销毁这个 servlet 对象,执行 destroy() 方法。

4)由 JVM 进行垃圾回收。

Request 和 Response

B/S架构中最重要的就是浏览器和服务器端交互,Java EE将其封装为请求响应对象,即 request(HttpServletRequest)response(HttpServletResponse)

HttpServletRequest对象用于处理来自客户端的请求,当客户端通过HTTP协议访问服务器时,HTTP 中的所有信息都封装在这个对象中,通过HttpServletRequest对象可以获取到客户端请求的所有信息。

HttpServletResponse对象用于响应客户端的请求,通过HttpServletResponse对象可以处理服务器端对客户端请求响应。

Filter和Servlet总结:

  1. FilterServlet都需要在web.xml注解(@WebFilter@WebServlet)中配置,而且配置方式是非常的相似的。
  2. FilterServlet都可以处理来自Http请求的请求,两者都有requestresponse对象。
  3. FilterServlet基础概念不一样,Servlet定义是容器端小程序,用于直接处理后端业务逻辑,而Filter的思想则是实现对Java Web请求资源的拦截过滤。
  4. FilterServlet虽然概念上不太一样,但都可以处理Http请求,都可以用来实现MVC控制器(Struts2Spring框架分别基于FilterServlet技术实现的)。
  5. 一般来说Filter通常配置在MVCServletJSP请求前面,常用于后端权限控制、统一的Http请求参数过滤(统一的XSSSQL注入Struts2命令执行等攻击检测处理)处理,其核心主要体现在请求过滤上,而Servlet更多的是用来处理后端业务请求上。

Tomcat 基础介绍

什么是 Tomcat

大致可以通过对标 Apache 来看一看。

Apache 是 web 服务器(静态解析,如 HTML),tomcat 是 java 应用服务器(动态解析,如 JSP)

Tomcat 只是一个 servlet(jsp 也翻译成 servlet)容器,可以认为是 apache 的扩展,但是可以独立于 apache 运行。

  • ​ 一句话概括一下,就是 Web 服务器,比较不稳定,但是业务能力比较强。

Tomcat 与 Servlet 的关系

我们根据上面的基础知识可以知道Tomcat 是Web应用服务器,是一个Servlet/JSP容器,而Servlet容器从上到下分别是 Engine、Host、Context、Wrapper。

在Tomcat中Wrapper代表一个独立的servlet实例,StandardWrapper是Wrapper接口的标准实现类(StandardWrapper 的主要任务就是载入Servlet类并且进行实例化),同时其从ContainerBase类继承过来,表示他是一个容器,只是他是最底层的容器,不能再含有任何的子容器了,且其父容器只能是context。而我们在也就是需要在这里去载入我们自定义的Servlet加载我们的内存马。

Tomcat 架构

Tomcat 架构原理

tomcat的框架如下图所示,主要有server、service、connector、container 四个部分

image-20231025195032719

图中可以看出 Tomcat 的心脏是两个组件:Connector 和 Container:
Connector 主要负责对外交流,进行 Socket 通信(基于 TCP/IP),解析 HTTP 报文,对应下图中的http服务器;

Container 主要处理 Connector 接受的请求,主要是处理内部事务,加载和管理 Servlet,由 Servlet 具体负责处理 Request 请求,对应下图中的servlet容器。

image-20231025195050119

以上两个功能,分别对应着tomcat的两个核心组件连接器(Connector)和容器(Container),连接器负责对外交流(完成 Http 服务器功能),容器负责内部处理(完成 Servlet 容器功能)。

image-20231025195111671

server

即服务器,代表整个 Tomcat 服务器,它要能够提供一个接口让其它程序能够访问到这个 Service 集合、同时要维护它所包含的所有 Service 的生命周期,包括如何初始化、如何结束服务、如何找到别人要访问的 Service。还有其它的一些次要的任务,如您住在这个地方要向当地政府去登记啊、可能还有要配合当地公安机关日常的安全检查什么的。

一个 Tomcat 只有一个 Server Server 中包含至少一个 Service 组件,用于提供具体服务。

service

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

Tomcat 中 Service 接口的标准实现类是 StandardService ,它不仅实现了 Service 借口同时还实现了 Lifecycle 接口,这样它就可以控制它下面的组件的生命周期了

connecter

Connector 组件是 Tomcat 中两个核心组件之一,它的主要任务是负责接收浏览器的发过来的 tcp 连接请求,创建一个 Request 和 Response 对象分别用于和请求端交换数据,然后会产生一个线程来处理这个请求并把产生的 Request 和 Response 对象传给处理这个请求的线程,处理这个请求的线程就是 Container 组件要做的事了。

根据运行的逻辑图,我们也能看到连接器 connector 主要有三个功能:

socket 通信
解析处理应用层协议,如将 socket 连接封装成 request 和 response 对象,后续交给 Container 来处理
将 Request 转换为 ServletRequest,将 Response 转换为 ServletResponse

这些,其实在 shiro 开发的过程当中也是用到这个了的,而且当时我记得还特别容易写错类名。

其中 Tomcat 设计了三个组件,其负责功能如下:

  • EndPoint: 负责网络通信,将字节流传递给 Processor;
  • Processor: 负责处理字节流生成 Tomcat Request 对象,将 Tomcat Request 对象传递给 Adapter;
  • Adapter: 负责将 Tomcat Request 对象转化成 ServletRequest 对象,传递给容器。

Adapter 组件

由于协议的不同,Tomcat 定义了自己的 Request 类来存放请求信息,但是这个不是标准的 ServletRequest。于是需要使用 Adapter 将 Tomcat Request 对象转成 ServletRequest 对象,然后就能调用容器的 service 方法。

简而言之,Endpoint 接收到 Socket 连接后,生成一个 SocketProcessor 任务提交到线程池进行处理,SocketProcessor 的 run 方法将调用 Processor 组件进行应用层协议的解析,Processor 解析后生成 Tomcat Request 对象,然后会调用 Adapter 的 Service 方法,方法内部通过如下代码将 Request 请求传递到容器中。

connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

一个总的 Tomcat Connector 功能如图所示image-20231025195129773

Container

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

image-20231025195153658

Tomcat 设计了 4 种容器: Engine、Host、Context、Wrapper,这四种容器是父子关系

  • Engine: 最顶层容器组件,可以包含多个 Host。实现类为 org.apache.catalina.core.StandardEngine
  • Host: 代表一个虚拟主机,每个虚拟主机和某个域名 Domain Name 相匹配,可以包含多个 Context。实现类为 org.apache.catalina.core.StandardHost
  • Context: 一个 Context 对应于一个 Web 应用,可以包含多个 Wrapper。实现类为 org.apache.catalina.core.StandardContext
  • Wrapper: 一个 Wrapper 对应一个 Servlet。负责管理 Servlet ,包括 Servlet 的装载、初始化、执行以及资源回收。实现类为 org.apache.catalina.core.StandardWrapper

通常一个 Servlet class 对应一个 Wrapper,如果有多个 Servlet 就可以定义多个 Wrapper,如果有多个 Wrapper 就要定义一个更高的 Container。

举个栗子,a.com和b.com分别对应着两个Host

image-20231025195214472

每一个 Context 都有唯一的 path。这里的 path 不是指 servlet 绑定的 WebServlet 地址,而是指独立的一个 Web 应用地址。就好比 Tomat 默认的 / 地址和 /manager 地址就是两个不同的 web 应用,所以对应两个不同的 Context。要添加 Context 需要在 server.xml 中配置 docbase。

如下图所示, 在一个 web 应用中创建了 2 个 servlet 服务,WebServlet 地址分别是 /Demo1 和 /Demo2 。因为它们属于同一个 Web 应用所以 Context 一样,但访问地址不一样所以 Wrapper 不一样。/manager 访问的 Web 应用是 Tomcat 默认的管理页面,是另外一个独立的 web 应用, 所以 Context 与前两个不一样。

image-20231025195234445

Tomcat 的类加载机制

由于 Tomcat 中有多个 WebApp 同时要确保之间相互隔离,所以 Tomcat 的类加载机制也不是传统的双亲委派机制。

Tomcat 自定义的类加载器 WebAppClassloader 为了确保隔离多个 WebApp 之间相互隔离,所以打破了双亲委托机制。每个 WebApp 用一个独有的 ClassLoader 实例来优先处理加载。它首先尝试自己加载某个类,如果找不到再交给父类加载器,其目的是优先加载 WEB 应用自己定义的类。

同时为了防止 WEB 应用自己的类覆盖 JRE 的核心类,在本地 WEB 应用目录下查找之前,先使用 ExtClassLoader(使用双亲委托机制)去加载,这样既打破了双亲委托,同时也能安全加载类。

什么是内存马?

内存马又叫无文件马,闻名知其意。简单理解就是没有webshell文件落地的webshell。

目前安全行业主要讨论的内存马主要分为以下几种方式:

  • 动态注册 servlet/filter/listener(使用 servlet-api 的具体实现)
  • 动态注册 interceptor/controller(使用框架如 spring/struts2)
  • 动态注册使用职责链设计模式的中间件、框架的实现(例如 Tomcat 的 Pipeline & Valve,Grizzly 的 FilterChain & Filter 等等)
  • 使用 java agent 技术写入字节码

内存马有哪些?

  • 传统Web应用型内存马

    • Servlet型内存马 —> 动态注册Servlet及映射路由
    • Filter型内存马 —> 动态注册Filter及映射路由
    • Listener型内存马 —> 动态注册Listener

    JavaWeb应用将Servlet与其映射、处理类/Filter与其映射、处理类/Listener与其处理类存放在Context中,并在程序允许时进行查找和匹配。传统Web应用型内存马就是内存马的处理代码与指定的映射动态的添加在Context中的关键位置中,使程序处理一个在本地代码、配置文件中不存在的恶意逻辑。

  • 框架型内存马

    • Spring Controller型内存马 —> 动态注册Controller及映射路由
    • Spring Interceptor型内存马 —> 动态注册Interceptor及映射路由
    • Spring Webflux型内存马 —> 动态注册WebFilter及映射路由

    Spring MVC 使用Controller来接收用户的输入和封装Service,虽然有一部分开发习惯用Mapping来处理,但是这里通常代表处理一个用户请求的“端点”,类似于Servlet,而Interceptor拦截器类似Filter

    Spring Webflux 是Spring Framework5.0中引入的新的响应式web框架,它不依赖Servlet-API,但是也同样必须有Filter这种思想,实际就是Filter。

  • 中间件型内存马

    • Tomcat Valve 型内存马 —> 动态注册Valve
    • Tomcat Upgrade 型内存马 —> 动态注册UpgradeProtocol
    • Tomcat Executor 型内存马 —> 动态替换全局Executor
    • Tomcat Poller 型内存马 —> 动态替换全局Poller
    • Grizzly Filter 型内存马 —> 动态注册Grizzly Filter及映射路由

    中间件的很多设计都是“流式”、“管道式”。一般称之为职责链模式,每个关键点都会将Request处理,并传递给下一个处理者。在这种模式下,攻击者可以向职责链中插入自己的恶意逻辑,实现内存马的驻留。

  • Agent 型内存马

    • Agent型内存马 —> 通过Hook并修改关键方法添加恶意逻辑

    这个可以参考我之前写的插桩文章,学习一下Agent基础之后会对后期学习Agent型内存马很有帮助。

  • 其他内存马

    • WebSocket型内存马 —> 动态注册Websocket路由及处理逻辑
    • Tomcat JSP型内存马 —> 动态注册Tomcat JSP管理逻辑并实现驻留
    • 线程型内存马 —> 动态添加一个无法杀死的线程
    • RMI型内存马 —> 动态启动一个RMI Registry

    对于WebSocket协议请求,JSP请求处理,各个中间件包含自己的逻辑,这些逻辑的具体实现也可以用来作为内存马的逻辑处理。

    线程型内存马在系统中启动一个永不停止的线程,此时关键类无法被GC,会一直存在目标系统中,执行实现预定义的逻辑。而RMI内存马启动一个RMI Registry 并绑定恶意类作为后门。


因为内存马这里个人想深入学习,所以后续会力所能及将每种内存马的实现过程和复现过程中遇到的坑点都分享出来。


初识内存马
http://example.com/2023/10/25/初识内存马/
作者
Yuanyi
发布于
2023年10月25日
许可协议