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 secretCalling 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