首页 → 怡安的网络实验室 → 利用PadBuster自动填充Oracle的攻击
2010年9月14日,星期二,上午10:20
最近有很多关于填充甲骨文攻击的讨论,这是Juliano Rizzo和Thai Duong在今年夏天早些时候在BlackHat Europe的演讲中展示的一种攻击载体。虽然填充神谕是比较容易利用的,但如果你没有一个好的方法来自动攻击,利用它们的行为可能会很耗时。由于缺乏识别和利用填充神迹的好工具,我们开发了自己的内部填充神迹利用脚本,PadBuster,我们决定与社区分享。该工具可以在这里下载,但我将花一点时间讨论该工具如何工作以及它支持的各种使用情况。
9/28/10更新:PadBuster v0.2已经发布。
一些背景
在我们讨论使用PadBuster之前,让我们简单地讨论一下经典的padding oracle攻击的基本原理。正如该术语所暗示的,填充神谕攻击背后的一个关键概念是加密填充的概念。明文信息有不同的长度;然而,区块密码要求所有的信息都是精确的区块数。为了满足这一要求,填充被用来确保一个给定的明文信息总是可以被分成精确数量的块。
虽然存在几种填充方案,但最常见的填充方案之一是在PKCS#5标准中描述的。通过PCKS#5填充,最后的明文块被填充了N个字节(取决于最后一个明文块的长度),值为N。这最好通过下面的例子来说明,这些例子显示了不同长度的单词(FIG, BANANA, AVOCADO, PLANTAIN, PASSIONFRUIT)以及它们如何使用PKCS#5填充(下面的例子使用8字节块)。
请注意,至少有一个填充字节总是被附加到一个字符串的末尾,所以一个7字节的值(如AVOCADO)将被填充0x01以填补该块,而一个8字节的值(如PLANTAIN)将被添加一整块填充。填充字节的值也表明了字节数,所以从逻辑上讲,最后一个密码文本块末尾的最终值必须是:
- 一个0x01字节(0x01)
- 两个0x02字节 (0x02, 0x02)
- 三个0x03字节(0x03, 0x03, 0x03)
- 四个0x04字节(0x04, 0x04, 0x04, 0x04)
- ...以此类推
如果最后的解密块不是以这些有效的字节序列结束,大多数密码提供者会抛出一个无效填充的异常。这个异常被抛出的事实对攻击者(我们)来说至关重要,因为它是填充神谕攻击的基础。
一个基本的 "填充神谕 "攻击场景
为了提供一个具体的例子,考虑以下情况:
一个应用程序使用一个查询字符串参数来传递用户的加密的用户名、公司ID和角色ID。该参数使用CBC模式进行加密,每个值都使用一个独特的初始化向量(IV),该向量被预先添加到密码文本中。
当应用程序被传递一个加密的值时,它以三种方式之一进行响应:
- 当收到一个有效的密码文本(一个被正确填充并包含有效数据的密码文本),应用程序正常响应(200 OK)
- 当收到一个无效的密码文本(当解密时,没有以有效的填充结束),应用程序抛出一个加密异常(500内部服务器错误)。
- 当收到一个有效的密码文本(一个被正确填充的密码文本),但解密为一个无效的值,应用程序会显示一个自定义的错误信息(200 OK)。
上面描述的场景是一个典型的填充神谕,因为我们可以使用应用程序的行为来轻松确定提供的加密值是否正确填充。术语oracle指的是一种机制,可以用来确定一个测试是否通过或失败。
现在场景已经布置好了,让我们看一下应用程序使用的一个加密参数。这个参数被应用程序用来存储一系列分号分隔的值,在我们的例子中,包括用户的名字(BRIAN),公司ID(12),以及角色ID(2)。该值在明文中可以表示为BRIAN;12;2;。下面是一个加密的查询字符串参数的例子,供参考。请注意,加密的UID参数值是用ASCII十六进制表示法编码的。
http://sampleapp/home.jsp?UID=7B216A634951170FF851D6CC68FC9537858795A28ED4AAC6在这一点上,攻击者不会知道底层的明文是什么,但为了这个例子,我们已经知道了明文值、填充的明文值,以及加密的值(见下图)。正如我们之前提到的,初始化向量被预先添加到密码文本中,所以第一个8字节的块就是这样。
至于区块大小,攻击者会看到,加密的密码文本的长度是24字节。由于这个数字是被8平均分割的,而不是被16分割的,因此可以得出结论,你所处理的是一个8字节的块大小。现在,作为参考,看看这个值被加密和解密时内部发生了什么。我们在图中看到了逐个字节的过程,因为这个信息在后面讨论这个漏洞时将会很有帮助。还要注意的是,圈起来的加号代表XOR函数。
加密图:
解密图:
同样值得指出的是,最后一个区块(2号区块),在解密时,如预期的那样以适当的填充序列结束。如果不是这样,密码学提供者会抛出一个无效填充的异常。
填充式Oracle解密漏洞
现在让我们来看看我们如何通过使用填充神谕攻击来解密该值。我们一次只对一个加密块进行操作,所以我们可以从隔离第一块密码文本(IV后面的那块)开始,并将其发送至应用程序,预先添加了一个所有NULL值的IV。URL和相关的响应如下所示:
Request: http://sampleapp/home.jsp?UID=0000000000000000F851D6CC68FC9537 响应: 500 - 内部服务器错误鉴于这个值在解密时不可能导致任何有效的结果,500错误响应是有意义的。下图显示了当应用程序试图解密这个值时,仔细看了一下下面发生的情况。你应该注意到,由于我们只处理一个单一的密码文本块(1号块),该块必须以有效的填充结束,以避免无效填充的异常。
如上所示,该异常被抛出,因为该块在解密后没有以有效的填充序列结束。现在,让我们来看看当我们发送完全相同的值,但初始化向量的最后一个字节增加了1,会发生什么。
Request: http://sampleapp/home.jsp?UID=0000000000000001F851D6CC68FC9537 响应: 500 - 内部服务器错误像以前一样,我们也得到一个500的异常。这是因为,和之前一样,解密后的值没有以有效的填充序列结束。然而,不同的是,如果我们仔细看一下封面下发生的事情,就会发现最后的字节值与之前不同(0x3C而不是0x3D)。
如果我们重复发出相同的请求,并且每次只增加IV中的最后一个字节(直到FF),我们将不可避免地遇到一个产生有效填充序列的单字节填充值(0x01)。只有一个值(在可能的256个不同字节中)会产生正确的填充字节0x01。当你碰到这个值时,你最终应该得到一个与其他255个请求不同的响应。
Request: http://sampleapp/home.jsp?UID=000000000000003CF851D6CC68FC9537 响应: 200 OK让我们看一下同一张图,看看这次会发生什么。
鉴于这种情况,我们现在可以推断出这个位置的中间值字节,因为我们知道,当与0x3C进行XOR时,它产生0x01。
如果[中介字节] ^ 0x3C == 0x01、那么[中间字节] == 0x3C ^ 0x01、
所以[中间字节]==0x3D
再进一步,现在我们知道了中间字节的值,我们可以推断出实际的解密值是什么。你会记得,在解密过程中,每个中间值字节都会与前一个区块的密码文本(或第一个区块的IV)的相应字节进行XOR。由于上面的例子使用的是原始样本的第一个块,那么我们需要将这个值与原始IV的相应字节(0x0F)进行XOR,以恢复实际的明文值。正如预期的那样,这给了我们0x32,也就是数字 "2"(第一个块中的最后一个明文字节)。
现在我们已经解密了样本块的第8个字节,是时候进入第7个字节了。为了破解样本的第8个字节,我们用蛮力逼出了一个IV字节,这个IV字节将产生一个0x01的最后解密字节值(有效填充)。为了破解第7个字节,我们需要做同样的事情,但这次第7和第8个字节都必须等于0x02(同样,有效的填充)。由于我们已经知道最后一个中间值字节是0x3D,我们可以将第8个IV字节更新为0x3F(这将产生0x02),然后专注于对第7个字节进行暴力破解(从0x00开始,一直到0xFF)。
再一次,我们继续产生填充异常,直到我们遇到唯一能产生0x02(有效填充)的第7个解密字节值的值,在这种情况下,它是0x24。
使用这种技术,我们可以通过整个区块向后工作,直到中间值的每一个字节都被破解,基本上让我们获得了解密值(尽管是一个字节一个字节的)。最后一个字节是用一个IV破解的,它产生了整个区块的填充(0x08),如下图所示。
使用PadBuster的解密漏洞
现在让我们讨论一下如何使用PadBuster来执行这个漏洞,这是相当简单的。PadBuster需要三个强制性参数:
- URL - 这是你要利用的URL,包括查询字符串(如果存在)。如果需要的话,还有可选的开关来提供POST数据(-post)和Cookies(-cookies)。
- Encrypted Sample - 这是请求中包含的加密样本。这个值也必须存在于URL、post或cookie值中,并将在每个测试请求中被自动替换。
- 区块大小 - 密码正在使用的区块大小。这通常是8或16,所以如果你不确定,你可以试试这两种。
在这个例子中,我们还将使用命令开关来指定加密后的样本的编码方式。默认情况下,PadBuster假定样本是Base64编码的,然而在这个例子中,加密的文本被编码为大写的ASCII HEX字符串。指定编码的选项(-encoding)采取以下三种可能的值之一:
- 0: Base64 (默认)
- 1: 小写字母HEX ASCII
- 2: 大写的HEX ASCII码
我们实际运行的命令将看起来像下面这样:
padBuster.pl http://sampleapp/home.jsp?UID=7B216A634951170FF851D6CC68FC9537858795A28ED4AAC6 7B216A634951170FF851D6CC68FC9537858795A28ED4AAC6 8 -encoding 2注意,我们从未告诉PadBuster如何识别填充错误。虽然有一个命令行选项(-error)来指定填充错误信息,但默认情况下,PadBuster会分析整个第一周期(0-256)的测试响应,并提示用户选择哪种响应模式与填充异常相匹配。通常情况下,除了一个初始测试请求外,填充异常都会发生,所以在大多数情况下,只有两种响应模式可供选择,如下图所示(而且PadBuster会建议使用哪一种)。
来自PadBuster的初始输出显示在上面的屏幕截图中。你会看到,在开始测试之前,PadBuster重新发出原始请求并显示生成的响应信息。这对经过认证的应用程序很有用,可以确保提供给PadBuster的认证cookies工作正常。
一旦选择了一个响应模式,PadBuster将自动循环通过每个块,并强制执行每个相应的明文字节,这最多需要每个字节256个请求。在每个区块之后,PadBuster还将显示获得的中间字节值和计算的明文。正如我们在下一节中所展示的,中间值在执行任意加密漏洞时是有用的。
加密任意值
我们刚刚看到一个填充神谕和PadBuster是如何被用来逐个解密任何加密样本的内容的。现在,让我们来看看如何利用同样的漏洞来加密任意的有效载荷。
你可能已经注意到,一旦我们能够推断出一个给定的密码文本块的中间值,我们就可以操纵IV值,以便完全控制密码文本被解密的值。因此,在前面的例子中,如果你想让第一个密码文本块解密为 "TEST "值,你可以通过将所需的明文与中间值进行XOR来计算产生这个值所需的IV。因此,字符串 "TEST"(当然是用四个0x04字节填充)将与中间值进行XOR,以产生所需的IV:0x6D,0x36,0x70,0x76,0x03,0x6E,0x22,0x39。
这对单个区块来说效果很好,但如果我们想生成一个超过一个区块长度的任意值呢?让我们看一个真实的例子,以使它更容易理解。这一次,我们将生成加密字符串 "ENCRYPT TEST",而不仅仅是 "TEST"。第一步是将样本分解成块,并添加必要的填充,如下图所示:
当构建一个以上的区块时,我们实际上是从最后一个区块开始,向后移动以生成有效的密码文本。在这个例子中,最后一个区块与之前的相同,所以我们已经知道,下面的IV和密码文本将产生字符串 "TEST"。
Request: http://sampleapp/home.jsp?UID=6D367076036E2239F851D6CC68FC9537接下来,我们需要弄清楚,如果把6D367076036E2239作为密码文本而不是仅仅作为IV传递,它将会解密成什么中间值。我们可以通过使用在解密漏洞中使用的相同技术来做到这一点,将其作为密码文本块发送,并以所有空值开始我们的蛮力攻击。
Request: http://sampleapp/home.jsp?UID=00000000000000006D367076036E2239一旦我们用蛮力攻击了中间值,IV就可以被操纵以产生我们想要的任何文本。然后,新的IV可以预先添加到之前的样本中,从而产生我们选择的有效的两块密码文本。这个过程可以重复无限次,以加密任何长度的数据。
用PadBuster加密任意值
除了解密之外,PadBuster还可以选择将创建任意加密值的过程自动化。有三个与创建密码文本有关的命令行开关:
- -plaintext [String]: 要加密的明文这是你要加密的明文。当使用PadBuster加密数据时,这个选项是强制性的,它的存在是告诉PadBuster执行加密而不是解密。
- -ciphertext [Bytes]: 中间字节的CipherText (Hex-Encoded)可选 - 可以用来提供用于加密的起始密码文本块。这个选项必须与-intermediary选项一起使用(见下文)
- -intermediary [Bytes]: CipherText的中间字节(Hex-Encoded)可选 - 可以用来为-ciphertext选项指定的密码文本提供相应的中间字节值。
密码文本和中介选项的目的是加快利用过程,因为它减少了生成伪造的密码文本样本时必须破解的密码文本块的数量。如果不提供这些选项,PadBuster将从头开始,用蛮力破解所有必要的加密块。让我们看一下用PadBuster加密数据的实际命令语法:
padBuster.pl http://sampleapp/home.jsp?UID=7B216A634951170FF851D6CC68FC9537858795A28ED4AAC6 7B216A634951170FF851D6CC68FC9537858795A28ED4AAC6 8 -encoding 2 -plainPadBuster的输出显示在下面的屏幕截图中:
你会注意到,就像以前一样,PadBuster会首先要求你选择正确的padding错误响应签名。如果你使用-error选项手动指定填充错误信息,这个步骤可以跳过。还注意到,PadBuster对两个完整的密码文本块进行了蛮力操作,因为我们没有指定要使用的起始密码文本块和相关的中间值。如果我们向PadBuster提供了这些值,第一个块就会被立即计算出来,只有第二个块需要进行暴力破解,如下图所示。
padBuster.pl http://sampleapp/home.jsp?UID=7B216A634951170FF851D6CC68FC9537858795A28ED4AAC6 7B216A634951170FF851D6CC68FC9537858795A28ED4AAC6 8 -encoding 2 -plaintext "ENCRYPT TEST" -密码文本 F851D6CC68FC9537 -中介 39732322076A263D正如你所看到的,第一个区块(区块2)是在提示响应签名之前计算出来的,因为这个区块是只用-ciphertext和-intermediary选项提供的数据来计算的。如果你想知道这两个值是从哪里来的,请记住,PadBuster在每一轮块处理结束时都会把这两个值打印到屏幕上。
因此,总结一下,我们发布PadBuster是因为我们认为其他笔试者可以从一个灵活的利用脚本中受益,以检测和利用填充神谕。我们很想知道是否有其他功能可以使这个工具从测试者的角度更有用或更实用,所以如果你有任何反馈,请告诉我们。
作者: 布莱恩-霍利菲尔德
©Aon plc 2023