沉铝汤的破站

IS LIFE ALWAYS THIS HARD, OR IS IT JUST WHEN YOU'RE A KID

浅显易懂のPadding-Oracle-Attack讲解

0x00 前言


复现shiro漏洞的时候,遇到Padding-Oracle-Attack的知识,结果直接歇逼了,看别人的文章和代码看了一个星期才看懂,所以在明白之后(自以为的明白)就想写一篇文章来浅显易懂的讲解一下这种攻击方式,既是帮自己加深记忆也是帮助他人(如果有人看的话)

本篇文章包含以下元素:

  • 通过Padding-Oracle-Attack获取明文
  • 通过Padding-Oracle-Attack构造明文为任意内容的密文
  • CBC翻转(和构造明文为任意内容的密文原理一样,只不过适用于不存在Padding-Oracle的情况)

0x01 前置知识


AES-PKCS#5 Padding

AES作为一种分组加密,需要将数据按照每组16字节的方式进行分组。显然,并不是所有的数据分组后最后一组都会正好是16字节,这时候要怎么办呢?这时候就需要我们进行Padding,即填充。常见的有以下几种Padding方式:

  • PKCS#5 Padding
  • PKCS#7 Padding
  • ZeroPadding
  • NoPadding
  • ANSI X.923
  • ISO 10126

为了突出如题的“浅显易懂”,所以我们这里就只讲和主题有关的PKCS#5 Padding,其他Padding可自行了解。一个有趣的事情是,网上很多博客说PKCS#5 Padding指仅支持对8字节块的填充,但是在Shiro中却是使用的PKCS#5模式(PKCS5Padding类),而AES显然是需要块为16字节的,所以我更倾向于仅支持8字节为谬传或者是PKCS#5可被拓展为16字节。废话了一大堆,下面我们看看什么是PKCS#5 Padding吧,如下图:

image-20211022191712174

值得注意的是,上图是以8字节块(Block)为基础的制图,但是无论是8字节块还是16字节块,规则都是一样的,我们可以依据上图总结出来:

  • 当不满足块大小时,填充的值即为缺省数量,填充次数与值相同或者说直至填满,如8字节块中:chen,缺省4字节,则填充字符为0x04,填充多少次呢?4,即:chen 0x04 0x04 0x04 0x04。
  • 当正好满足块大小时,并不是想当然的不填充,而是直接再填充一个块的大小,如8字节块中:chentang, 正好是8字节,但是任然需要填充,即:chentang 0x08 0x08 0x08 0x08 0x08 0x08 0x08 0x08。

AES-CBC

当分组进行完Padding后,就可以进行加密了,CBC模式是分组加密中常用到的一种模式,其加密模式如下:

Cbc_encryption

分组明文P与前一组的密文C异或后得到中间值M,M再经过块加密后即可获得此分组的密文,为了保持一致性,第一个分组需要一个初始向量IV作为虚拟的前一组密文。最后的密文将各组密文进行拼接即可。

同样的,其解密模式图如下:Cbc_decryption

是加密的逆过程,各分组块解密后中间值M的与前一组的密文C异或即可得到该分组的明文P。同样的第一个分组需要一个初始向量IV。

将以上加密与解密过程直观的表示出来,如下:

  • 加密:(明文P ^ 前一组密文C0) -> 中间值M - - -(块加密)- - -> 本组密文C1
  • 解密: 本组密文C1 –(块解密)—> 中间值M - - -(M ^ 前一组密文C0)- - -> 本组明文P

0x02 Padding-Oracle-Attack


简介

什么是Padding-Oracle-Attack呢?Padding我们在前置知识中已经知道了,那什么是Oracle呢?显然这里并不是指“甲骨文公司”,而是其字面意思“神谕”,神谕,即神的旨意,神的指示,虚头巴脑的……其实就类似于SQL注入中的盲注。如果一个服务器采用了CBC模式的加密,PKCS#5的Padding,并且在我们Padding正确和Padding错误时具有不同的响应,那么我们就能够爆破出中间值M,并借此解出明文P或者构造解密后明文为任意内容的密文C。

Padding-Oracle 解密明文

由于懒和为了不让人纠结于8字节和16字节(主要是懒),我这里直接使用3字节块😋,并且之后也不会管异或的正确性,会随便写一个异或结果,且为了方便,缺省0x,即5表示0x05,特殊的,明文中字母若未带0x,默认表示字符。

假设现在我们有一串AES密文:A B C 1 2 3 (16进制),且服务器采用了AES的CBC模式,PKCS#5填充规则进行加解密。一般情况下,密文首块即为第一分组的初始向量IV,这里即为: ABC,那么我们有如下的解密过程:

image-20211022211557330

可以看到,符合Padding的规则,缺1填1,所以这时候服务器给我们返回200。但是我们知道,任何值与0x00异或后的结果都不改变,如果我们将ABC123改为0 0 0 1 2 3,传送给服务器,解密过程就如下:

这时候,服务器检查Padding的时候,会发现0x06显然是不符合规则的,因为我们这里只有三位,填充的值最大都仅为0x03,所以服务器给我们返回500。至此,我们便可以判断出:服务器Padding正确与否会有不同的响应,我们能够进行Padding-Oracle-Attack。那具体要怎么做呢?

试想一下,在不知道密文解密出的明文情况下,我们完全可以用0 0 ?来保证前两位一定有数据,这时候如果要符合Padding,显然只要保证解密后明文第三位为0x01,而从0x00-x0ff,总共只有256个数,且有且仅有一个数与中间值M中的0x06(这里记为M[3]) 异或后的结果为0x01(这个我也不知道怎么证明,你可以拿00 01 10 11这四个值互相异或着测试一下),那么我们完全可以爆破出那个值,这里记为Fake_IV[3],即Fake_IV[3]^M[3] = 0x01。 如果这时我们传入0 0 Fack_IV[3],则有如下的解密过程:

image-20211022221704724

这时候,显然是符合了Padding规则,服务器返回200,而当传入其他任何值时,如 0 0 2,显然是会返回500的,基于此,我们便可以爆破出Fack_IV[3],使得解密后明文第三位为0x01。这有什么用呢?根据CBC的规则,中间值M[3] ^ True_IV[3] = 明文P[3],而此处中间值M[3] ^ Fake_IV[3] = 0x01,变换为M[3]=0x01^Fake_IV[3], 由此可得明文P[3]=0x01^Fake_IV[3]^True_IV[3]。至此我们便仅由密文中的IV获得了中间值,进而解密出了明文,这便是一次Padding-Oracle-Attack。

如果我们要接着解密出剩下的明文呢?原理相同,我们可以使用0 ??来保证第一位一定有数据,要使解密后明文P符合Padding,我们只需要让后两位与M异或后为0x02 0x02(缺多少填多少,这是Padding规则)。但是我们这里并不需要爆破两位,因为在之前我们已经解出了第三位的M值,即M[3] = 0x01 ^ Fake_IV[3],这里我们假设得到的值为0x1c,则新的Fake_IV[3] = 0x02 ^ 0x1c = 0x1e,则我们需要传入的值为0 ? 1e 1 2 3,即任然只需要爆破所需位。

第一位的Fake_IV[1],也是如法炮制,那么我们很快就能够得到我们的完整Fake_IV,假设为2f 1e 1d。然后我们再与 0x03 0x03 0x03异或,即可得到整个中间值M。为什么是0x03呢?如果你搞懂了上面的爆破过程,应该很显然,爆破完所有位后,就应该是填充的最大值,即3字节块为0x03 * 3, 8字节块为0x08 * 8, 16字节快为0x10 * 16。接着我们使用M^True_IV,即可得到明文P,True_IV在此处为前3字节,即明文P=(2f 1e 1d) ^ (3 3 3) ^ (A B C)。

那如果不止一组呢?原理相同,但我们还是来体会一下吧。密文为A B C 1 2 3 E F D,从上面的过程,我们解出了第一组的明文,那第二组呢?显然也是只需要获得IV即可,而根据CBC的解密,下一组的IV便是上一组的密文,即True_IV = 1 2 3。接着我们传入 0 0 0 E F D(M值只与密文值有关,所以可以不需要之前的部分)来爆破出E F D的M值即可,有了True_IV和M,明文就得到了。

Padding-Oracle 构造密文

从上文中,我们可以知道,在能进行Padding-Oracle-Attack的情况下,我们可以爆破出任何一组的中间值M,并且通过M^Ture_IV即可得到明文P。那如果我们想要构造出解密后的明文为我们想要的Payload的密文要怎么做呢?显然,这里的已知条件变成了M(因为任何一组的M我们都可以爆破得到)与明文Payload,那么,只剩下未知的IV,如果我们能够构造出特殊的IV,即可让密文解密后为Payload,即:IV=Payload^M

具体要怎么做呢?假设我们现在发现某购物网站中用户的金币💴的数目是使用AES-CBC加密后存储在Cookie之中,并且能够进行Padding-Oracle-Attack,作为穷比黑客的你会有什么想法呢?没错,当然是想构造出一个密文,让他在服务端解密之后能够有超多钱,实现从0到1w😋。

1w太大,“先定一个小目标,赚他个100”。依然是拿3字节块为例子,我们先随便写一个密文,如:1c 2c 3c。根据上面的讲解:

  • 第一步依然是爆破出中间值M,所以我们给服务器传入0 0 0 1c 2c 3c等,直至爆破出M,这里假设最后爆破出的M值为:2b 2d 2c。
  • 第二步,很显然,便是根据公式IV=Payload^M得到IV,即: IV = 100(字符) ^ (2b 2d 2c),这里假设得到的是:3a 3b 3c。
  • 第三步,便是传入我们的精心构造的密文,即Cookie=3a 3b 3c 1c 2c 3c

恶意密文在服务器的解密过程如下:

image-20211023192752866

如此,我们便实现了从0到100的转变,那如果我们想要赚他个1w呢?显然,要实现1w,我们需要一组IV+两组密文。我们这里已经构造出了100,是不是只需要最后一组密文就可以了呢?答案是,对不起,不可以。因为从原理上看,是我们的IV改变了解密出的明文,而最后一组的IV是前一组的密文,我们要构造最后一组则必须修改前一组。由此,我们便可以得出,我们构造多组密文的时候一定要从后往前

还是一样的,我们先随便写一组密文:1c 2c 3c。根据上面的,我们得出中间值M为:2b 2d 2c,然后与明文异或即可得到前一组的密文,即00(字符) 0x01^(2b 2d 2c),这里假设为cf cd cc。此时如果传入cf cd cc 1c 2c 3c即可获得 00(字符)0x01,那么我们下一步就是照猫画虎的构造出100了。爆破出密文cf cd cc的中间值M,这里假设为df dc da,那么很显然,IV即为:(df dc da)^ 100(字符),这里我们假设为 66 23 33。至此,构造完毕,最后一个价值1w的Cookie即为:66 23 33 cf cd cc 1c 2c 3c。

CBC翻转

其实原理和构造密文一模一样,假设我们现在有密文: A B C 1 2 3,但是我们发现网站不存在Padding-Oracle-Attack,然而我们莫名奇妙(总之就是很神奇)的获得了明文为CY(字符) 0x01,并且只要明文为AY(字符) 0x01就能获得网站的管理员权限,作为带黑阔的你会怎么做捏?显然,我们根据现有条件可以的到: M[1] ^ True_IV[1] = C(字符),而我们要得到的明文Fake_P[1] = M[1] ^ Fake_IV[1] = A(字符),由此便可以得出Fake_IV[1] = True_IV[1] ^ C ^ A,假设得到结果为0x66。那么当我们传入0x66 B C 1 2 3时,就可以成功获得管理员权限。

可以看到,这里原理也是通过M^Fake_P来获得Fake_IV,只不过这里的M不是爆破得到,而是由True_IV^True_P得到。

0x03 参考


padding oracle和cbc翻转攻击 · sky’s blog (skysec.top)

Padding Oracle Attack 浅析 - lightless blog