内存马(基础知识)

前言

前面我跟随P牛的脚步看了一些反序列化,当然还有很多链没有跟着分析,可能考虑后面再接着跟。今天开始打算对java内存马进行一个系统化的学习,并记录一些学习过程中的知识点。

内存马是一种无文件落地马,相对于原来的普通木马有隐蔽性强的特点,原来的木马由于新webshell检测技术也更容易被检测到,随着冰蝎的java agent 类型的内存马出现,内存马又出现在大众的眼中。

目前主要的内存马方式有以下几种方式:

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

基础介绍

servlet

servlet介绍

借用菜鸟教程的介绍:

Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。

image-20211127140840551

我们可以看到servlet主要是用来处理由http协议发送和传输的报文

servlet主要的工作流程我们借用一张图来展示

image-20211127142122754

我们收到http请求web服务器会将我们的request和response封装成HttpServletRequest和HttpServletResponse对象,然后调用对应servlet的init()方法,该方法只在第一次请求时被调用,接着servlet会去调用service()方法处理消息,并将结果返回给HttpServletResponse对象。

servlet生命周期

image-20211127142656302

服务器启动时或者第一次请求该servlet时会初始化一个servlet对象,当服务关闭时,就会调用destory()方法销毁。所以servlet的生命周期是由web容器负责。

filter

filter介绍

filter是servlet的过滤器,是对servlet的一种补充,我们在调用servlet的service()方法之前会经过对应的filter,可以对一些不合法的请求进行拦截或更改

image-20211127143611401

filter基本工作原理

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的生命周期
  • init(FilterConfig filterConfig)
    • 初始化方法,只会在web应用程序启动时调用一次
    • 和我们编写的Servlet程序一样,Filter的创建和销毁由WEB服务器负责。
    • web 应用程序启动时,web 服务器将创建Filter 的实例对象,并调用其init方法,读取web.xml配置,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作(filter对象只会创建一次,init方法也只会执行一次)。开发人员通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。
    • Filter对象创建之后会驻留在内存,一直服务。
  • doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    • 完成实际的过滤操作。当客户请求访问与过滤器关联的URL的时候,Servlet过滤器将先执行doFilter方法。FilterChain参数用于访问后续过滤器。
  • destroy();
    • 销毁方法,只会在当web应用移除或服务器停止时才调用一次来卸载Filter对象。
    • 通常在这个方法中,可以释放过滤器使用的资源。
filterchain(filter链)

在一个web应用当中,可以开发编写多个Filter,这些Filter组合起来称之为一个Filter链,其中每个过滤器(Filter)都可以决定是否执行下一步。

简单实现了个demo

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
//TestFilter
package com.example.tomcat;

import javax.servlet.*;
import javax.servlet.FilterChain;
import java.io.IOException;

public class TestFilter implements Filter{

@Override
public void init(FilterConfig filterConfig) {
}

@Override
public void doFilter(ServletRequest req, ServletResponse rep, FilterChain chain) throws ServletException, IOException {
System.out.println("this is a Filter test");
chain.doFilter(req,rep);
}

@Override
public void destroy() {
}
}

//web.xml
<filter>
<filter-name>TestFilter</filter-name>
<filter-class>com.example.tomcat.TestFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>TestFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

该过滤器只要访问该web服务的资源就会调用该filter

Listener

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

ServletContextListener:对Servlet上下文的创建和销毁进行监听,监听 Web 应用的生命周期;
ServletContextAttributeListener:监听Servlet上下文属性的添加、删除和替换;
HttpSessionListener:对Session的创建和销毁进行监听。Session的销毁有两种情况,一个中Session超时,还有一种是通过调用Session对象的invalidate()方法使session失效。
HttpSessionAttributeListener:对Session对象中属性的添加、删除和替换进行监听;
ServletRequestListener:对请求对象的初始化和销毁进行监听;
ServletRequestAttributeListener:对请求对象属性的添加、删除和替换进行监听。

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

这里我写了一个ServletRequestListener的demo,也就是监听请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//TestListener
package com.example.tomcat;

import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;

public class TestListener implements ServletRequestListener {
@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("this is a listener");
}
@Override
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("listener out");
}
}

//web.xml
<listener>
<listener-class>com.example.tomcat.TestListener</listener-class>
</listener>

image-20211204143718345

servlet api 动态注册

当我们使用tomcat注册servlet、filter和listener时主要是三种注册方式

Web.xml注册

注解注册

动态注册

web.xml注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//servlet	
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.example.tomcat.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>

//listener
<listener>
<listener-class>com.example.tomcat.TestListener</listener-class>
</listener>

//filter
<filter>
<filter-name>TestFilter</filter-name>
<filter-class>com.example.tomcat.TestFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>TestFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

注解注册即在对应类上使用注解

1
2
3
4
5
6
7
8
//servlet
@WebServlet("/hello")

//listener
@WebListener

//filter
@WebFilter("/*")

动态注册

动态注册是在Web容器初始化阶段,使用ServletContext去动态注册。

一个最简单的注册servlet的demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.example.tomcat;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletRegistration;
import javax.servlet.annotation.WebListener;

@WebListener
public class ContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext servletContext = sce.getServletContext();
ServletRegistration.Dynamic dynamic = servletContext.addServlet("TestServlet",TestServlet.class);
dynamic.addMapping("/dynamic");
}

@Override
public void contextDestroyed(ServletContextEvent sce) {}
}

image-20211204155434823

Filter

1
2
3
FilterRegistration.Dynamic dynamicFilter = servletContext.addFilter("DynamicFilter",new DynamicFilter());
dynamicFilter.addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST),false,new String[]{"/*"});
}

addMappingForUrlPatterns中一共有三个参数
EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter, String... urlPatterns

  • dispatcherTypes一共有下面几种模式,表示过滤器的类型,我们这里使用Request模式
类型 说明
Forward 通过RequestDispatcher的forward(),或者jsp:forward
Include 通过RequestDispatcher的include(),或者jsp:include
Request 普通模式,来自客户端的请求
Error 请求错误页面来处理HTTP错误,例如404,500
Async 来自AsyncContext的异步请求
  • boolean isMatchAfter,动态注册Filter中,过滤顺序由isMatchAfter属性决定

​ true表示放在当前应用所有的过滤器之后,false表示将该过滤器放在当前应用所有的过滤器之前

​ 我们注册的恶意filter优先级一定要高,放在所有过滤器之前,所以这里选false

  • String… urlPatterns 表示我们要拦截的url
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//DynamicFilter
package com.example.tomcat;

import javax.servlet.*;
import java.io.IOException;

public class DynamicFilter implements Filter {
@Override
public void destroy() {
}

@Override
public void init(FilterConfig filterConfig) throws ServletException {
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("this is dynamic Filter");
}
}

//ContextListener
FilterRegistration.Dynamic dynamicFilter = servletContext.addFilter("DynamicFilter",new DynamicFilter());
dynamicFilter.addMappingForUrlPatterns(null,false,new String[]{"/*"});

image-20211204161202213

参考文章: