Skip to main content

All weeks · Worksheet · Overview

Week 4 · Lecture slides

Week 4

Contents17 sections

AES / Block-Cipher Modes (CBC bit-flipping, malleability)

Security & Cryptography · Nutthakorn Chalaemwongwan


Today

  • AES: the primitive (block cipher) vs. the mode wrapping it
  • Why unauthenticated CBC is malleable — bit-flip an encrypted token
  • The fix: AES-GCM / AEAD — encryption + integrity, together
  • 🏁 Game: Flip Your Way to Admin — forge role=admin without the key

Recap — Week 3

  • A keyed hash is not automatically a MAC — length-extension forges valid tags without the key
  • HMAC fixed that gap: a construction designed to resist it
  • Same theme, one level up: last week broke integrity without a real MAC; today we break integrity without any authentication at all

AES: the primitive vs. the mode

  • AES itself: a textbook-secure block cipher — 128-bit blocks, 128/192/256-bit keys, no known practical break
  • A block cipher only encrypts one 16-byte block
  • Real messages need a mode of operation to chain blocks together: ECB, CBC, GCM…
  • The mode — not AES — decides whether the resulting scheme is actually secure

ECB leaks patterns (Q1)

  • ECB encrypts each block independently with the same key
  • Identical plaintext blocks → identical ciphertext blocks
  • The "ECB penguin": encrypt a bitmap image in ECB — the outline is still visible in the ciphertext
  • AES is unbroken here — the mode leaks structure, the cipher doesn't

CBC: chaining fixes ECB's leak

  • Each plaintext block is XOR-ed with the previous ciphertext block before encrypting
  • C_i = E(P_i XOR C_{i-1}) — block 0 uses a random IV instead of a previous block
  • Decrypt: P_i = D(C_i) XOR C_{i-1}
  • Fixes ECB's pattern leak... but the XOR-chaining opens a different door

The gap: encryption ≠ integrity (Q3, Q9)

  • CBC (and most classic modes) give confidentiality only
  • Nothing stops an attacker from editing ciphertext — the receiver still decrypts to something and trusts it
  • No MAC / no auth tag = no way to detect tampering
  • CWE-353 (Missing Support for Integrity Check) / CWE-649 (Reliance on Encryption Without Integrity)
  • Today's lab exploits exactly this gap to become admin

The target: an unauthenticated CBC token

vulnerable_app.py :8096 issues base64(IV ‖ C0 ‖ C1) — AES-256-CBC, no MAC. Fixed 32-byte plaintext, two blocks:

BlockPlaintextToken bytes
0comment=FILLER!!IV = token[0:16]
1role=guest;xpad0C0 = token[16:32], C1 = token[32:48]
  • /login issues the token, /whoami decrypts and reports the role, /admin checks it

Why flipping C0 changes the role

  • CBC decrypt: P1 = AES_decrypt(C1) XOR C0
  • Flipping byte k of C0 flips byte k of P1 — deterministically, without the key
  • guest sits at P1[5..9]; admin is also 5 bytes → same-length swap
  • C0[5+i] ^= guest[i] ^ admin[i] for i = 0..4 → edit token bytes 21..25
  • Side effect: block 0's plaintext (comment=...) turns to garbage — harmless, the app never reads it

Cashing in the flip

docker compose up -d          # vulnerable_app.py :8096, fixed_app.py :8097
python exploit.py             # PASS :8096 (flag), PASS :8097 (rejected)
  • No AES library needed for the attack itself — pure byte math (XOR)
  • Replay the tampered token cookie against /admin → flag
  • The attack never touches the key — swapping AES_KEY still works, proving it's pure ciphertext malleability, not a key leak

The fix: authenticated encryption (Q4)

  • fixed_app.py :8097 issues base64(nonce ‖ AES-256-GCM(plaintext) ‖ tag)
  • GCM = AES-CTR (confidentiality) + GHASH authentication tag computed over the ciphertext
  • Decrypt checks the tag first — any edited ciphertext byte → tag mismatch → reject
  • Attacker can't recompute a valid tag without the key — no equivalent of the CBC trick exists

Proving it empirically

  • Apply the same "flip a ciphertext byte" idea to a :8097 token
  • Result: 403, no flag — GCM rejects the tamper every time
  • The concrete difference: CBC silently accepts a coherent forged plaintext; GCM raises on decrypt
  • Q10's trap: GCM only helps if the app actually checks the tag and doesn't swallow the exception (an improper-verification bug, in the spirit of CWE-347)

The wider family of mode pitfalls

  • ECB — pattern leakage (Q1)
  • CBC, reused IV — reveals whether two messages share a prefix (Q2)
  • GCM, reused nonce — catastrophic: keystream and auth key both reused → plaintext XOR leaks and tags become forgeable (Q5/Q6)
  • Disk encryption (LUKS/BitLocker) uses AES-XTS, not GCM — no room for a per-sector nonce+tag, so it trades away authentication for a tweakable cipher (Q8)
  • Mobile/IoT: AES-GCM where hardware AES acceleration exists (x86 AES-NI, ARMv8 crypto ext.); ChaCha20-Poly1305 otherwise — avoids timing side channels without hardware accel (Q7)

🏁 Game — Flip Your Way to Admin

ServicePortSchemeTamperable?
vulnerable_app.py:8096AES-256-CBC, no MACYes
fixed_app.py:8097AES-256-GCM, AEADNo
  1. GET /login on :8096 → capture the token cookie
  2. XOR the 5-byte delta into token[21..25] — no key needed
  3. Replay against /admin → flag
  4. Try the identical trick on :8097 → rejected

Lab today

📋 Worksheet 4 — labs/week04-aes-modes/worksheet.md · kickoff: docker compose up -d

  • Part 1: Q1–Q10 written questions (ECB/CBC/GCM/XTS/nonces)
  • Part 2A: Tasks 0–5 — capture a token, locate the bytes, flip, cash in the flag, confirm GCM rejects, explain why
  • Part 2C — Audit the AI: an AI claims CBC's random IV + secret key means "the client can't change the role" — find the flaw, rewrite with AEAD, verify against the lab
  • Part 2D: EiPE + Prompt Problem · Part 2E: viva spot-check (3 questions, pass/fail gate)

Key takeaways

  • AES the cipher is textbook-secure — the mode is where real systems fail
  • Encryption alone is not integrity: unauthenticated CBC is malleable
  • AEAD (GCM / ChaCha20-Poly1305) binds confidentiality + integrity — but only if the tag is actually checked

Questions?

Next week: Key Exchanges — MITM-ing an unauthenticated Diffie-Hellman handshake

All weeks in Security & Cryptography