shiro 550
在Shiro <= 1.2.4中,在反序列化过程中所需要用到AES加密的KEY是一个固定在源码中写死的值,攻击者可以通过源码分析加密流程,对恶意代码进行加密,然后通过rememberMe字段进行传递,在服务端进行恶意代码执行
漏洞原理(shiro-550)
当用户勾选RememberMe进行登录的时候,shiro会将用户序列化之后的cookie值先进行AES加密,然后进行base64编码并序列化在储存在用户cookie的rememberMe字段中,然后在解密流程先是会进行BASE64解码,然后AES解密,最后在进行反序列化操作(漏洞利用点)
shiro 721
在后续的版本更新中,修复了之前shiro-550的固定KEY值,转为服务启动的时候动态生成一个KEY,然后后续的流程还是没变
影响版本
Shiro < 1.4.2
漏洞原理
在cookie中使用的是AES-128-CBC模式进行的加密,在加密流程中使用了Padding填充,这里导致用户可以通过Padding Oracle攻击来生成攻击代码来构造恶意的rememberMe字段,然后触发反序列化攻击,最终导致的任意代码执行。
漏洞背景
AES加密
- AES是一种对称加密的分组加密算法。
- 分组长度固定为128bit
- 密钥长度可变,128bit、192bit、256bit
- AES应密钥长度可以称为AES-128,AES-192,AES-256
AES加密标准 | 密钥长度 | 分组长度 | 加密轮数 |
---|---|---|---|
AES-128 | 128 bits ( 4 Bytes × 32 bits/Bytes ) | 128 bits | 10 |
AES-192 | 192 bits ( 6 Bytes × 32 bits/Bytes ) | 128 bits | 12 |
AES-256 | 256 bits ( 8 Bytes × 32 bits/Bytes ) | 128 bits | 14 |
分组模式
分组加密有 5 种可选方式:
ECS
( Electronic Codebook Book , 电话本模式 )CBC
( Cipher Block Chaining , 密码分组链接模式 )CTR
( Counter , 计算器模式 )CFB
( Cipher FeedBack , 密码反馈模式 )OFB
( Output FeedBack , 输出反馈模式 )
在shiro中使用的就是AES的CBC加密模式
Padding填充
Padding填充是为了解决分组可能不能恰好分成block的整数倍的问题,对于不能整除剩余的部分数据就涉及到填充操作。
比较常用的是PKCS#5
和PKCS#7
(在shiro中使用的是PKCS#5)
-
PKCS#5:PKCS#5是8字节填充的,即填充一定数量的内容,使得成为8的整数倍,而填充的内容取决于需要填充的数目。
例如,串
0x56
在经过PKCS#5填充之后会成为0x56 0x07 0x07 0x07 0x07 0x07 0x07 0x07
因为需要填充7字节,因此填充的内容就是7。- 1个字节的padding为0x01
- 2个字节的padding为0x02,0x02
- 3个字节的padding为0x03,0x03,0x03
- 4个字节的padding为0x04,0x04,0x04,0x04
在特殊情况下,如果已经满足了8的整倍数,按照PKCS#5的规则,仍然需要在尾部填充8个字节,并且内容是
0x08
,目的是为了加解密时统一处理填充。 -
PKCS#7:PKCS#7与PKCS#5的区别在于PKCS#5只填充到8字节,而PKCS#7可以在1-255之间任意填充。
在解密时会校验明文的填充是否满足该规则,如果是以N个0x0N结束,则意味着解密操作执行成功,否则解密操作失败。
CBC模式
CBC模式是分组加密中的一种
加密流程
CBC主要是引入一个初始化向量(IV)来加强密文的随机性,保证相同明文通过相同的密钥加密的结果不一样。
- 明文经过填充后,分为不同的组block,以组的方式对数据进行处理
- 初始化向量(IV)首先和第一组明文进行XOR(异或)操作,得到
中间值
- 采用密钥对中间值进行块加密,删除第一组加密的密文 (加密过程涉及复杂的变换、移位等)
- 第一组加密的密文作为第二组的初始向量(IV),参与第二组明文的异或操作
- 依次执行块加密,最后将每一块的密文拼接成密文
然后因为初始化向量IV是随机生成的,所以IV常常是放在密文之前的,解密的时候先获取前面的IV,然后再对后面的密文进行解密
解密流程
- 会将密文进行分组(按照加密采用的分组大小),前面的第一组是初始化向量,从第二组开始才是真正的密文
- 使用加密密钥对密文的第一组进行解密,得到”中间值“
- 将中间值和初始化向量进行异或,得到该组的明文
- 前一块密文是后一块密文的IV,通过异或中间值,得到明文
- 块全部解密完成后,拼接得到明文,密码算法校验明文的格式(填充格式是否正确)
- 校验通过得到明文,校验失败得到密文
Padding Oracle Attack
Padding Oracle Attack 是一种针对CBC模式分组加密算法的攻击,知道padding model,解密获得明文。
在进行Padding Oracle Attack进行爆破测试的时候是需要服务端返回两个不同的响应特征来进行bool判断(因为需要进行爆破所以在现实情况下的利用还是比较少的):
在Shiro中:
- 失败时:应报文的Set-Cookie头字段返回
rememberMe=deleteMe
- 成功时:返回正确的响应报文内容
Padding-Oracle 解密明文
前提
- 服服务器会对解密结果进行Padding校验,给出响应,在判断解密结果的正确性
- 攻击者能够获取到初始IV和全部密文
我们可以利用padding校验的特性(检验填充的字符是否符合规范),我们传递
恶意的IV(eval_IV)
+第一组密文(Ciphertext[0])
爆破流程
-
尝试通过使
eval_IV
的最后一个字节从0x00~0xff开始爆破,让Plaintext[0]
的最后一个字节位0x01(也就是说使得服务端返回值为200) -
然后根据解密流程可知,我们传递的
eval_IV
与解密之后的中间值进行异或操作得到的明文,根据异或的性质可以得到Middle[0][-1] XOR eval_IV = Plaintext[0] => Middle[0][-1] = Plaintext[0] XOR eval_IV
-
接下来就是尝试使
eval_IV
的倒数第二个字节从0x00~0xff开始爆破,让Plaintext[0]
的倒数第二个字节位0x02(最后一个字节可以通过计算得到),然后就是倒数第三个字节,依次类推,直到得到第一组完整的Middle -
然后根据加密流程可以得知
Plaintext XOR IV = Middle && Middle XOR eval_IV = Plaintext => Plaintext = Middle(通过eval_IV求解) XOR IV(已知) XOR eval_IV(爆破)
-
然后第二部分以及之后的部分使用的都是前一部分的密文值作为IV
-
所以我们可以根据这样依次类推我们得到全部的明文
这样我们就在不知道KEY的情况下获取了全部的明文
Padding-Oracle 恶意明文构造密文
通过明文构造密文其实和之前解密明文的流程基本上就是相反的
- 首先我们先随机伪造最后一组的密文
- 然后我们可以根据之前求解明文的方式得到eval_IV
- 然后根据之前得到的
Plaintext = Middle XOR IV XOR eval_IV
=>IV = Plaintext(已知) XOR Middle(通过eval_IV求解) XOR eval_IV(爆破)
- 这里根据加密的流程可知,后一组加密过程中使用的使前一组的密文,所以我们就可以得到倒数第二组的密文就是IV
- 然后我们就可以依次获取所以有的密文和最后的IV(也就是恶意的初始IV)
- 然后把IV和密文拼接就是我们需要构造的恶意rememberMe
CBC字节翻转攻击
在AES的CBC模式中除了padding Oracle攻击还有一种攻击方式CBC字节翻转攻击
CBC字节翻转攻击的方式可以总结成通过修改密文进而篡改明文
根据解密流程可以推导出公式
Plaintext[0] = Decrypt(Cipher[0]) XOR IV (N=0)
Plaintext[N] = Decrypt(Cipher[N]) XOR Cipher[N-1] (N>=1)
如果我们想修改第N组的明文可以通过修改第N-1组的密文来解决