CVE-2019-12422漏洞分析

admin 2022年9月21日11:32:42安全文章评论0 views5675字阅读18分55秒阅读模式
出品|先知社区(ID:alter99)

声明

以下内容,来自先知社区的alter99作者原创,由于传播,利用此文所提供的信息而造成的任何直接或间接的后果和损失,均由使用者本人负责,长白山攻防实验室以及文章作者不承担任何责任。


CVE-2019-12422

漏洞信息

漏洞编号:

CVE-2019-12422/CNVD-2016-07814/SHIRO-721
影响版本:shiro < 1.4.2
漏洞描述:RememberMe默认通过 AES-128-CBC 模式加密,易受Padding Oracle Attack攻击
漏洞补丁:Commit
参考:padding oracles Padding oracle attack Pad-dingOracleAttackShiro-721代码分析


漏洞分析

本次漏洞实际并不是针对shiro代码逻辑的漏洞,而是针对shiro使用的AES-128-CBC加密模式的攻击,首先了解一下这种加密方式。


AES-128-CBC

AES-128-CBC模式就代表使用AES密钥长度为128 bit,使用CBC分组算法的加密模式。


  • AES是对称、分组加密算法,分组长度固定为128bit,密钥key的长度可以为128bit(16字节)、192bit(24字节)、256bit(32字节),如果数据块及密钥长度不足时,会补齐。


  • CBC,全称 Cipher Block Chaining (密文分组链接模式),简单来说,是一种使用前一个密文组与当前明文组 XOR 后再进行加密的模式。CBC主要是引入一个初始化向量(Initialization Vector,IV)来加强密文的随机性,保证相同明文通过相同的密钥加密的结果不一样。


CBC 模式下,存在以下填充方式,用于在分组数据不足时,在结尾进行填充,用于补齐:

  • NoPadding:不填充,明文长度必须是16Bytes 的倍数。


  • PKCS5Padding:PKCS7Padding跟PKCS5Padd-ing的区别就在于数据填充方式,PKCS7Padding是缺几个字节就补几个字节的0,而PKCS5Padding是缺几个字节就补充几个字节的几,比如缺6个字节,就补充6个字节的6,如果不缺字节,就需要再加一个字节块。


  • ISO10126Padding:以随机字节填充 , 最后一个字节为填充字节的个数。


Shiro 中使用的是 PKCS5Padding,也就是说,可能出现的 padding byte 值只可能为:

1 个字节的 padding 为 0x012 个字节的 padding 为 0x02,0x023 个字节的 padding 为 0x03,0x03,0x034 个字节的 padding 为 0x04,0x04,0x04,0x04...

当待加密的数据长度刚好满足分组长度的倍数时,仍然需要填充一个分组长度,也就是说,明文长度如果是 16n,加密后的数据长度为 16(n+1) 。

CVE-2019-12422漏洞分析



加密过程

  • 明文经过填充后,分为不同的组block,以组的方式对数据进行处理

  • 初始化向量(IV)首先和第一组明文进行XOR(异或)操作,得到”中间值“

  • 采用密钥对中间值进行块加密,删除第一组加密的密文 (加密过程涉及复杂的变换、移位等)

  • 第一组加密的密文作为第二组的初始向量(IV),参与第二组明文的异或操作

  • 依次执行块加密,最后将每一块的密文拼接成密文

  • IV经常会被放在密文的前面,解密时先获取前面的IV,再对后面的密文进行解密

CVE-2019-12422漏洞分析


解密过程

  • 会将密文进行分组(按照加密采用的分组大小),前面的第一组是初始化向量,从第二组开始才是真正的密文

  • 使用加密密钥对密文的第一组进行解密,得到中间值

  • 将中间值和初始化向量进行异或,得到该组的明文

  • 前一块密文是后一块密文的IV,通过异或中间值,得到明文

  • 块全部解密完成后,拼接得到明文,密码算法校验明文的格式(填充格式是否正确)

  • 校验通过得到明文,校验失败得到密文

CVE-2019-12422漏洞分析


Padding Oracle Attack 原理

这个攻击的根源是明文分组和填充,同时应用程序对于填充异常的响应可以作为反馈。首先明确以下两点


  1. 解密之后的最后一个数据块,其结尾应该包含正确的填充序列。如果这点没有满足,那么加/解密程序就会抛出一个填充异常。Padding Oracle Attack的关键就是利用程序是否抛出异常来判断padding是否正确。


  2. 解密时将密文分组,第一组是初始化向量,后面才是真正的密文。密文传过去后先解密得到中间值,中间值与初始向量异或得到明文片段。


比如我们的明文为admin,则需要被填充为 adminx0bx0bx0bx0bx0bx0bx0bx0bx0bx0bx0b,一共11个x0b


如果我们输入一个错误的IV,依旧是可以解密的,但是中间值middle和我们输入的IV经过异或后得到的填充值可能出现错误这样就出现验证错误的情况。


比如本来应该是

adminx0bx0bx0bx0bx0bx0bx0bx0bx0bx0bx0b

而我们错误的得到

adminx0bx0bx0bx0bx0bx0bx0bx0bx0bx0bx2b

这样解密程序往往会抛出异常(Padding Error),应用在web里的时候,往往是302或是500报错,而正常解密的时候是200

所以这时,我们可以根据服务器的反应来判断我们输入的IV是否正确


举例解释

这里使用参考链接中的数据进行举例说明

我们假设正确的IV为

0x6d 0x36 0x70 0x76 0x03 0x6e 0x22 0x39

middle中间值为(为了方便,这里按8位分组来阐述)

0x39 0x73 0x23 0x22 0x07 0x6a 0x26 0x3d

解密后正确的明文为:

TEST0x040x040x040x04

以攻击者的角度来看,我们可以知道IV的值和服务器的状态,不知道中间值和解密后明文的值,所以我们可以根据输入的IV值和服务器的状态去判断出解密后明文的值,这里的攻击即叫做Padding Oracle Attack攻击,首先输入IV

0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

一起传到服务器后,服务器对IV后面的加密数据进行解密,得到中间值,然后IV与中间值进行异或,得到明文:

0x39 0x73 0x23 0x22 0x07 0x6a 0x26 0x3d

此时程序会校验最后一位padding字节是否正确。由于是按8位进行分组,所以正确的padding的值应该只有0x01~0x08,这里是0x3d,显然是错误的,所以程序会抛出500。


知道这一点后,我们可以通过遍历最后一位IV,从而使这个IV和middle值异或后的最后一位是我们需要0x01,这时候有256种可能。


这时问题来了,我们为什么要使最后一位是0x01呢?因为此时我们想知道plain[8]的值,只计算最后一位就可以了,只计算最后一位的话只有0x01时服务器才会通过验证,我们才能计算下面的公式。


此时IV的值为:

0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x3c

IV和Middle异或后得到的是:

0x39 0x73 0x23 0x22 0x07 0x6a 0x26 0x01

这时候程序校验最后一位,发现是0x01,即可通过校验,服务器返回200,然后我们有公式:

Middle[8]^原IV[8] = plain[8]Middle[8]^现IV[8] = 0x01

所以,我们可以算出

middle[8] = 0x01^现IV[8]

然后可以计算得到:

plain[8] = 0x01^现IV[8]^原IV[8] = 0x01^0x3c^0x39=0x04

和我们之前解密成功的明文一致(最后4位为填充),下面我们需要获取plain[7]。
因为这次我们需要的明文是2个0x02,而非之前的一个0x01,所以需要将IV更新

现IV[8] = middle[8]^0x02

为什么是现在的IV[8] = middle[8]^0x02?
因为现在的IV[8]^middle[8]=服务器校验的值,而我们遍历倒数第二位,应该是2个0x02,所以服务器希望得到的是0x02,所以

现IV[8]^middle[8]=0x02

然后再继续遍历现在的IV[7]
方法还是和上面一样,遍历后可以得到IV:

0x00 0x00 0x00 0x00 0x00 0x00 0x24 0x3f

IV和middle异或得到的是

0x39 0x73 0x23 0x22 0x07 0x6a 0x02 0x02

此时真正的明文值:

plain[7]=现IV[7]^原IV[7]^0x02

所以plain[7] = 0x02^0x24^0x22=0x04
和我们之前解密成功的明文一致(最后4位为填充)
最后遍历循环,即可得到完整的plain


CBC翻转攻击过程

这个实际上和padding oracle攻击差不多,还是关注这个解密过程。但这时,我们是已知明文,想利用IV去改变解密后的明文


比如我们知道明文解密后是1dmin,我们想构造一个IV,让他解密后变成admin。


还是原来的思路

原IV[1]^middle[1]=plain[1]

而此时,我们想要有如下等式

构造的IV[1]^mddle[1]=’a’

所以我们可以得到

middle[1]=原IV[1]^plain[1]构造的IV[1] = middle[1]^’a’构造的IV[1]= 原IV[1]^plain[1]^’a’

我们可以用这个式子,遍历明文,构造出IV,让程序解密出我们想要的明文


Shiro中的攻击

在了解上面的基础知识后,就很好理解后面的攻击流程了,攻击者通过已知 RememberMe 密文使用 Padding Oracle Attack 爆破和篡改密文,构造可解密的恶意的反序列化数据,触发反序列化漏洞。


之前提到过 Padding Oracle Attack 是利用类似于盲注的思想来判断是否爆破成功的,在验证 Padding 失败时的返回信息应该不同,那我们看一下在Shiro中,验证Padding失败时的返回值?


关注点依旧

AbstractRememberMeManager#getRememberedPrincipals中开始

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);    }    return principals;}

负责解密的 convertBytesToPrincipals 方法会调用 CipherService 的 decrypt 方法,调用栈如下所示如下:

CVE-2019-12422漏洞分析

其中 PKCS5Padding#unpad 方法对数据的填充格式进行判断,有问题会返回 -1;当返回值小于0时,CipherCore#doFinal 方法会抛出 BadPadding-Exception 异常;


接着 JcaCipherService#crypt 方法、 AbstractRe-memberMeManager#getRememberedPrincipals 方法均返回异常,而且AbstractRemember-MeManager#getRememberedPrincipals方法还好调用onRememberedPrincipalFailure移除rem-emberMe cookie并添加 deleteMe。

CVE-2019-12422漏洞分析

由此可见,只要 padding 错误,服务端就会返回一个 cookie: rememberMe=deleteMe;,攻击者可以借由此特征进行 Padding Oracle Attack。


漏洞复现

直接使用 longofo 师傅的项目。
首先获取一个有效的 rememberMe 值,其次生成一个反序列化利用的 payload,然后使用如下参数执行攻击。

java -jar PaddingOracleAttack-1.0-SNAPSHOT.jar http://localhost:8080/samples_web_war/ "P5MwbBios...sdSdf" 16 cb.ser

经过一段时间后,生成payload,替换rememberMe的值,发送到服务器

CVE-2019-12422漏洞分析

这个洞需要大量的请求,在实际中应该不太可能攻击成功。


问题:
由于系统初始化后,只要不重启服务器,密钥就固定了,那应该就可以攻击成功一次之后,后面继续攻击应该就不需要大量请求了,可以直接生成payload,但是目前不知道需要保存哪些值才能实现这种需求。


不完全解答:
改了一下代码,目前只实现攻击一次后,ser不变的情况下,可以快速生成,但是ser改变,就需要重新生成。


原因在于原代码是基于nextCipherTextBlock也就是nextBLock计算的tmpIV,继而计算的nextBLock。所以无法通过保存nextBLock或tmpIV达到通用的目标。但我认为从攻击算法的角度来看,还是有办法实现的。

CVE-2019-12422漏洞分析

也有师傅对利用代码进行分析后,实现了payload瘦身的功能


漏洞修复

在 1.4.2 版本的更新 Commit 中对此漏洞进行了修复 ,在父类 JcaCipherService 中写了一个抽象方法 createParameterSpec() ,该方法返回加密算法对应的类,并在 AesCipherService 中重写了这个方法,默认使用 GCM 加密模式,避免此类攻击。

CVE-2019-12422漏洞分析



CVE-2019-12422漏洞分析
CVE-2019-12422漏洞分析
CVE-2019-12422漏洞分析

▇ 扫码关注我们 ▇

长白山攻防实验室

学习最新技术知识


原文始发于微信公众号(长白山攻防实验室):CVE-2019-12422漏洞分析

特别标注: 本站(CN-SEC.COM)所有文章仅供技术研究,若将其信息做其他用途,由用户承担全部法律及连带责任,本站不承担任何法律及连带责任,请遵守中华人民共和国安全法.
  • 我的微信
  • 微信扫一扫
  • weinxin
  • 我的微信公众号
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年9月21日11:32:42
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                  CVE-2019-12422漏洞分析 http://cn-sec.com/archives/1309333.html

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: