环境搭建

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.1</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-web -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.1</version>
</dependency>

漏洞成因

在Shiro721中,remeberMe Cookie默认通过AES-128-CBC模式进行加密,这种加密容易受到Padding Oracle Attack攻击,攻击者可通过Padding Oracle加密生成的攻击代码作为remeberMe Cookie的值来实现反序列化前缀

利用条件

Shiro <1.4.2

对比Shiro 550的改进

找到org.apache.shiro.mgt.AbstractRememberMeManager类,在Shiro 1.2.4版本中,该类下存在一个默认key,其构造方法将默认key设置为Cookie加解密的密钥:

image-20250921170356971

而在Shiro 1.4.1版本中,则不再定义默认key,key是通过cipherService.generateNewKey().getEncoded()来获取

image-20250921170608595

而通过测试发现每次生成的key都会发生变化:

1
2
AesCipherService cipherService = new AesCipherService();
cipherService.generateNewKey().getEncoded()

Padding Oracle Attack

CBC加解密过程

加密:

img

即第一组明文在加密运算前与IV异或;从第二组开始,所有明文都先与前一组密文异或

解密:
img

解密过程则相反,第一组密文在加密运算前与初始向量IV异或;第二组密文解密后都会和前一组密文异或

PKCS5填充模式

因为Shiro主要采用PKCS5方式填充,该模式是以8字节填充,以完整字节填充。当待加密的数据长度刚好满足分组长度的倍数时,仍然需要填充一个分组长度。

img

CBC字节翻转攻击

异或的特点:

  • 相同字符之间异或为0
  • 任何字符与0异或都为本身

假设我们有第N-1组的密文某一位的值A,也知道第N组密文解密后的中间值与A相同位的值为B,那么我们容易得到第N组该位上的明文C

但同时,如果我们通过遍历修改第N-1组每一位的值在与中间值异或,那么就能达到控制明文为我们想要的内容

即通过CBC字节翻转攻击,只要我们能触发加解密过程,且能获得每次加密后的密文,那我们就能在不知道key的情况下,通过修改密文或IV,来控制输出明文为自己想要的内容,且只能从最后一组开始,并且每改完一组,都要重新获取一遍解密后的数据,要根据解密后的数据来修改前一组密文值

Padding Oracle Attack原理

Padding Oracle:在解密时,如果算法发现解密后得到的结果填充方式不符合规则,那么就表示输入数据有问题,对于解密的库来说往往会抛出一个异常来提示Padding不正确。

Padding Oracle Attack,填充提示攻击,主要通过根据CNC字节翻转攻击、Padding填充规则以及服务端解密后返回的不同状态来穷举中间值进而获取明文的攻击。仅针对CBC分组模式的攻击而不是针对某个加密算法的攻击

攻击条件:

  • 攻击者能够获取到密文,以及密文对象的初始向量IV
  • 攻击者能够触发密文的解密过程,并且能够知道密文的解密结果是否正确

服务器对于使用CBC模式加密敏感信息进行操作时,先是检测密文最后一组的填充值是否正确来确定能否进行正常解密,如果错误直接返回错误,如果正确则进一步判断解密后的内容是否正确

一般分为三种情况:

  • 密文不能解密
  • 密文能解密但解密结果不对
  • 密文能解密且结果正确

攻击者可以通过服务端解密后的响应状态来判断填充的字节是否正确来进行穷举攻击

漏洞原理

Shiro就是通过不同情况Cookie时服务器的响应来进行穷举的

有三种情况:

  • Padding错误
  • padding正确,反序列化失败
  • 反序列化成功

注意:对于Java来说,反序列化是以Stream的方式按顺序进行的,向其后添加或更改一些字符串并不会影响正常反序列化,也就是Java序列化数据后的脏数据不影响反序列化结果。