0


shiro 721 反序列化漏洞复现与原理以及Padding Oracle Attack攻击加解密原理

文章目录

1. 前置知识

1.1 shiro550利用条件

知道aes加密的key且目标服务器含有可利用的攻击链。

原理

在Shiro <= 1.2.4中,反序列化过程中所用到的AES加密的key是硬编码在源码中,当用户勾选RememberMe并登录成功,Shiro会将用户的cookie值序列化,AES加密,接着base64编码后存储在cookie的rememberMe字段中.

服务端收到登录请求后,会对cookie的rememberMe字段的值进行base64解码,接着进行AES解密,然后反序列化。由于AES加密是对称式加密(key既能加密数据也能解密数据),所以当攻击者知道了AES key后,就能够构造恶意的rememberMe cookie值从而触发反序列化漏洞。

影响版本:1.2.4以下

解决payload过长的方式:

  1. 使用urlclassloader加载远程字节码
  2. 将字节码放在post的body中,恶意类实现加载body中的字节码即可.

1.2 shiro721利用条件

知道已经登陆用户的合法cookie且目标服务器含有可利用的攻击链就可以进行漏洞利用。

原理

shiro721用到的加密方式是AES-CBC,而且其中的ase加密的key基本猜不到了,是系统随机生成的。而cookie解析过程跟cookie的解析过程一样,也就意味着如果能伪造恶意的rememberMe字段的值且目标含有可利用的攻击链的话,还是能够进行RCE的。

通过Padding Oracle Attack攻击可以实现破解AES-CBC加密过程进而实现rememberMe的内容伪造。下面会有单独的篇幅讲Padding Oracle Attack。
影响版本:

1.2.5,
1.2.6,
1.3.0,
1.3.1,
1.3.2,
1.4.0-RC2,
1.4.0,
1.4.1

shiro-721对cookie中rememberMe的值的解析过程

在这里插入图片描述

1.3 基于返回包的shiro特征检测

1. 根据返回包中是否有rememberMe=DeleteMe

在这里插入图片描述

构造恶意的rememberMe数据,看房回包中是不是rememberMe=DeleteMe。

2. 使用序列化的SimplePrincipalCollection类的对象

发送一个经过序列化的SimplePrincipalCollection类的对象来判断。如果key正确,则返回包中不会有

rememberMe=DeleteMe

示例代码如下:

publicclassSimplePrincipalCollectionTest{@Testpublicvoidtest01()throwsIOException,NotFoundException,CannotCompileException{SimplePrincipalCollection simplePrincipalCollection =newSimplePrincipalCollection();AesCipherService aes =newAesCipherService();byte[] key =java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");ByteArrayOutputStream barr =newByteArrayOutputStream();ObjectOutputStream oos =newObjectOutputStream(barr);
        oos.writeObject(simplePrincipalCollection);
        oos.close();ByteSource ciphertext = aes.encrypt(barr.toByteArray(), key);System.out.println(ciphertext.toString());}}

Java Shiro SimplePrincipalCollection 探测密钥正确性

2. 环境搭建

git clone https://github.com/inspiringz/Shiro-721.git
cd Shiro-721/Docker
docker build -t shiro-721 .
docker run -p 8080:8080 -d shiro-721

3. 漏洞复现

先使用合法账户登陆,记得勾选remember Me,然后使用burp抓包获取cookie:
在这里插入图片描述

获取到cookie:

在这里插入图片描述

将其中的remember Me字段复制下来输入到工具中进行利用:

java -jar ysoserial.jar CommonsBeanutils1 "touch /tmp/123" > payload.class
python shiro_exp.py http://192.168.171.137:8080/login.jsp LIO2vKStP5R4NN+TLY0Bgfrz+3sacQHB1BfrOheCVAHeFAGtRsX9JW24tCvcedluOxZwFPoOSs7/tA0fK+UJ9ylRjLIT87NIN1smV22TVqdQ4vSJXB42IQCTV1mDA2CwlDpoeem6M4qY2SeB4JwIpV+iUwNJoOj+NfWeX3/lLZHkoCnsR5TCm6GrHyhdaDZYK0BAJNXFQ9658sJGAF1fztcfR0pYD9RtX26iLW73+D0pd3x6DhPQB7euA4uhUZ3Ue8RoOK3jTqxHC3U5n0DIMpc1RWlHVzUyHjejFAPXCReV+7ds/dWr+b5XlgP9/7ajmi2+6dqr2apVaIhEMC5SP4X4Y+QZw3wS6w76pD1vT8JSlG6l+h4+tIRuS4/gbUzX8GhmPCtw2MBMS/xZ2FsjvTPexdPLEf+114qo4152aNNcXul4zN3czLlve+otlqd5E/WyhhbBA2+EFk+Pewnsq2g2sS53s57H9BcWhXHkcwf0cIrkOXAn9a9xfkkm1HH9 payload.class

在这里插入图片描述
在这里插入图片描述

此 exp 爆破时间较长,建议使用 ysoserial 生成较短的 payload 验证(eg: ping 、 touch /tmp/success, etc),约 1 个多小时可生成正确的 rememberme cookie,生成成功后将自动停止运行。

在这里插入图片描述

在这里插入图片描述

最终会生成恶意的rememberMe cookie,我们使用这个cookie替换原数据包中的cookie。然后登陆进服务器看,会发现/tmp目录下被创建了一个123文件。

4. Padding Oracle Attack原理

这种攻击方式可以达到破解AES-CBC加密的效果。我们先了解几个基础知识。

4.1 分组密码填充

首先我们知道密码都是字符,一个字符8bit也就是1个byte。

aes加密的时候会将字符进行分组并填充,有可能是每8字节一组,也可能是16字节一组。

但是密码不可能恰好填满每个组,可能第一组填满了,第二组只有一个字符,这时候就需要进行填充。

如果明文密码分组完后刚好不需要填充,那么会额外添加一个组,这个组的数据全部都是0xXY,这个数据是多少取决于aes加密是多少个字节分一组的。具体举例如下:

假设密码为一个1,且加密算法中分组的逻辑是8个字节也就是64bit为一组。因为1只占1个字节,所以第一组中还有7个字节是空的。这时候第一组中的数据为:

1 0x07 0x07 0x07 0x07 0x07 0x07 0x07

如果明文恰好不需要填充比如是8个1,且分组逻辑还是8个字节也就是64bit为一组,那么分完组的明文如下:

第一组:

1 1 1 1 1 1 1 1 

第二组:

0x08 0x08 0x08 0x08 0x08 0x08 0x08 0x08

综上,填充的逻辑是,一定会填充数据,区别是填充一个组还是填充几位,如果分组后的明文最后一组中有N字节没有数据就填充0x0N。

如果分组后刚好合适,那么额外添加一个组,且这个组的明文数据一定

全是

0xXY,0xXY是多少取决于

aes加密的分组逻辑

填充了多少个数据

如果解密出的明文的结尾不是符合规律的0xXY,那么系统会判断解密失败。

4.2 AES-CBC模式算法

CBC主要是引入一个初始化向量(IV)来加强密文的随机性,保证相同明文通过相同的密钥加密的结果不一样。Shiro的IV通过源码我们可知是16byte的。因此分组也是16byte一组。

加密:

在这里插入图片描述

逻辑:

  1. 明文经过填充后,分为不同的组,以组的方式对数据进行处理
  2. 初始化向量(IV)首先和第一组明文进行XOR(异或)操作,得到得值称为middle。
  3. 采用密钥key,对middle进行加密生成第一组的密文。
  4. 第一组加密的密文作为第二组的初始向量(IV),参与第二组明文的异或操作。
  5. 依次执行块加密,最后将每一块的密文拼接成最终的密文。

由于初始化向量(IV)每次加密都是随机的,所以IV经常会被放在密文的前面,解密时先获取前面的IV,再对后面的密文进行解密。

密文的第一组就是IV。

解密:
在这里插入图片描述

逻辑:

  1. 将密文进行分组(按照加密采用的分组大小),前面的第一组是初始化向量IV,从第二组开始才是真正的密文。
  2. 使用加密密钥对密文的第一组进行解密,得到Middle,第一组中只要密文不变,那么Middle一定不会改变。
  3. 将Middle和初始化向量IV进行异或,得到该组的明文
  4. 前一块密文是后一块密文的IV,通过异或中间值,得到明文
  5. 块全部解密完成后,拼接得到明文,密码算法校验明文的格式(填充格式是否正确)
  6. 校验通过得到明文,校验失败得到密文

4.3 解密

假设密文跟IV的值如下:

初始化向量: 7B 21 6A 63 49 51 17 0F
第一组密文: F8 51 D6 CC 68 FC 95 37
第二组密文: 85 87 95 A2 8E D4 AA C6


我们首先要知道,系统判断解密是否成功的点是

解密后明文点最后一位或者几位的值跟aes算法中的分组逻辑是否匹配,匹配才能解密成功。

例如,解密后最后一组明文的最后一位值为0xFF,而aes算法的分组逻辑是8byte一组。那么这次解密一定是失败的。原因如下:

  1. 如果进行了明文填充,那么最后一位的值一定是小于0x08且大于0x01的。
  2. 如果没有进行明文填充而直接额外加了一个组,那么最后一位的值一定是0x08。

只有这两种可能性,而0xFF没在这两种可能性中,所以解密失败。


我们这时候选择只将IV跟第一段密文传入服务及进行解密,且将IV改成全0,就会有下面这张图:

在这里插入图片描述

我们只用第一组密文是因为第一组密文一定没有填充,也就是说解密出的明文不可能有0x01-0x08中的值(假设这个例子中是8字节一组)。

图中最后一行解密我们是看不到的,只会看到页面反馈是解密失败。这时候我们只需更改传进去的IV的最后一位,从0x00改到0xFF,在这个例子中直到当IV最后一位等于0x3C的时候,程序显示解密成功。

因为明文的最后一组一定会有填充物,而我们只传进去了IV跟一组密文,因此推断出这组密文被系统认为是最后一组密文,那么只有当这组密文的明文有合法填充位的时候解密才会成功,程序才不会报错。

因为我们知道这一组其实是密文中的第一组,也就一定不会有填充位,也就意味着所有的数据都不可能有0x01-0x08中的值(假设这个例子中是8字节一组)。

综上,这时候有

8种

可能性。因为明文填充位有可能是0x01到0x08八种,于是middle中的最后一位也就有八种可能性。这时候我们可以对这8种可能的middle进行分别实验,举例如下:


假设当明文填充位的数据为0x01-0x08时分别对应的middle最后一位的值为ABCDEFGH。

我们假设middle的最后一位是A,且填充位是0x01
这时候将IV最后一位的值进行更改,使得A

异或

IV的最后一位的值得到0x02。
然后对IV的倒数第二位进行爆破,从0x00到0xFF。

如果爆破成功

,网页返回200,就能确定最后一位填充位是0x01,且前面的数据都不是0x01-0x08中的。进而能确定middle的最后一位。

如果爆破失败,那将middle最后一位换成另一个,以此类推。

只要第二个数据能爆破成功,就能确定第一个数据一定正确。

最终我们测试出填充位是0x01,反推出middle中的最后一位是0x3D,IV最后一位 xor 0x01 = middle最后一位。

接下来就更改IV的最后两位,使得解密可以成功,此时如果解密成功,那么解密出的明文最后两位一定是0x02,由此可推算出middle的倒数第二位。快速的做法是根据刚得到的middle中最后一位的值来更改IV中最后一位的值,先使得得到明文的最后一位是0x02,然后其实只需要更改IV倒数第二位的值就可以了,极大的减少了运算次数,节省破解时间。

以此类推可以推断出middle的值,进而可以解密真正的明文。

第一组的密文会变成第二组密文的IV

,根据上面的逻辑可以继续进行解密,然后推断出所有的铭文数据。

4.4 加密

在这里插入图片描述

具体加密过程如下:

  1. 将要加密的数据分成N个块并进行数据填充,假设要加密的数据为hello,aes加密分组为8字节每组,那么明文填充后应该为hello\x03\x03\x03
  2. 创建两个明文组,内容为\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA,第二个为全0,将全0的分组跟全A分组链接,0分组在前A分组在后。
  3. 将第二步形成的联合分组加到shiro合法cookie的最后面,对全0分组的最后一位进行修改,直到页面不报错,或者说没有rememberMe=deleteme。此时明文的最后一位是0x01,因此能推断出middle最后一位的数据。
  4. 接着修改倒数第二位直到页面不报错,推断出middle倒数第二位的数据,以此类推。此时可以得到全A组那一部分中middle的所有数据,只要全A组数据不变,那么全A组的middle永远不会变,因为middle是A组数据与key运算得到的。
  5. A组前一组的密文是由A组的middle与明文得到的,middle是不会变的,明文是我们要加密的铭文组的最后一个组,这两个属性我们都知道,因此我们可以用hello\x03\x03\x03这个明文去跟middle去异或,得到A组上一组的密文,假设运算得到的密文全部为B。那么如果我们想将hello\x03\x03\x03加密传送给服务端,则需要传输的数据为\xBB\xBB\xBB\xBB\xBB\xBB\xBB\xBB\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA。
  6. 如果有多个分组,比如说在hello前面还有八个A字符,而我们已经得到这个明文字符串的结尾密文为\xBB\xBB\xBB\xBB\xBB\xBB\xBB\xBB\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA,此时需要给\xBB\xBB\xBB\xBB\xBB\xBB\xBB\xB前面再添加八位的全0字符,接着就从后往前依次修改全0组的数据,像第3、4步一样,得到全B组的middle,可以用这个middle加密倒数第二组的明文形成密文,以此类推可以得到所有明文的密文。

5. 防御方式

  1. 由于这种方法需要爆破得到key,因此可以对短时间内多次访问的ip进行禁止访问操作,达到防御目的。
  2. 升级至安全版本。
  3. 关闭rememberMe持久化登录功能。

6. 参考文章

Shiro RCE again(Padding Oracle Attack)
Padding Oracle Attack(填充提示攻击)详解及验证
Apache Shiro 反序列化漏洞(Shiro-721 CVE-2016-4437)
利用Oracle Padding加密任意数据
Apache Shiro反序列化漏洞-Shiro-550复现总结
Shiro-721 RCE Via Padding Oracle Attack

标签: 安全 web安全

本文转载自: https://blog.csdn.net/qq_41874930/article/details/121314926
版权归原作者 Shanfenglan7 所有, 如有侵权,请联系我们删除。

“shiro 721 反序列化漏洞复现与原理以及Padding Oracle Attack攻击加解密原理”的评论:

还没有评论