道哥的《白帽子讲web安全》有一章提到Padding Oracle Attack的攻击方式,据说这货在2011年的Pwnie Rewards上还被评为”最具价值的服务器漏洞”。而且比赛中经常遇到,而且一般都是混在web
题里的crypto
。
PS:ms10-070
适用于CBC模式下的AES
,DES
,3DES
等分组密码
攻击成立的条件:
1 2
| 1. 攻击者能够获得密文(Ciphertext),以及附带在密文前面的IV(初始化向量) 2. 攻击者能够触发密文的解密过程,且能够知道密文的解密结果
|
原理
尽量去看《白帽子讲web安全》,网上有些讲解原理有些问题,我看了半天才发现,而且后来虽然看懂了,但是代码实现又有些难度,就结合一些题目的wp中别人的代码去理解,然后自己实现一遍之后发现对原理理解更加深刻。
分组的填充(Padding)
分组密码Block Cipher需要在加载前确保每个每组的长度都是分组长度的整数倍。一般情况下,明文的最后一个分组很有可能会出现长度不足分组的长度:
这个时候,普遍的做法是在最后一个分组后填充一个固定的值,这个值的大小为填充的字节总数。即假如最后还差3个字符,则填充0×03。
1 2 3 4 5 6 7 8
| 1个字节的Padding为0x01 2个字节的Padding为0x02 3个字节的Padding为0x03 4个字节的Padding为0x04 5个字节的Padding为0x05 6个字节的Padding为0x06 7个字节的Padding为0x07 8个字节的Padding为0x08(当原始的明文正好是分组的整数倍的时候,Padding一个整组的填充值)
|
just like:
这种Padding原则遵循的是常见的PKCS#5标准
cbc(Cipher Block Chaining CBC)模式下加解密:
这里要注意,前几个分组的解密结果对我们都没有意义,我们重点关注的是最后一个分组的解密结果。看这张图可能会清楚一点:
攻击原理
在Padding Oracle Attack攻击中,攻击者输入的参数是IV+Cipher
,我们要通过对IV的”穷举”来请求服务器端对我们指定的Cipher进行解密,并对返回的结果进行判断。
当提交参数时,服务端的返回结果会有下面3种情况:
- 参数是一串正确的密文,分组、填充、加密都是对的(程序运行本身没出问题),包含的内容也是正确的(业务逻辑是对的),那么服务端解密、检测用户权限都没有问题,返回HTTP 200。
- 参数是一串错误的密文,包含不正确的bit填充(程序运行本身出现致命错误),那么服务端解密时就会抛出异常,返回HTTP 500 server error。
- 参数是一串正确的密文(程序运行本身没出问题),包含的用户名是错误的(业务逻辑是错的),那么服务端解密之后检测权限不通过,但是依旧会返回HTTP 200戒者HTTP 302,而不是HTTP 500。
因此慢慢调整IV的值,以此希望解密后,最后一个字节的值为正确的padding byte,比如一个0x01。
破解密文得到明文
先爆破出最后字节:
因为Intermediray Value
是固定不变的,所以我们可以通过遍历Initialization Vector
的最后一个字节(从0x00带0xFF),有且只有一个的值与Intermediray Value
进行XOR后结果是0x01。通过遍历这255个值,我们得到了那个我们所需的IV,此时解密系统返回的是一个乱码,而不是解密失败,可以根据返回值来确定那个IV。
PS:下面这图就是我开始说的网上有问题的地方,黄色应该是0x3c,蓝色的地方应该是0x3d,可以参考书本
然后将我们现在得到的IV
与目前填充的值通过XOR运算,我们可以算出Intermediray Value
的最后一个字节,根据解密过程,将这个字节和真实的IV最后一个字节XOR,则是明文的最后一个字节~~
爆破倒数第二个字节:
在正确匹配了padding “0x01”后,需要做的是继续推导出剩下的Intermediary Byte
。根据padding标准,当需要padding两个字节的时候,其值应该为0x02,0x02。而此时我们知道了Intermediary Byte
为0x3d,因此我们可以先更新IV的最后一个字节为0x3d^0x02=0x3f(这样等会做XOR的时候,最后一位肯定一直为0x02,我们就只需要关注倒数第二位即可),此时可以开始遍历IV倒数第二个字节了(0x00-0xFF),跟前面步骤一样。
接下来依次类推,就可以推导出所有Intermediary Byte
,我们这时候就完成了不需要密钥来解密爆破得到了明文。
构造任意明文对应的密文
根据上面的解法,我们可以得到Intermediary Value
,而在这个前提下,而且我们可以控制IV,所以我们可以通过改变IV来使得XOR的结果为任意明文,还是观察这张图:
对于多个分组的密文来说,从最后一组密文开始往前推。以两个分组为例,当我们推出第二个分组的IV(我们通过改变IV来改变解密出来的明文),而此时这个IV是第一个分组的密文,所以我们需要将这个IV作为第一个分组的密文再进行推导。
例题
虽然上面的原理理解起来不难,但是具体情境中代码实现起来还是挺难的,所以还是用例题来详细说明吧
is_aes_secure
题目是第三届上海市网络安全大赛的题目,题目源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| #!/usr/bin/ruby -w require 'openssl' require 'base64' def banner() puts ' ____________________________________________' puts '| |' puts '| Welcome to our secure communication system |' puts '| Our system is secured by AES |' puts '| So...No key! No Message! |' puts '|____________________________________________|' puts '' end def option() puts '1. Get the secret message.' puts '2. Encrypt the message' puts '3. Decrypt the message.' puts 'Give your option:' STDOUT.flush op=gets return op.to_i end def init() file_key=File.new("./aeskey","r") $key=file_key.gets file_key.close() end def aes_encrypt(iv,data) cipher = OpenSSL::Cipher::AES.new(256, :CBC) cipher.encrypt cipher.key = $key cipher.iv = iv cipher.update(data) << cipher.final end def aes_decrypt(iv,data) cipher = OpenSSL::Cipher::AES.new(256, :CBC) cipher.decrypt cipher.key = $key cipher.iv = iv data = cipher.update(data) << cipher.final end def output_secret() file_secret=File.new("./flag","r") secret=file_secret.gets file_secret.close secret_enc=aes_encrypt("A"*16,secret) secret_enc_b64=Base64.encode64(secret_enc) puts secret_enc_b64 end init banner while true do begin op=option if op==1 output_secret elsif op==2 puts "IV:" STDOUT.flush iv=Base64.decode64(gets) puts "Data:" STDOUT.flush data=Base64.decode64(gets) data_enc=aes_encrypt iv,data puts Base64.encode64(data_enc) puts "Encrytion Done" STDOUT.flush elsif op==3 puts "IV:" STDOUT.flush iv=Base64.decode64(gets) puts "Data:" STDOUT.flush data=Base64.decode64(gets) data_dec=aes_decrypt iv,data puts "Decrpytion Done" STDOUT.flush else puts 'Wrong Option' STDOUT.flush end rescue Exception => e puts e.message STDOUT.flush retry end end
|
这个题是aes
的cbc 256bit
加密方式,从给出的脚本可以看出,我们可以得到flag的密文,而且我们可以通过操作3得知我们输入的iv和密文是否符合格式,所以,可以使用padding oracle attack。这个密文长48个字节,所以,这是分成3块的cbc加密,第一块密文原本使用原来的iv: AAAAAAAAAAAAAAAA
作为iv来进行解密,第二块它使用第一块密文来进行解密,第三块使用第二块密文进行解密。这个加密过程,我们要不断更换iv,因为我们知道cbc模式是密文使用key进行加密得到一个中间值,中间值与iv逐位异或得到明文。根据padding的原理,我们只要一位一位进行爆破,求出中间值就好。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| # coding=utf-8 from pwn import * Cipher_Text = 's\xf8\x804*S=\x06\x9b=,3\xea,E*\xaa\xc1\xcd\xf6\xcc\xb8\x1eQ\xf0\x81\xa9\x0e\xa4\x11\xfe\x9e\xdb\xd6\xbfm\xe7\xba\xb5\x02\xda\xbd\xb9\xc5\x1b\x7f\xb4\x90' block1 = Cipher_Text[:16] block2 = Cipher_Text[16:32] block3 = Cipher_Text[32:] Origin_IV = 'A'*16 #Origin_IV = block1 #Origin_IV = block2 p = remote('106.75.98.74',10010) def Padding_Oracle_Attack(New_IV): p.recvuntil('option:\n') p.send('3\n') p.recvuntil('IV:\n') p.send(New_IV) p.recvuntil("Data:\n") p.send(block1.encode('base64')) response = p.recvline() print response if "Decrpytion Done" in response: print 'find true iv byte' print New_IV return 1 else: print 'false' Clear_Text = '' Known_Byte_IV = '' Known_Byte_Intermediary = '' #因为AES分组长度为16,所以Padding可以达到0x0f,而不是像DES的0x08 for Now_Padding in xrange(1,17): #将已知的Intermediary Byte与当前需要构造的填充进行XOR,来构造出后面几位的IV for byte in Known_Byte_Intermediary: Known_Byte_IV += chr(ord(byte)^Now_Padding) #开始遍历0x00-0xFF for i in xrange(0,256): #未知的Intermediary Byte所对应的IV用'0'来补齐 Unknown_Byte_IV = (15-len(Known_Byte_Intermediary)) * chr(0) New_IV = Unknown_Byte_IV +chr(i)+Known_Byte_IV if Padding_Oracle_Attack(New_IV.encode('base64')): Now_Byte_Intermediary = chr(i^Now_Padding) Known_Byte_Intermediary = Now_Byte_Intermediary+Known_Byte_Intermediary Clear_Text_Byte = chr(Now_Byte_Intermediary^ord(Origin_IV[16-Now_Padding])) Clear_Text = Clear_Text_Byte+Clear_Text print Clear_Text Known_Byte_IV = '' break
|
分三次跑出三组的值,连起来就是flag。
工具
工具:
1 2
| [https://github.com/mpgn/Padding-oracle-attack](https://github.com/mpgn/Padding-oracle-attack) [https://github.com/GDSSecurity/PadBuster](https://github.com/GDSSecurity/PadBuster)
|
我感觉这种题目还是不适合用工具(因为我也没看懂这些工具怎么用)