Hello World
Spiga

Padding Oracle Attack实例分析

2010-10-09 00:20 by 老赵, 11785 visits

在之前的《浅谈》一文中,我提到《Automated Padding Oracle Attacks with PadBuster》一文对理解Padding Oracle Attack非常有帮助,并打算将其翻译出来。现在我便来实现承诺了。《Automated》一文其实是在介绍PadBuster这个自动攻击工具,不过其中也通过实例加配图详细介绍了Padding Oracle Attack的原理——这也是我会翻译的部分。这篇文章写的非常通俗易懂,您只需要了解一点点关于加密的基础概念即可,不需要对加密算法或其证明有任何了解。我想只要配合些许Wikipedia上的定义,大部分朋友应该都能顺利地理解这篇文章。

以下为翻译内容。

最近出现了许多有关Padding Oracle Attack的声音,在今年夏天早些时候的BlakHat Europe会议上,Juliano Rizzo和Thai Duong在他们的演讲中演示了这种攻击方式。虽然Padding Oracle是种相对容易的攻击方式,但如果您还没有对它的自动攻击原理有一定了解,那么利用它进行攻击还是需要不少时间的。由于缺少好用的工具以识别及利用Padding Oracles,我们开发了一个基于Padding Oracle的内部脚本,PadBuster,现在我们打算将它与社区分享。您可以在这里下载工具,现在我们也会花些时间来讨论这个工具的工作方式,以及它所支持的几种场景。

一些背景知识

在讨论PadBuster之前,我们先来简单讨论一下典型的Padding Oracle Attack基础。故名思义,Padding Oracle Attack背后的关键性概念便是加/解密时的填充(Padding)。明文信息可以是任意长度,但是块状加密算法需要所有的信息都由一定数量的数据块组成。为了满足这样的需求,便需要对明文进行填充,这样便可以将它分割为完整的数据块。

加密时可以使用多种填充规则,但最常见的填充方式之一是在PKCS#5标准中定义的规则。PCKS#5的填充方式为:明文的最后一个数据块包含N个字节的填充数据(N取决于明文最后一块的数据长度)。下图是一些示例,展示了不同长度的单词(FIG、BANANA、AVOCADO、PLANTAIN、PASSIONFRUIT)以及它们使用PKCS#5填充后的结果(每个数据块为8字节长)。

请注意,每个字符串都至少有1个字节的填充数据,因此7字节的值(如AVOCADO)则使用0x01进行填充,而8字节的值(如PLANTAIN)则会填充一个额外的数据块。填充字节的值也说明了填充的字节数,因此待加密数据的最后几个字节必须是以下几种情况之一:

  • 一个0x01(0x01)
  • 两个0x02(0x02,0x02)
  • 三个0x03(0x03,0x03,0x03)
  • 四个0x04(0x04,0x04,0x04,0x04)
  • ……

如果解密后的最后一个数据块末尾并非这些合法的字节序列,大部分加/解密程序都会抛出一个填充异常。这个异常对于攻击者尤为关键,它是Padding Oracle Attack的基础。

一个基本的Padding Oracle Attack场景

作为一个具体例子,请考虑以下场景:

某个应用程序使用Query String参数来传递一个用户加密后的用户名,公司ID及角色ID。参数使用CBC模式加密,每次都使用不同的初始化向量(IV,Initialization Vector)并添加在密文前段。

当应用程序接受到加密后的值以后,它将返回三种情况:

  • 接受到正确的密文之后(填充正确且包含合法的值),应用程序正常返回(200 - OK)。
  • 接受到非法的密文之后(解密后发现填充不正确),应用程序抛出一个解密异常(500 - Internal Server Error)。
  • 接受到合法的密文(填充正确)但解密后得到一个非法的值,应用程序显示自定义错误消息(200 - OK)。

上述的场景体现了一个典型的Padding Oracle(填充提示),我们可以利用应用程序的行为轻易了解某个加密的值是否填充正确。这里的单词Oracle代表了一种机制,用于了解某个测试是否通过。

既然已经给出了场景,那么我们便来查看应用程序所使用的一个加密后的参数。这个参数保存了使用分号隔离的一系列值,在我们的示例中,则是用户名(BRIAN),公司ID(12)及角色ID(12):因此这里的明文是“BRIAN;12;2;”。以下则是经过加密的Query String实例,请注意加密后的UID参数使用了ASCII十六进制表示法。

http://sampleapp/home.jsp?UID=7B216A634951170FF851D6CC68FC9537858795A28ED4AAC6

在实际情况中,攻击者并不会知道这里所对应的明文是多少,不过作为示例,我们已经知道了明文、填充、以及加密后的值(如下表)。正如之前所提到的那样,IV添加在密文的前段,即最前面8个字节。

攻击者可以根据加密后值的长度来推测出数据块的大小。由于长度(这里是24)能被8整除但不能被16整除,因此可以得知数据块的大小是8个字节。现在我们来观察下加密和解密的内部实现,下图便展示了字节级别的运算方式,这对以后攻击方式的讨论很有帮助。请注意,其中带圆圈的加号表示XOR(异或)操作。

加密过程:

解密过程:

同样值得指出的是,解密之后的最后一个数据块,其结尾应该包含正确的填充序列。如果这点没有满足,那么加/解密程序就会抛出一个填充异常。

利用Padding Oracle进行解密

我们现在来关注一下如何利用Padding Oracle Attack进行解密。我们将每次操作一个单独的加密块,因此我们可以独立出第一块密文(IV后的那块),在前面加上全为NULL的IV值,并发送至应用程序。以下是URL极其相关回复:

Request: http://sampleapp/home.jsp?UID=0000000000000000F851D6CC68FC9537
Response: 500 - Internal Server Error

回复的500错误是意料之中的,因为这个值在解密后完全非法。下图展示了应用程序在尝试解密的时候究竟做了哪些事情。您会发现,因为我们只处理单个数据块,因此它的结尾必须包含正确的填充字节,才能避免出现非法填充异常。

如上图所示,在解密之后,数据块的末尾并没有包含正确的填充序列,因此出现了异常。现在我们将IV加一,并发送同样的密文,看看会发生什么:

Request: http://sampleapp/home.jsp?UID=0000000000000001F851D6CC68FC9537
Response: 500 - Internal Server Error

与之前一样,我们得到了500异常。这是因为在解密后我们还是没有获得合法的填充序列。稍有不同的是,我们在深入内部之后会发现,最后一个字节的值会有所变化(变成了0x3C而不是0x3D)。

如果我们重复发送这样的请求,每次将IV的最后一个字节加一(直至0xFF),那么最终我们将会产生一个合法的单字节填充序列(0x01)。对于可能的256个值中,只有一个值会产生正确的填充字节0x01。遇上这个值的时候,你应该得到一个不同于其他255个请求的回复结果:

Request: http://sampleapp/home.jsp?UID=000000000000003CF851D6CC68FC9537
Response: 200 OK

同样,我们从示意图中了解一下此时发生了什么:

在这个情况下,我们便可以推断出中间值(Intermediary Value)的最后一个字节,因为我们知道它和0x3C异或后的结果为0x01,于是:

因为 [Intermediary Byte] ^ 0×3C == 0×01, 
得到 [Intermediary Byte] == 0×3C ^ 0×01, 
所以 [Intermediary Byte] == 0×3D

现在我们可以更进一步。我们已经知道了中间值的最后一个字节,于是我们可以推断出解密后的值是多少。您可以回忆一下,在解密的过程中,中间值的每个字节都会与密文中的前一个数据块(对于第一个数据块来说便是IV)的对应字节进行异或操作,于是我们使用之前示例中原来的IV中的最后一个字节(0x0F),与中间值异或一下便可以得到明文。不出意料,我们会得到0x32,这表示数字“2”(明文中第一个数据块的最后一个字节)。

我们现在已经破解了示例数据块中的第8个字节,是时候关注第7个字节了。在破解第8个字节时,我们使用暴力枚举IV,让解密后的最后一个字节成为0x01(合法填充)。在破解第7个字节的时候,我们要做的事情也差不多,不过此时要求第7个字节与第8个字节都为0x02(再重复一遍,这表示合法的填充)。我们已经知道,中间值的最后一个字节是0x3D,因此我们可以将IV中的第8个字节设为0x3F(这会产生0x02)并暴力枚举IV的第七个字节(从0x00开始,直至0xFF)。

我们再次遭遇填充异常,直至遇上某个值,它使得解密后的第7个字节成为0x02(正确填充),此时IV中的字节为0x24:

使用这种技巧,我们可以从后往前破解中间值里的每个字节,最终得到解密后的值(尽管每次一个字节)。下图展示了完全破解后的IV值,此时整个数据块都为填充值(0x08):

使用PadBuster进行解密

(译注:这段内容为PadBuster的使用指南,在此略过,如果您对这部分内容感兴趣可以阅读原文。)

加密任意的值

我们已经知道如何利用Padding Oracle和PadBuster来依次破解每个加密的数据块。现在,我们就来观察下如何使用同样的漏洞来加密任意数据。

可能您已经发现,一旦我们可以推断出密文数据块的中间值,我们便能通过操作IV的值来完全控制解密所得到的结果。例如,在前面的示例中,如果想要将密文中第一个数据块解密为“TEST”这个值,您可以计算出它所需要的IV值,只要将目标明文与中间值进行异或操作即可。因此,只要您将字符串“TEST”(自然,还包括四个0x04字节作为填充)与中间值异或之后,便可以得到最终的IV,即0×6D,0×36,0×70,0×76,0×03,0×6E,0×22,0×39:

这种做法对于单个数据块来说自然没有问题,但如果我们想要用它来生成长度超过一个数据块的值又该怎么办呢?我们来看一个简单通俗的实际案例。这次我们要生成一个加密的字符串“ENCRYPT TEST”而不仅仅是“TEST”。第一步,还是将文本分拆成数据块,并补上必须的填充字节,如下图:

在构造超过一个数据块的值时,我们实际上是从最后一个数据块开始,向前依次生成所需的密文。在这里,最后的数据块与之前的相同,因此我们已经知道以下的IV和密文能够生成字符串“TEST”:

Request: http://sampleapp/home.jsp?UID=6D367076036E2239F851D6CC68FC9537

接下来,我们需要弄明白中间值6D367076036E2239在作为密文,而不是IV传递至应用程序时会被如何解密。在这里只要使用与破解过程相同的技巧就行了,我们把它作为密文传递给应用程序,并从全部为NULL的IV开始进行暴力破解:

Request: http://sampleapp/home.jsp?UID=00000000000000006D367076036E2239

一旦我们通过暴力破解得到中间值之后,IV便可以用来生成我们想要的任意值。新的IV可以被放在前一个示例的前面,这样便可以得到一个符合我们要求的,包含两个数据块的密文了。这个过程可以不断重复,这样便能生成任意长度的数据了。

使用PadBuster加密任意的值

(译注:这段内容为PadBuster的使用指南,在此略过,如果您对这部分内容感兴趣可以阅读原文。)

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

17 条回复

  1. 老赵
    admin
    链接

    老赵 2010-10-09 00:34:13

    翻译真费时,还总是翻不好……

  2. 小强.假的
    61.172.247.*
    链接

    小强.假的 2010-10-09 09:16:04

    ...2010-10-09 00:34:13...

    太给力了.

  3. 老赵
    admin
    链接

    老赵 2010-10-09 23:18:04

    对于这个话题大家好像都没什么反应呢。

  4. 我还是不懂
    202.108.19.*
    链接

    我还是不懂 2010-10-09 23:52:40

    没看懂,哈哈!

  5. luotong
    123.9.198.*
    链接

    luotong 2010-10-10 09:50:25

    这么长,辛苦了呀.

  6. 为了梦想
    119.186.135.*
    链接

    为了梦想 2010-10-10 19:31:11

    太难懂了,不好意思

  7. ipmsteven
    122.100.176.*
    链接

    ipmsteven 2010-12-03 23:05:03

    翻译的很好,加油啊。希望作者多翻译一些好的文章。

  8. jiangbin00cn
    114.255.204.*
    链接

    jiangbin00cn 2011-02-27 18:59:56

    加密算法使用和项目管理之间不可调和矛盾。 加密的安全性在于将数据作为一个整体, 而高效项目管理需要分割问题。

    看来程序员(特别是编写安全模块)有必要学习加密算法的原理,不能仅仅满足于应用。

  9. veryhonker
    121.230.205.*
    链接

    veryhonker 2011-03-23 08:39:57

    写的太好了,大开眼界,虽然只是略懂了些。

  10. 11111edee
    117.79.232.*
    链接

    11111edee 2011-03-31 13:04:12

    看懂了,哈哈,太牛了

  11. hackloft
    61.170.201.*
    链接

    hackloft 2011-06-07 11:13:19

    我是在想...如果可以加密...如果不是用来读文件呢? axd的即ms10070 我觉得只是这个漏洞的 很好的一个表现方式 如果知道密文加密的方式、结构 又可以这种方式再次加密 那么...

  12. data
    210.45.251.*
    链接

    data 2011-11-10 00:55:07

    看是看懂了,不过觉得里面只是实现了最简单的情况,很想向您请教如果更复杂的情况怎么处理呢,比如IV是不公开的、使用了认证加密呢……

  13. czw_mr
    124.126.234.*
    链接

    czw_mr 2011-12-01 12:15:32

    protected override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        //我的逻辑 blablabla……
        base.OnActionExecuting(filterContext);
    }
    
    protected override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        base.OnActionExecuting(filterContext);
        //我的逻辑 blablabla……
    }
    

    哪个合理??

  14. 老赵
    admin
    链接

    老赵 2011-12-01 15:58:57

    @czw_mr

    这完全看你的逻辑是要干什么啊,甚至不调用base的方法也是可能的。

  15. czw_mr
    124.126.234.*
    链接

    czw_mr 2011-12-01 17:42:07

    还是得看逻辑而定,不能想当然。谢谢老赵~~~

  16. package
    1.192.82.*
    链接

    package 2013-10-22 15:47:51

    你好 关于分块的情况 能不能讨论下 我这边 看到的基本上都是 92 字符 该怎么分块?

  17. kericnnoe1964
    111.242.184.*
    链接

    kericnnoe1964 2023-03-13 01:36:40

    你知道一個線上博弈平台主打什麼樣的標語會讓玩想立家刻去下載嗎?那就是超高勝率,畢竟沒有人不希望自己花時間去玩百家投注最後不僅沒有賺到錢還需要賠錢的

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我