Topic: (new topic — not in KOSEN68; built fresh) · Kind: LAB Concepts: authenticated encryption (AEAD), unauthenticated-CBC malleability, PKCS#7 padding oracle, AES-GCM, encrypt-then-MAC · Analogous CWE: CWE-347 (Improper Verification of Cryptographic Signature), CWE-757 (Selection of Less-Secure Algorithm), CWE-208 (Observable Timing/Response Discrepancy — the side channel)
This is the capstone of the symmetric block: Week 3 gave you a MAC (integrity), Week 4 gave you AES-CBC (confidentiality) and showed CBC ciphertext is malleable. Bolt those two together wrong and you get a padding oracle — an attacker decrypts your ciphertext byte-by-byte without ever seeing the key. Bolt them together right — encrypt-then-MAC, or use a single AEAD primitive like AES-GCM — and the attack evaporates. That "combine them correctly" primitive is AEAD.
✅ This week — what to do
- Before class — Docker Desktop working (same Docker-first setup as
software-security); re-skim Week 4 (AES-CBC, bit-flipping malleability) and Week 3 (MACs) — this week is those two combined. - Lecture (120 min) — what AEAD guarantees (confidentiality + integrity + authenticity in one primitive); Encrypt-then-MAC vs MAC-then-Encrypt vs Encrypt-and-MAC; why unauthenticated CBC leaks to a padding oracle; why AES-GCM has no padding step and checks the tag first. Slides:
slides/week06.md. - Lab (180 min) — play the decryption game below, capture your flag, then complete Worksheet 6 (
worksheet.md, Part 1 Conventional + Part 2 AIR-Sec incl. Audit the AI + EiPE + Prompt Problem). Kickoff:docker compose up. - Submit — worksheet PDF + flag → see SUBMISSION.md · exploit notes → GitHub.
Objectives
- State what AEAD stands for and the three guarantees it delivers in one primitive.
- Explain a padding oracle: how a single distinguishable "bad padding" response turns unauthenticated CBC into a byte-by-byte decryption of arbitrary ciphertext — without the key.
- Run the standard CBC padding-oracle attack to recover a secret plaintext (containing your flag) from ciphertext alone, using only a 200-vs-403 side channel.
- Explain why AES-GCM (an AEAD cipher) has no padding oracle — stream mode means no padding to probe, and the authentication tag is verified before any plaintext is released — and verify that empirically against a fixed target that returns a uniform error for every failure.
- Distinguish the provably safe generic composition (encrypt-then-MAC) from the unsafe ones.
🔓 Decryption game — "Read the Secret Without the Key"
Two Flask targets, same endpoints, different cipher:
| Service | Port | Cipher | Vulnerable to a padding oracle? |
|---|---|---|---|
vulnerable_app.py | :8098 | AES-256-CBC, unauthenticated, /decrypt leaks padding validity | Yes |
fixed_app.py | :8099 | AES-256-GCM (AEAD), /decrypt returns a uniform error | No |
Why it's exciting: a padding oracle turns a plain 403 error into a skeleton key, recovering a whole secret one byte at a time from nothing but yes/no answers.
Both serve the same secret message (which contains the flag) at GET /secret as base64. Both expose POST /decrypt {"ciphertext": "<base64>"}. The difference is the decrypt response:
- Vulnerable (
:8098, CBC):200if the decrypted plaintext has valid PKCS#7 padding,403 "bad padding"if not. Those two distinguishable answers are the oracle — the endpoint never returns the plaintext, yet the attacker recovers it purely from the 200/403 signal. - Fixed (
:8099, GCM):200only if the 128-bit tag verifies;403(uniform) for any failure — bad base64, wrong length, bad nonce, tag mismatch — all indistinguishable. No signal.
- Grab the ciphertext:
GET /secreton:8098→base64(IV || ct). - Recover it: using only the
/decryptoracle, run the CBC padding-oracle attack to recover the full plaintext byte-by-byte, without the AES key (exploit.pyimplements it from scratch — no crypto library needed on the attacker side, onlyrequests). - Read the flag: the recovered plaintext is
msg:FLAG{...}→ that's your flag. - Confirm the fix: run the identical attack against
:8099(AES-GCM). It gets zero usable signal (every query is403), recovers nothing → the AEAD app is safe.
The key is never known to the attacker. The whole point is that unauthenticated CBC + a padding side channel gives decryption without the key; AEAD removes both the side channel and the malleability.
Run it
cd labs/week06-aead
docker compose up -d # vulnerable_app.py on :8098, fixed_app.py on :8099
python exploit.py # PASS on :8098 (recovers plaintext + flag), PASS on :8099
# (attack gets no signal); exit 0
docker compose down
Per-student flag: python3 ../../instructor/seed_flags.py env <STUDENT_ID> > .env before docker compose up (once this course's instructor/seed_flags.py exists — see course-plan-19weeks.md open decision; until then FLAG_AEAD falls back to a shared placeholder that is the same for everyone). The exploit derives the block count from /secret, so flags of any length work.
Verified: docker compose up -d --build (from a scratch copy — OneDrive placeholders can break in-place docker build) followed by python exploit.py was run against live containers on this machine. Both PASS checks printed and the script exited 0: the padding oracle recovered the full plaintext msg:FLAG{...} from :8098 without the key, and the identical attack recovered nothing from the AES-GCM app on :8099 (uniform 403). The FLAG_AEAD env-override was confirmed with a different-length flag (FLAG{aead_student_42_xyz} → a 2-block ciphertext instead of 3) — the exploit derived the new block count from /secret and recovered it correctly, PKCS#7 padding stripped. Negative controls confirmed: garbage/empty ciphertext → 403 on both apps; a tampered GCM tag → 403 on :8099; the original secret → 200 on both.
Deliverable
The recovered flag + a short note on which byte you force to 0x01, 0x02 0x02, … in the forged previous block, why that reveals one byte of the intermediate value D(C_t) per query, and why AES-GCM has no equivalent probe. Full tasks: worksheet.md.
References
- Boneh & Shoup, A Graduate Course in Applied Cryptography, ch. 9 (Authenticated Encryption) — free online.
- Vaudenay (2002), Security Flaws Induced by CBC Padding — the original padding-oracle paper.
- NIST SP 800-38D — Galois/Counter Mode (GCM) and GMAC.
- RFC 5116 — An Interface and Algorithms for Authenticated Encryption (defines the AEAD API).
- https://en.wikipedia.org/wiki/Padding_oracle_attack