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
代码即为链接中的solver.py
学到了一个新的暴力姿势:
charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
for c in itertools.product(charset, repeat=2):
user_input = "".join(c)