前段时间遇到一个挺有意思的题目,用到了padding oracle attack的相关知识,于是恶补了一下padding oracle attack相关内容,本着取之于民用之于民同时也可以方便自己以后复习的心态,我决定整理一下这几天的所学所得,也算是留下点什么hhh。
前面两篇文章简单介绍了padding oracle attack需要事先了解的一些知识,以便于理解,现在终于进入正题了hhh。下面我就我自己的理解说一下padding oracle attack的实现原理以及过程。
Padding Oracle Attack是针对CBC链接模式的攻击,和具体的加密算法无关,换句话说,这种攻击方式不是对加密算法的攻击,而是利用了算法的使用不当,进行的攻击。选择DES为例进行阐述,假设明文为: LittleHann(明文长度为10 8 < 10 < 16 即使用2个分组)经过DES加密(CBC模式)后,其密文为: EFC2807233F9D7C097116BB33E813C5E
如上图所示,padding oracle攻击其实就是通过服务器的返回值判断自己构建的IV值对不对,进而猜测出正确的中间值Intermediary Value,中间值再和获取到的原本的IV值异或便可得到明文,因此攻击成立的两个重要假设前提:
(1) 攻击者能够获得密文(Ciphertext),以及附带在密文前面的IV(初始化向量)
(2) 攻击者能够触发密文的解密过程,且能够知道密文的解密结果
这里有几个概念要先理清一下:
(1)基于密码学算法的攻击,往往第一个要搞清楚的是,我们在攻击谁,或者准确的说我们的攻击点在哪里?在一个密码学算法中,有很多的参数(指攻击者可以控制的参数),攻击者往往是针对其中某一个或某一些参数进行破解,穷举等攻击。在Padding Oracle Attack攻击中,攻击者输入的参数是IV+Cipher,我们要通过对IV的”穷举”来请求服务器端对我们指定的Cipher进行解密,并对返回的结果进行判断。
(2)和SQL注入中的Blind Inject思想类似。Padding Oracle Attack也是利用了这个二值逻辑的推理原理,或者说这是一种”边信道攻击(Side channel attack)”。这种漏洞不能算是密码学算法本身的漏洞,但是当这种算法在实际生产环境中使用不当就会造成问题。和盲注一样,这种二值逻辑的推理关键是要找到一个”区分点”,即能被攻击者用来区分这个的输入是否达到了目的(在这里就是寻找正确的IV)。 在web应用中,如果Padding不正确,则应用程序很可能会返回500的错误(程序执行错误);如果Padding正确,但解密出来的内容不正确,则可能会返回200的自定义错误,所以,这种区别就可以成为一个二值逻辑的”注入点”。
(4)明文分组和填充就是Padding Oracle Attack的根源所在,但是这些需要一个前提,那就是应用程序对异常的处理。当提交的加密后的数据中出现错误的填充信息时,不够健壮的应用程序解密时报错,直接抛出”填充错误”异常信息(这个错误信息在不同的应用中是不同的体现,在web一般是报500错误)。攻击者就是利用这个异常来做一些事情,假设有这样一个场景,一个WEB程序接受一个加密后的字符串作为参数,这个参数包含用户名、密码。参数加密使。攻击者只需要提交错误的密文,根据HTTP Code即可做出攻击。
下面就我做的这个题目说明一下padding oracle attack的大概思路:
首先,我们要明确目的——找到正确的中间值,进而得到明文。我们先回忆一下CBC模式解密的过程,如下图:
从图中我们可以看出,第n-1块密文是与第n块密文中间值异或的IV值。根据PKCS #5标准,我们知道,填充N位需在明文后面加上n个0xN,同时我们还知道明文最后一定有填充值,也就是说倒数第二块密文和最后一块密文的中间值异或得到的值最后N位(0 这样说看上去很乱,下面我就题目再捋一下padding oracle attack的过程: (1)待破解的密文为9F0B13944841A832B2421B9EAF6D9836813EC9D944A5C8347A7CA69AA34D8DC0DF70E343C4000A2AE35874CE75E64C31,采用了CBC模式下的AES算法加密,下面是服务器的登录程序(python): 完整的解题源码如下:s = None
def Oracle_Connect():
import socket
global s
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect(('128.8.130.16', 49101))
except socket.error as e:
print (e)
return -1
print ("Connected to server successfully.")
return 0
def Oracle_Disconnect():
if not s:
print ("[WARNING]: You haven't connected to the server yet.")
return -1
s.close()
print ("Connection closed successfully.")
return 0
# Packet Structure: < num_blocks(1) || ciphertext(16*num_blocks) || null-terminator(1) >
def Oracle_Send(ctext, num_blocks):
if not s:
print ("[WARNING]: You haven't connected to the server yet.")
return -1
msg = ctext[:]
msg.insert(0, num_blocks)
msg.append(0)
s.send(bytearray(msg))
recvbit = s.recv(2)
try:
return int(recvbit)
except ValueError as e:
return int(recvbit[0])
(2)首先根据AES算法的标准,我们把密文分组,每16字节为一组,可以分为三组,并且新建用于IV值构造的IV列表和用于存放明文的P列表
data = "9F0B13944841A832B2421B9EAF6D9836813EC9D944A5C8347A7CA69AA34D8DC0DF70E343C4000A2AE35874CE75E64C31"
ctext = [(int(data[i:i+2],16)) for i in range(0, len(data), 2)]
C = [ctext[:16], ctext[16:32], ctext[-16:]]
P = [[0] * 16, [0] * 16, [0] * 16]
IV = [[0] * 16, [0] * 16, [0] * 16]
(3)重复向服务器提交密文,猜测正确的明文加密后的中间值(Intermediary Value),得到正确的中间值后,和已知的IV值互相异或便可以得到明文:
Oracle_Connect()
for bi in range(2):#循环两次,解密两个密文块(有一块密文块是IV值)
b = 2 - bi
for k in range(16):#K+1代表padding的个数,例如K=0时填充1位,服务器异或得到的值应该为0x01
if b == 2:
C1 = [C[0][:], C[1][:], C[2][:]]#解密C[2]对应的密文块,C[1]是IV值
if b == 1:
C1 = [C[0][:], C[1][:]]#解密C[1]对应的密文块,C[0]是IV值
pos = 15 - k#pos对应的是IV值的第pos-1位
for i in range(pos):
C1[b-1][i] = randrange(256)#构造IV值,从第0位到第pos—1位,随机填入0到255(即0x00到0xFF)中的一个数字
#剩下K+1位用于循环猜测
for i in range(pos + 1, 16):
C1[b-1][i] = (k + 1) ^ IV[b][i]#得到正确的中间值后存入IV,并将正确的中间值和K+1异或,
#保证每次只循环猜测第pos-1位IV值
ii = -1
for i in range(256):#循环猜测IV值,正确的IV值满足与正确中间值异或得到的数是0x(K+1)
C1[b-1][pos] = i
if b == 2:
rc = Oracle_Send(C1[0][:] + C1[1][:] + C1[2][:], 3)
if b == 1:
rc = Oracle_Send(C1[0][:] + C1[1][:], 2)
if rc == 1:
ii = i#ii即正确的中间值与0x(K+1)异或得到的IV值
break
IV[b][pos] = ii ^ (k + 1)#IV值与K+1异或便可得到正确的中间值
P[b][pos] = C[b-1][pos] ^ IV[b][pos]#中间值与原来的IV值异或便可得到对应位的明文
Oracle_Disconnect()
这样我们便能得到正确的明文了:
from oracle import *
import sys
from random import randrange
data = "9F0B13944841A832B2421B9EAF6D9836813EC9D944A5C8347A7CA69AA34D8DC0DF70E343C4000A2AE35874CE75E64C31"
ctext = [(int(data[i:i+2],16)) for i in range(0, len(data), 2)]
C = [ctext[:16], ctext[16:32], ctext[-16:]]
P = [[0] * 16, [0] * 16, [0] * 16]
IV = [[0] * 16, [0] * 16, [0] * 16]
Oracle_Connect()
for bi in range(2):#循环两次,解密两个密文块(有一块密文块是IV值)
b = 2 - bi
for k in range(16):#K+1代表padding的个数,例如K=0时填充1位,服务器异或得到的值应该为0x01
if b == 2:
C1 = [C[0][:], C[1][:], C[2][:]]#解密C[2]对应的密文块,C[1]是IV值
if b == 1:
C1 = [C[0][:], C[1][:]]#解密C[1]对应的密文块,C[0]是IV值
pos = 15 - k#pos对应的是IV值的第pos-1位
for i in range(pos):
C1[b-1][i] = randrange(256)#构造IV值,从第0位到第pos—1位,随机填入0到255(即0x00到0xFF)中的一个数字
#剩下K+1位用于循环猜测
for i in range(pos + 1, 16):
C1[b-1][i] = (k + 1) ^ IV[b][i]#得到正确的中间值后存入IV,并将正确的中间值和K+1异或,
#保证每次只循环猜测第pos-1位IV值
ii = -1
for i in range(256):#循环猜测IV值,正确的IV值满足与正确中间值异或得到的数是0x(K+1)
C1[b-1][pos] = i
if b == 2:
rc = Oracle_Send(C1[0][:] + C1[1][:] + C1[2][:], 3)
if b == 1:
rc = Oracle_Send(C1[0][:] + C1[1][:], 2)
if rc == 1:
ii = i#ii即正确的中间值与0x(K+1)异或得到的IV值
break
IV[b][pos] = ii ^ (k + 1)#IV值与K+1异或便可得到正确的中间值
P[b][pos] = C[b-1][pos] ^ IV[b][pos]#中间值与原来的IV值异或便可得到对应位的明文
Oracle_Disconnect()
print (P[1])
print (P[2])
print (map(lambda x: str(unichr(x)), P[1]))
print (map(lambda x: str(unichr(x)), P[2]))
a = ""
print (("the text is: %s") % a.join(text))