CVE-2019-12422 Apache Shiro RememberMe Padding Oracle
字数 2146 2025-08-05 13:25:37
Apache Shiro RememberMe Padding Oracle漏洞(CVE-2019-12422)深入分析与利用
1. 漏洞概述
Apache Shiro是一个功能强大且易用的Java安全框架,用于身份验证、授权、加密和会话管理。在1.4.1及以下版本中,Shiro的RememberMe功能存在Padding Oracle漏洞(CVE-2019-12422),攻击者可以在不知道密钥的情况下构造恶意的RememberMe Cookie,最终实现远程代码执行。
2. 前置知识
2.1 CBC模式加密
CBC(Cipher Block Chaining)模式是一种分组加密模式,其特点如下:
- 每个明文分组在加密前会与前一个密文分组进行异或操作
- 第一个分组使用初始化向量(IV)进行异或
- 解密是加密的逆过程
CBC加密流程:
- 明文分组P1与IV异或
- 结果用密钥K加密得到C1
- C1与P2异或后加密得到C2
- 以此类推
CBC解密流程:
- 密文分组C1用密钥K解密得到中间值M1
- M1与IV异或得到P1
- C2解密得到M2,与C1异或得到P2
- 以此类推
2.2 PKCS #5/PKCS #7填充
由于分组加密需要固定长度的数据块,当数据长度不足时需要填充:
- PKCS #5:固定8字节分组
- PKCS #7:支持1-255字节分组
- 填充规则:缺少n个字节就填充n个0xn
例如:
- 最后一组剩下5字节 → 填充3个0x03
- 完整分组(8字节) → 填充8个0x08
2.3 Padding Oracle攻击原理
Padding Oracle攻击是针对CBC模式的攻击,利用服务器对不同填充错误的响应差异来恢复明文。
关键概念:
- RawIV:原始IV,解密时是前一个密文分组
- FuzzIV:枚举的IV
- MediumValue:密文解密后的中间值
- 公式关系:
- 解密:
MediumValue = BlockCipherDecrypt(CipherText, Key) - 明文:
PlainText = MediumValue XOR RawIV
- 解密:
攻击步骤:
- 提交构造的FuzzIV和密文给服务器
- 服务器解密后检查填充有效性
- 根据服务器响应判断填充是否正确
- 利用正确填充时的FuzzIV计算中间值和明文
计算明文:
当找到使最后一个字节为0x01的有效FuzzIV时:
MediumValue[8] = FuzzIV[8] ^ 0x01
PlainText[8] = MediumValue[8] ^ RawIV[8] = FuzzIV[8] ^ 0x01 ^ RawIV[8]
同理可计算其他位置的明文。
2.4 CBC字节翻转攻击
通过修改密文中的特定字节,可以影响解密后的明文:
- 修改前一个密文分组的字节会影响当前分组的解密结果
- 公式:
P'[i] = C[i-1]' XOR Decrypt(C[i]) XOR P[i] - 可用于篡改解密后的明文内容
3. 漏洞分析
3.1 RememberMe功能流程
- 用户登录时勾选"Remember Me"
- 服务器生成包含用户身份信息的序列化数据
- 使用AES-CBC加密序列化数据
- 设置RememberMe Cookie返回给客户端
- 下次请求时,服务器解密Cookie恢复用户身份
3.2 漏洞触发点
在AbstractRememberMeManager#getRememberedPrincipals方法中:
protected PrincipalCollection getRememberedPrincipals(SubjectContext subjectContext) {
try {
byte[] bytes = getRememberedSerializedIdentity(subjectContext);
if (bytes != null && bytes.length > 0) {
return convertBytesToPrincipals(bytes, subjectContext);
}
} catch (Exception e) {
onRememberedPrincipalFailure(e, subjectContext);
}
return null;
}
convertBytesToPrincipals方法调用解密逻辑:
protected PrincipalCollection convertBytesToPrincipals(byte[] bytes, SubjectContext context) {
if (getCipherService() != null) {
bytes = getCipherService().decrypt(bytes, getDecryptionCipherKey()).getBytes();
}
return deserialize(bytes);
}
3.3 解密过程分析
-
JcaCipherService#decrypt方法处理解密:- 获取IV长度(
getInitializationVectorSize) - 从密文中提取IV
- 调用
crypt方法进行实际解密
- 获取IV长度(
-
解密失败时的处理:
- 抛出
CryptoException - 被
getRememberedPrincipals捕获 - 调用
onRememberedPrincipalFailure - 最终设置
RememberMe=deleteMe的Cookie
- 抛出
-
解密成功时的特征:
- 返回正常响应
- 无
deleteMeCookie
3.4 Padding Oracle条件
- Padding失败:返回
RememberMe=deleteMe - Padding成功:返回正常响应或反序列化错误
这种差异化的响应构成了Padding Oracle。
4. 漏洞利用
4.1 利用步骤
- 获取一个合法的RememberMe Cookie
- Base64解码获取原始密文
- 使用Padding Oracle攻击恢复中间值
- 构造恶意的序列化数据
- 生成新的合法RememberMe Cookie
- 发送恶意Cookie实现RCE
4.2 利用工具
推荐使用Shiro-721 Exploit:
python shiro_exp.py "http://target/" "base64_rememberMe_cookie" payload.ser
4.3 生成Payload
使用ysoserial生成反序列化payload:
java -jar ysoserial.jar URLDNS "http://dnslog.cn" > payload.ser
5. 防御措施
- 升级到Apache Shiro 1.4.2或更高版本
- 禁用RememberMe功能(如果不需要)
- 使用自定义密钥而非默认密钥
- 实施网络层防护,如WAF规则检测攻击