Shiro漏洞
Shiro漏洞主要指的是Apache Shiro框架中存在的安全漏洞,这些漏洞可能被攻击者利用来执行恶意代码或获取敏感信息。Apache Shiro是一个功能强大且易于使用的Java安全框架,它提供了身份验证、授权、密码学和会话管理等功能,用于保护应用程序的安全。然而,由于框架设计或实现上的缺陷,Shiro在某些情况下会存在安全漏洞。
Shiro漏洞的类型
Shiro漏洞的类型多种多样,但其中较为著名的是**反序列化漏洞**,特别是与“Remember Me”功能相关的漏洞。这些漏洞允许攻击者通过构造恶意的序列化数据来触发反序列化操作,进而执行任意代码或获取系统权限。
Shiro服务器端识别身份加解密处理cookie的流程
加密
- 用户使用账号密码进行登录,并勾选“RememberMe”。
- Shiro验证用户登录信息,通过后,查看用户是否勾选了”RememberMe”。
- 若勾选,则将用户身份序列化,并将序列化后的内容进行AES加密,再使用base64编码。
- 最后将处理好的内容放于cookie中的rememberMe字段。
解密
- 当服务端收到来自未经身份验证的用户的请求时,会在客户端发送请求中的cookie中获取rememberMe字段内容
- 将获取到的rememberMe字段进行base64解码,再使用AES解密。
- 最后将解密的内容进行反序列化,获取到用户身份。
Shiro反序列化漏洞的示例
Shiro-550(CVE-2016-4437)
- 漏洞介绍
shiro-550主要是由shiro的rememberMe内容反序列化导致的命令执行漏洞,造成的原因是默认加密密钥是硬编码在shiro源码中,任何有权访问源代码的人都可以知道默认加密密钥。于是攻击者可以创建一个恶意对象,对其进行序列化、编码,然后将其作为cookie的rememberMe字段内容发送,Shiro将对其解码和反序列化,导致服务器运行一些恶意代码。
- 特征:
- cookie中含有rememberMe字段, 如:”rememberMe=JV+gEljeMVBj3EFY22otzX…”
- cookie中含有”rememberMe=deleteMe”
- 漏洞原理:Shiro框架在登录时,如果勾选了“Remember Me”的功能,关闭浏览器再次访问时便无需再次登录。此时,cookie中会增加一个rememberMe字段,其value的值是经过序列化、AES加密和Base64编码后得到的结果。由于AES加密的密钥被硬编码在代码中,攻击者可以通过构造恶意的序列化数据,使用相同的密钥进行AES解密和Java反序列化,从而执行任意代码。
- 影响范围:Apache Shiro <= 1.2.4
复现:
初始界面

抓包存在remember me字段

接下来利用shiro反序列化工具,爆破加密密钥和利用链

然后命令执行
- 修复建议:
- 更新shiro到1.2.4以上的版本。
- 不使用默认的加密密钥,改为随机生成密钥
Shiro-721(CVE-2019-12422)
- 漏洞介绍
Shiro-721反序列化漏洞,使用由于Shiro通过使用AES-128-CBC模式进行cookie中rememberMe字段的加密,于是用户可通过Padding Oracle加密生成的攻击代码来构造恶意的rememberMe字段,并重新请求网站,进行反序列化攻击,最终导致任意代码执行。
- 漏洞原理:与Shiro-550类似,但加密方式改为了AES-CBC加密,且密钥是系统随机生成的。然而,这种加密模式可以通过Padding Oracle Attack(填充攻击)来绕过。攻击者可以使用有效的RememberMe Cookie作为前缀,然后精心构造恶意的RememberMe Cookie来实施反序列化攻击。
- 影响范围:Apache Shiro<1.4.2
AES 的 CBC 模式
AES加密算法全称是AdvancedEncryptionStandard(高级加密标准),是最为常见的对称加密算法之一。AES的区块长度固定为128位,密钥长度则可以是128,192或256位。分组密码在加密时明文分组的长度是固定的,而使用中待加密消息的数据量是不定的,数据格式可能是多种多样的。为了能在各种应用场合安全地使用分组密码,通常对不同的使用目的运用不同的工作模式。AES有五种工作模式:
电码本模式(Electronic Codebook Book,ECB)
密码分组链接模式(Cipher Block Chaining,CBC)
计算器模式(Counter,CTR)
密码反馈模式(Cipher FeedBack,CFB)
输出反馈模式(Output FeedBack,OFB)。
而在Shiro721中使用的就是CBC模式。
密码分组链接模式(CipherBlockChaining,CBC),其中”分组“是指加密和解密过程都是以分组进行的。每一个分组大小为128bits(16字节),如果明文的长度不是16字节的整数倍,需要对最后一个分组进行填充(padding),使得最后一个分组长度为16字节。”链接”是指密文分组像链条一样相互连接在一起。
**加密过程**:(串行运算,需要上一个组的密文,才能进行下一组的异或)
1.发送方将明文(Plaintext)成若干分组(Plaintext[1],,Plaintext[n]),每个分组16个字节,不够则填充。
2.生成一个跟分组长度一致(16个字节)的IV(InitializationVector,初始向量),用于后续的异或运算。
3.将IV与第一个明文分组(Plaintext[1])进行异或得到m1(可以看做一个中间值)。
4.将m1使用密钥Key进行加密得到第一个密文分组(Ciphertext[1])。
5.将上一步得到的密文与下一个明文分组进行异或,得到一个新的中间值,再将这个中间值使用Key进行加密得到后续的一个密文分
组。接着重复这个过程直到所有明文分组被加密。
6.为了接收方能够成功解密,还需要将IV也发送给接收方。为描述方便,这里把将IV当成Ciphertext[0],发送时会将IV作为密文的第一个分组,最后将后续密文分组按顺序拼接即可得到最终的密文(Ciphertext)**解密过程**(并行运算,知道所有密文分组,只需要单个解密即可)
- 接收方将密文(Ciphertext) 分成若干分组(Ciphertext[1],..,Ciphertext[n])。
- 将第一个密文分组(Ciphertext[1])使用密钥Key进行解密,得到中间值m1。
- 将IV(Ciphertext[o])与m1进行异或运算得到第一个明文分组(Plaintext[1])。
- 将下一个密文分组使用Key进行解密得到一个新的中间值,而再将上一个密文分组与该中间值进行异或即可得到后续的一个明文分组。重复这个过程直到所有密文分组被解密。
- 最后将所有明文分组组合在一起即可获取到完整的明文(Plaintext)。
Padding Oracle 攻击原理
Padding的含义是“填充”,在解密时,如果算法发现解密后得到的结果,它的填充方式不符合规则,那么表示输入数据有问题,对于解密的类库来说,往往便会抛出一个异常,提示Padding不正确。Oracle在这里便是“提示”的意思,和甲骨文公司没有任何关系。
分组填充方式(PKCS5Padding,PKCS7Padding)
因为分组加密方式只能使用一个固定大小的密钥加密相同字节长度的明文(一般长度为8个字节或16个字节),所以需要将加密的明文按照密钥大小拆分为多块(所以也叫块加密),如果拆分后最后一个块明文长度不够,就需要填充字节来补齐长度。按照常见的PKCS#5或PKCS#7公钥加密标准,最后需要填充几个字节,那么每个填充字节的值就用所需填充的字节数,若最后一个明文刚好符合固定长度,就需填充一个完整分组。
通过下图我们可以更好的进行理解:
我们假设每个分组8个字节,当最后一个分组为8个字节长度时,就再填充8个字节,且每个字节的值都为16进制的“8”,即0x08。若最后一个分组为7个字节,则需要填充1个字节,该字节的值为0x01。以此类推,6个字节就需填充两个字节,都为0x02。
那么它如何判断填充是否错误?当将密文解密后,其会检查明文最后的一个字节,若发现其为0x02,则继续检查倒数第二个字节是否为0x02,若倒数第二个字节不是0x02,则判断出填充错误。其判断方式就是通过去读明文最后一个字节填充的字节,根据该字节的值,继续向前检查。
Padding Oracle
当我们知晓IV与密文,输入点可控(能够任意输入IV与任意密文交由解密器解密),且当密文解密出错时,能够判断出是否是由于填充错误造成的,就能在不知道对称密钥的情况下,通过构造明文分组中不同的填充值,再利用填充时的错误回显或是时间延迟,进行爆破,推测出密文解密后的中间值,进而可以推测出原始明文,或是利用中间值结合特定的V构造出想要的明文。而这个利用错误回显或是时间延迟做判断的过程就称为oracle。
接下分析该攻击的具体实现流程,前面我们知道了分组的填充方式以及如何判断填充是否错误,这里我们可以从第一个分组开始进行分析。
- 关键词说明:
- Plaintext:明文,Plaintext[-n]:明文分组中最后第n个字节
- m:中间值,由IV与Plaintext进行异或运算得到,m[-n]:中间值的最后第n个字节
- oIV:初始向量,IV[-n]:初始向量的最后第n个字节
- G_IV:构造的IV,G_IV[-n]:构造的IV的最后第n个字节
采用CBC模式进行解密时,其会将密文分组解密为一个中间值m,而后再将m与IV进行异或得到最后的明文分组。当我们可以控制输入的IV与密文时,我们可以先只输入第一个密文分组,而将其解密后得到的就是完整的明文,是没有填充字节的,这必定会触发填充错误。于是我们可以尝试构造一个G_IV,使得中间值m与G_IV进行异或后得到的明文的最后一个字节Plaintext[-1]为0x01,这样就不会出现填充错误。
那么如何找到这个G_IV呢?我们可以将G_V的前面7个字节全部设置为0(这样不会改变明文中的前七个字节),而最后一个字节G_IV[-1]从0x00开始到0xFF(一个字节为8位,最多为256种可能)进行尝试,当解出明文的最后一个字节不为0x01就会发生填充错误,由此进行判断,最后我们必然找的到一个G_IV[-1]使得”G_IV[-1] xor m[-1]=0x01”。 找到这个G_IV[-1]后,我们可以有以下推论:
1
2
3 G_IV[-1] xor m[-1]=0x01 #找到一个G_IV[-1]与m[-1]异或为0x01
G_IV[-1] xor 0x01=m[-1] #G_IV[-1]与0x01异或得到解密后的m[-1]
IV[-1] xor m[-1]=Plaintext[-1] #将m[-1]与原本的Iv[-1]异或就会得到明文的最后一个字节 知道了m[-1],这个同理我们可以继续构造G_IV[-1] xor m[-1]=0x02,G_IV[-2] xor m[-2]=0x02。因为m[-1]知道所以很容易得到G_IV[-1],而后我们就可以将G_IV[-2]从0x00开始到0xFF,必定会找到GIV[-2]使得“GIV[-2] xor m[-2]=0x02”,此时不会发生填充错误。
1
2
3 G_Iv[-2] xor m[-2] = 0x02
G_IV[-2] xor Ox02 = m[-2]
Iv[-2] xor m[-2] = Plaintext[-2] 于是可以继续构造G_IV[-3]……G_IV[-8],我们就可以得到该明文分组的所有字节。接下来我们就可以在后续的分组中使用该方法来获取所有明文分组,但是后续的明文分组是使用上一个密文分组来进行异或,所以我们需要修改的是前一个密文分组。
注意:当对最后一个密文分组的中间值进行猜解的时候,会遇到明文本身最后一个字节为填充字节,如0x02,可能会得出不同的两个结果。我们将最后一个字节构造成0x01时,无论前面字节是什么都不会触发填充错误,而将最后一个字节构造成0x02同样也不会报错。这时我们可以在最后一个填充字节判断成功的情况下,构造倒数第二字节为任意值都不出现填充错误,则明文最后一个字节就构造成了0x01。
CBC翻转攻击
我们了解了如何猜解出中间值,并进一步通过中间值来得到明文。当我们知道其解密后的中间值,就可以构造一个ⅣV使得二者异或得到的明文为我们想要的明文,从而完成攻击。
具体攻击细节我们通过上图来了解,如上图所示,我们需要修改明文分组3的内容,就可以修改密文分组2的内容,让其与中间值3异或运算得到我们想要的结果。但是修改了密文分组2会让其解密后的中间值乱码(损坏),最后得到的明文会是乱码,所以我们需要通过前面的填充攻击的方式猜解出损坏的中间值,再通过修改密文分组来还原该明文分组。同理密文分组1对应的明文分组1也可通过修改IV来还原,最终我们就修改了明文分组3,而且其他明文不变。使用这种方法我们也可以修改整个明文以及添加新的明文。
复现:
漏洞的危害
Shiro反序列化漏洞的危害主要包括远程代码执行(RCE)、获取系统权限、敏感信息泄露等。攻击者可以利用这些漏洞来执行恶意代码,控制受影响的系统,进而进行更严重的攻击行为。
防护措施
为了防止Shiro反序列化漏洞的攻击,可以采取以下防护措施:
- 升级Shiro版本:及时关注Apache Shiro的官方公告和更新信息,将Shiro框架升级到最新版本。
- 密钥管理:避免将密钥硬编码在代码中,采用更安全的方式生成和管理密钥。
- 输入验证和过滤:对用户输入的数据进行严格的验证和过滤,确保输入的数据符合预期的格式和类型。
- 限制反序列化的类:在反序列化操作中,指定允许反序列化的类,防止恶意用户通过构造特定类型的对象来触发反序列化漏洞。
- 安全审计和代码审查:定期进行安全审计和代码审查,检查是否存在其他潜在的安全风险和漏洞。
综上所述,Shiro漏洞是Apache Shiro框架中存在的安全漏洞之一,主要包括反序列化漏洞等类型。为了防止这些漏洞的攻击,需要采取相应的防护措施来加强系统的安全性。



