CBC Padding Oracle Attack
原理:
https://blog.gdssecurity.com/labs/2010/9/14/automated-padding-oracle-attacks-with-padbuster.html
https://www.jianshu.com/p/1851f778e579
https://www.cnblogs.com/LittleHann/p/3391393.html
总结:
在加密解密中,为了针对不同长度的明文都能进行加密,经常会有分组与填充的情况。对于CBC模式,经常使用的模式是PKCS#5,即:最后缺少x字节,就填充x字节的x。
同时,在解密的过程中,一旦解密出来的结果不符合PKCS#5的填充规则,那么会给出与正确解密不一致的提示信息,这就给了攻击者暴力破解的可乘之机。
题目链接:
https://github.com/sonickun/ctf-crypto-writeups/tree/master/2016/hack.lu-ctf/cryptolocker
先分析题目的关键代码
class AESCipher(object): def __init__(self,key): self.bs = 32 self.key = key def encrypt(self,raw): raw = self._pad(AESCipher.str_to_bytes(raw)) iv = Random.new().read(AES.block_size) cipher = AES.new(self.key,AES.MODE_CBC,iv) return iv + cipher.encrypt(raw) def _pad(self,s): return s + (self.bs - len(s) % self.bs) * AESCipher.str_to_bytes(chr(self.bs - len(s) % self.bs))
AES加密,16个字节一组,以PKCS#5方式填充,CBC模式
user_input = sys.argv[2].encode('utf-8') assert len(user_input) == 8 i = len(user_input) // 4 keys = [ # Four times 256 is 1024 Bit strength!! Unbreakable!! hashlib.sha256(user_input[0:i]).digest(),hashlib.sha256(user_input[i:2*i]).digest(),hashlib.sha256(user_input[2*i:3*i]).digest(),hashlib.sha256(user_input[3*i:4*i]).digest(),] s = SecureEncryption(keys)
自己的密钥为8位,分成4组,不够的程序自动填充
所以,满足攻击条件,我们只需要暴力密钥,然后对明文解密,根据padding的情况是不是符合格式来判断密钥是否正确即可。暴力成功后,直接解密得到flag
import sys import hashlib from AESCipher import * import string import itertools class SecureEncryption(object): def __init__(self,keys): #assert len(keys) == 4 self.keys = keys self.ciphers = [] for i in range(len(keys)): self.ciphers.append(AESCipher(keys[i])) def enc(self,plaintext): # Because one encryption is not secure enough one = self.ciphers[0].encrypt(plaintext) two = self.ciphers[1].encrypt(one) three = self.ciphers[2].encrypt(two) ciphertext = self.ciphers[3].encrypt(three) return ciphertext def dec(self,ciphertext): three = AESCipher._unpad(self.ciphers[3].decrypt(ciphertext)) two = AESCipher._unpad(self.ciphers[2].decrypt(three)) one = AESCipher._unpad(self.ciphers[1].decrypt(two)) plaintext = AESCipher._unpad(self.ciphers[0].decrypt(one)) return plaintext def mydec(self,ciphertext): tmp = ciphertext for i in range(len(self.keys)-1): tmp = AESCipher._unpad(self.ciphers[len(self.keys)-i-1].decrypt(tmp)) plaintext = self.ciphers[0].decrypt(tmp) return plaintext def checkPadding1(plain): padlen = ord(plain[-1]) pad = chr(padlen)*padlen if plain[-padlen:] != pad or padlen > 32 or padlen == 1: return False else: return True def checkPadding2(plain): pad = chr(16)*16 if len(plain) > 1 and plain[-16:] == pad: return True else: return False cipher = open("flag.encrypted","rb").read() keys = [] password = "" charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" for i in range(4): for c in itertools.product(charset,repeat=2): user_input = "".join(c) tmp_keys = keys[:] tmp_keys.insert(0,hashlib.sha256(user_input).digest()) s = SecureEncryption(tmp_keys) plain = s.mydec(cipher) if i == 3: if checkPadding1(plain): keys = tmp_keys[:] password = user_input + password print "[+] found password:",password open("flag.odt","wb").write(AESCipher._unpad(plain)) break else: if checkPadding2(plain): keys = tmp_keys[:] password = user_input + password print "[+] found password:",password break
学到了一个新的暴力姿势:
charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" for c in itertools.product(charset,repeat=2): user_input = "".join(c)