freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

从蓝队流量角度分析Shiro-550反序列化漏洞
2024-09-05 20:24:14

Apache Shiro 简介

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。Shiro的优势在于轻量级,使用简单、上手更快、学习成本低。

Shiro反序列化原理:Apache Shiro框架提供了 RememberMe 功能,用户登陆成功后会生成经过加密并编码的cookie,在服务端接收cookie值后,Base64解码–>AES解密–>反序列化。因此攻击者只要找到AES加密的密钥,就可以构造一个恶意对象,对其进行序列化–>AES加密–>Base64编码,然后将其作为cookie的rememberMe字段发送,Shiro将rememberMe进行解密并且反序列化,最终造成反序列化漏洞。

在 Apache Shiro<=1.2.4 版本中 AES 加密时采用的 key 是硬编码在代码中的,这就为伪造 cookie 提供了机会。只要 rememberMe 的 AES 加密密钥泄露,无论 shiro 是什么版本都会导致反序列化漏洞

Shiro-550(CVE-2016-4437)

特征:返回包中包含rememberMe=deleteMe字段。

影响版本:shiro<1.2.24

环境搭建

git clone https://github.com/apache/shiro.git
cd shiro
git checkout shiro-root-1.2.4

注意导入代码部署的文件夹是samples/web,不是web

1725537877_66d99e55a2e8ecef9992c.png!small?1725537877933

打开shiro-shiro-root-1.2.4/pom.xml文件,并把jstl依赖版本改为1.2

1725537915_66d99e7b53f95c949b7c5.png!small?1725537915699

使用IDEA打开项目,选择shiro-shiro-root-1.2.4文件夹,最后点击OK

1725537989_66d99ec5eb043423b2efc.png!small?1725537990452

IDAE会自动下载依赖项,下载完成后编辑运行配置,设置为Tomcat本地服务器运行,然后JRE选择Java8版本

1725538007_66d99ed70a7819ca14aa6.png!small?1725538007418

然后点击部署,工件选择samples-web:war或 samples-web:war exploded

1725538022_66d99ee6a805777b331b9.png!small?1725538023070

最后点击运行即可,出现下面界面即代表配置成功

1725538038_66d99ef6158d6b4f9fe0f.png!small?1725538038541

漏洞分析

加密流程分析

首先,分析Apache官方通告(https://issues.apache.org/jira/projects/SHIRO/issues/SHIRO-550?filter=allissues)提到CookieRememberMeManager,以此为入口点进行分析。

1725538062_66d99f0e7f1ceefa5bba6.png!small?1725538063050

CookieRememberMeManager继承AbstractRememberMeManager

1725538076_66d99f1c84c84ff5a5ebe.png!small?1725538077235

AbstractRememberMeManager实现了RememberMeManager接口中的多个方法,主要是密钥、加解密、序列化/反序列化相关功能。另外包括登录成功、失败的处理,查看调用发现在DefaultSecurityManager中

1725538095_66d99f2f6e36399f8f828.png!small?1725538095952

DefaultSecurityManager是登录处,只要登陆都会经过这里的Login方法,从这里开始分析。在这里下个断点,然后authenticate方法验证凭证的正确性,如果不正确就不走下面的onSuccessfulLogin方法:

1725538113_66d99f419472ac94715c1.png!small?1725538114330

F7步入后,F8步过forgetIdentity,if判断中的isRememberMe方法判断是否勾选RememberMe,如果没有就不走rememberIdentity,rememberIdentity是加密的关键方法:

1725538130_66d99f526af9a31d17440.png!small?1725538130812

F7进入rememberIdentity方法,调用getIdentityToRemember(),作用就是获取用户名赋值给 principals。然后继续F7进入rememberIdentity方法:

1725538161_66d99f716e1af4f9aca57.png!small?1725538161753

1725538178_66d99f823d0ce6269d99a.png!small?1725538178513

1725538192_66d99f909e01a55b6db5c.png!small?1725538192914

进到convertPrincipalsToBytes方法,会序列化principals对象,也就是登陆名称root字符串。

1725538209_66d99fa19d481ef3ac26c.png!small?1725538210006

往下走,就到了加密的函数位置,会上步骤中序列化principals对象进行加密处理。getCipherService()获取加密方式AES/CBC/PKCS5Padding

1725538224_66d99fb087438a882d2f0.png!small?1725538224878

1725538242_66d99fc2190c32fbc718d.png!small?1725538242681

在下一行代码的if判断中通过getEncryptionCipherKey进行获取密钥进行加密,那么重点分析下这段代码逻辑:首先进入getEncryptionCipherKey(其实所有功能都在AbstractRememberMeManager文件中)

发现getEncryptionCipherKey函数中直接返回了一个encryptionCipherKey属性值,也就是密钥。

1725538339_66d9a0230bed8413d78ba.png!small?1725538339505

encryptionCipherKey属性是AbstractRememberMeManager中定义的一个私有属性

1725538353_66d9a0310ec48f27568a6.png!small?1725538353371

那么需要找到赋值的位置,在setEncryptionCipherKey方法中

1725538367_66d9a03f7f92aa1d23187.png!small?1725538367830

继续找调用setEncryptionCipherKey的位置,发现在setCipherKey方法中

1725538381_66d9a04d113cf4bddb9bb.png!small?1725538381392

继续找调用setCipherKey的位置,发现在构造方法中

1725538395_66d9a05b9dfa4f7180baf.png!small?1725538396213

构造方法的setCipherKey传参了一个属性值DEFAULT_CIPHER_KEY_BYTES,该值即加密密钥,直接默认固定写死在代码中。

1725538405_66d9a065724b119832732.png!small?1725538405954

最终可以顺推一下密钥生成获取的流程。

1725538426_66d9a07a34cb36652e95f.png!small?1725538426525在回到getDecryptionCipherKey获取密钥的位置,进入encrypt函数传参密钥和原始明文进行加密

1725538442_66d9a08aa366fdfbbcd3d.png!small?1725538443432

encrypt函数中首先随机生成16位的IV。

1725538454_66d9a096d4eece8e296af.png!small?1725538455515

1725538469_66d9a0a585b807b6f87f7.png!small?1725538470086

获取到IV后回到encrypt函数中执行encrypt函数执行加密处理

1725538486_66d9a0b60001432a6666e.png!small?1725538486496

加密原始明文数据,并将IV拼接到加密后的数据前16位

1725538501_66d9a0c5c2c5c3b3d79a9.png!small?1725538509436

到此加密流程已经结束,回到rememberIdentity方法中,会进入rememberSerializedIdentity方法,F7步入会跳到AbstractRememberMeManager的子类CookieRememberMeManager中,其实现了rememberSerializedIdentity方法。此方法会将上步骤中的IV和加密数据进行BASE64编码后存入Cookie字段的rememberMe中。

1725538542_66d9a0ee98918efe73c2d.png!small?1725538543066

整体加密流程并不复杂:

1、序列化principals对象,对象中保存了登录用户名(root)

2、加密算法AES/CBC/PKCS5Padding,iv是16位随机生成的值

3、将1)中序列化后principals对象的值跟DEFAULT_CIPHER_KEY_BYTES进行AES加密,将2)中随机生成的iv拼接到加密后的字符串最前面

4、Base64编码生成Base64字符串,写入Cookie中rememberMe的值

解密流程分析

解密时就不一定涉及到登录了,那么就需要从请求发起时进行跟进。可从哪个位置开始下断点呢?

Javaweb是根据web.xml文件配置路由,通过该路由指定过滤器等,在web.xml里面看到以下配置:

1725538555_66d9a0fbb72bbbbb47a34.png!small?1725538556214

说明访问根目录就要走ShiroFilter过滤器,那么跟进过滤器进行分析:

ShiroFilter继承于AbstractShiroFilter类,AbstractShiroFilter又继承于OncePerRequestFilter类

1725538569_66d9a10993a9bd5116ad2.png!small?1725538569981

1725538583_66d9a117f24b3f0052ee4.png!small?1725538584331

OncePerRequestFilter类就是开始的地方,无论是GET还是POST都会从这里开始,但是呢中间会有很多各种调用,会比较繁琐,部分无关的会直接跳过,到关键函数位置再细致分析:

1725538602_66d9a12a41d567e9ef102.png!small?1725538602918

为了分析简单,可以直接构造Get方法的Cookie头部rememberMe发包。

1725538635_66d9a14baeecab87d7647.png!small?1725538636255

F7进入doFilterInternal方法,在createSubject位置继续F7进入

1725538650_66d9a15a7628e7221d97d.png!small?1725538650877

F7进入buildWebSubject方法

1725538680_66d9a178a89def7dc4761.png!small?1725538681070

F7进入buildSubject方法

1725538702_66d9a18e467ac2f07108c.png!small?1725538702648

F7进入createSubject方法

1725538724_66d9a1a4a3198f31c13e0.png!small?1725538724930

可以看到关键的resolvePrincipals方法了,

1725538743_66d9a1b74c65f31dcdd58.png!small?1725538743894

F7进入resolvePrincipals方法,就会跳到DefaultSecurityManager.java文件中,也就是之前分析shiro登录加密最开始下断点的文件。

1725538757_66d9a1c5ef6c05cd4048a.png!small?1725538758389

F7跟进getRememberedIdentity方法进去,继续往下跟getRememberedPrincipals方法

1725538772_66d9a1d4d55af130e3a96.png!small?1725538773418

可以看到两个方法getRememberedSerializedIdentityconvertBytesToPrincipals,后面本不用太过于分析了,跟加密流程大体相当。

1725538788_66d9a1e4ca7a7b53159ca.png!small?1725538789179

  • getRememberedSerializedIdentity:获取cookie的RememberMe的值并进行Base64解码
  • convertBytesToPrincipals:获取到的密文进行解密并进行反序列化

convertBytesToPrincipals方法中获取前16位作为iv进行解密:

1725538814_66d9a1fe66686a7bff5e2.png!small?1725538814710

1725538834_66d9a212bf7ae026816a3.png!small?1725538835596

防守侧分析

推荐一款由ABC_123大佬开发的蓝队分析研判工具箱。

1、正常登录的原始明文内容形式如下:

1725538849_66d9a221a7b73a0ef970d.png!small?1725538850251

2、攻击流量解密后的内容

1725538869_66d9a235a9996b5b85070.png!small?1725538870316

可以很明显发现恶意攻击的流量,rememberMe字段的值会很长,而且解密后存在很多可疑的反序列化类。

那么在日常分析过程中,在分析大量shiro流量时,可以先根据rememberMe字段的长度进行排序,优先分析长度最长的流量;然后对rememberMe字段进一步解密,分析原始内容,确定shiro攻击的真实性。

参考链接:

https://xxe.icu/shiro_deserialization_vulnerability.html#%E6%BC%8F%E6%B4%9E%E5%88%A9%E7%94%A8

https://www.cnblogs.com/1vxyz/p/17572415.html

# 漏洞 # web安全 # 漏洞分析
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录