Padding_Oracle 攻击

道哥的《白帽子讲web安全》有一章提到Padding Oracle Attack的攻击方式,据说这货在2011年的Pwnie Rewards上还被评为”最具价值的服务器漏洞”。而且比赛中经常遇到,而且一般都是混在web题里的crypto

PS:ms10-070

适用于CBC模式下的AESDES3DES等分组密码

攻击成立的条件:

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

这个题是aescbc 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)

我感觉这种题目还是不适合用工具(因为我也没看懂这些工具怎么用)

×

纯属好玩

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

文章目录
  1. 1. 原理
  • 分组的填充(Padding)
    1. 1. 攻击原理
  • 破解密文得到明文
  • 构造任意明文对应的密文
    1. 1. 例题
  • is_aes_secure
  • 工具
  • ,