Decrypt the secret — without the key, one 200 or 403 at a time

A server encrypts data with AES-CBC and never returns the plaintext — only a 200 if the decrypted padding checks out, or a 403 if it doesn't. That feels safe: nothing is ever "leaked." Watch how far that single yes/no answer actually goes.

Both services protect the same secret text. This is all an attacker ever sees on the wire — never the plaintext, never either key:

Every other byte of the forged block stays 0x00. Drag until Oracle A flips to 200 — that single accepted guess reveals one real byte of the server's internal decryption state, with zero knowledge of its key.

Oracle A — AES-CBC (unauthenticated)

check: pkcs7_valid( D(C_t) XOR forged_prev )

Oracle B — toy-AEAD (tag checked first)

check: tag(forged_prev || C_t) == real tag — BEFORE any padding logic

This one manual guess is step one of the real attack. The automated version below repeats it for every byte of every block — forcing …0x02 0x02, then …0x03 0x03 0x03, and so on — disambiguating the false-positive case the same way exploit.py does, until the whole secret falls out. No AES key, ever.

Automated recovery — Oracle A

Automated recovery — Oracle B