Java反序列化漏洞——Shiro721

概述

在Shiro550漏洞中,Cookie所使用的AES加密密钥为硬编码,所以我们可以构造恶意序列化数据并使用固定的AES密钥进行正确加密恶意序列发送给服务端,进而达到攻击的目的。但在该漏洞公布后,Shiro官方修复了这一漏洞,将AES密钥修改成了动态生成。也就是说,对于每一个Cookie,都是使用不同的密钥进行加解密的。

Shiro反序列化漏洞——Shiro721(CVE-2019-12422)

漏洞原理

在Shiro721漏洞中,由于Apache Shiro cookie中通过 AES-128-CBC 模式加密的rememberMe字段存在问题,用户可通过Padding Oracle Attack来构造恶意的rememberMe字段,并重新请求网站,进行反序列化攻击,最终导致任意代码执行。

虽然使用Padding Oracle Attack可以绕过密钥直接构造攻击密文,但是在进行攻击之前我们需要获取一个合法用户的Cookie。

漏洞流程

  • 登录网站获取合法Cookie
  • 使用rememberMe字段进行Padding Oracle Attack,获取intermediary
  • 利用intermediary构造出恶意的反序列化密文作为Cookie
  • 使用新的Cookie请求网站执行攻击

影响版本

Apache Shiro <= 1.4.1

特征判断

响应包中包含字段remember=deleteMe字段

漏洞环境搭建

搭建步骤与Shiro550类似,可以参考Java反序列化漏洞——Shiro550

配置Maven的时候可以选择默认下载源码和文档,在Settings-Build Tools-Maven-Importing中

设置自动下载源码和文档

漏洞分析

密钥生成

在Shiro550中,密钥是硬编码,就像下面这样

public AbstractRememberMeManager() {
        this.serializer = new DefaultSerializer<PrincipalCollection>();
        this.cipherService = new AesCipherService();
        setCipherKey(DEFAULT_CIPHER_KEY_BYTES);
    }

而在Shiro721中,密钥的生成方式变为了动态生成

public AbstractRememberMeManager() {
        this.serializer = new DefaultSerializer<PrincipalCollection>();
        AesCipherService cipherService = new AesCipherService();
        this.cipherService = cipherService;
        setCipherKey(cipherService.generateNewKey().getEncoded());
    }

我们可以跟进调试一下,在 AbstractRememberMeManager()方法中,通过generateNewKey()获取密钥,跟进

generateKey()

初始化了一个KeyGenerator对象并调用init()方法初始化其参数,跟进看看参数是怎么被赋值的

init()

这里调用了双参数init(),并且获取了一个随机数发生器SecureRandom

下一步调用了kg.generateKey(),跟到engineGenerateKey()

engineGenerateKey()

可见这里已经生成了一串16字节的随机序列,然后返回一个SecretKeySpec对象,再使用getEncoded()方法获取key密钥序列。

返回密钥序列

至此就是Shiro721完整的密钥生成过程。

布尔条件

我们知道,Padding Oracle Attack攻击是一种类似于sql盲注的攻击,这就要求服务器端有能够被我们利用的布尔条件。在CBC字节翻转攻击&Padding Oracle Attack原理解析这篇文章中,我们模拟的服务器环境如下

  • 当收到一个有效的密文(一个被正确填充并包含有效数据的密文)时,应用程序正常响应(200 OK)
  • 当收到无效的密文时(解密时填充错误的密文),应用程序会抛出加密异常(500 内部服务器错误)
  • 当收到一个有效密文(解密时正确填充的密文)但解密为无效值时,应用程序会显示自定义错误消息 (200 OK)

我们可以通过响应头来判断明文填充是否正确,进而爆破出中间值。那么对于解密不正确的Cookie,Shiro是怎么处理的呢?

Padding错误处理

解密函数在AbstractRememberMeManager.decrypt()

decrypt()

跟进cipherService.decrypt(),最后到crypt()中调用doFinal()方法

crypt()

这里的doFinal()方法对密文进行异常处理

doFinal()

doFinal()方法有IllegalBlockSizeExceptionBadPaddingException这两个异常,分别用于捕获块大小异常和填充错误异常。异常会被抛出到crypt()方法中,最终被getRememberedPrincipals()方法捕获,并执行onRememberedPrincipalFailure()方法。

onRememberedPrincipalFailure()方法调用了forgetIdentity()。在Shiro550中我们分析过,该方法会调用removeFrom(),在response头部添加字段Set-Cookie: rememberMe=deleteMe

onRememberedPrincipalFailure()

倘若Padding结果不正确的话,响应包就会返回 Set-Cookie: rememberMe=deleteMe

Padding正确,反序列化错误处理

CBC模式下的分组密码,如果某一组的密文被破坏,那么在其之后的分组都会受到影响。这时候我们的密文就无法正确的被反序列化了。

Shiro中关于反序列化的处理在DefaultSerializer类中

deserialize()

如果反序列化的结果错误,则会抛出异常。最后异常仍会被getRememberedPrincipals()方法处理。

但是对于Java来说,反序列化是以Stream的方式按顺序进行的,向其后添加或更改一些字符串并不会影响正常反序列化。我们可以来测试一下。

我们获取正常用户的Cookie并使用密钥解密,可以看到最后填充的数据为0x0B

Padding数据

下面我们将其更改为其他合法填充方式,然后加密发送出去

更改Padding方式
发送解密加密数据

服务器端正常响应,于是这里就构造出了布尔条件

  • Padding正确,服务器正常响应
  • Padding错误,服务器返回Set-Cookie: rememberMe=deleteMe

漏洞利用

在Shiro550中,我们可以直接通过硬编码密钥直接生成攻击密文。但是Shiro721使用了动态密钥,无法直接获取密钥。但是仍然可以通过Padding Oracle Attack绕过密钥,直接生成攻击密文。

利用链和Shiro550类似,这里我们使用ShiroExploit.V2.51工具进行攻击测试。输入测试网址以及登录用户的Cookie

测试攻击

选择dnslog进行漏洞检测,此时已经开始爆破每一分组的intermediary

Padding Oracle Attack

测试命令执行

calc

使用生成的Cookie成功执行。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇