freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

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

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 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录