CBC Bit-Flipping — controlled tampering with no oracle needed
CBC decryption computes p_i = D(c_i) ⊕ c_{i-1}, so flipping a bit in ciphertext block c_{i-1} flips the *same* bit in plaintext block p_i — no key, no oracle, just arithmetic.
The signal that gives it away
- You are handed a CBC-encrypted ciphertext (very often a cookie, session token, or serialized-state blob) and the application acts on the decrypted content — checks a field like admin=0, role=user, an ACL string, or a serialized struct — after decrypting it, without any integrity check (no MAC, no signature, no AEAD tag).
- You know or can guess part of the plaintext at a known offset (a field name, a fixed-format token). You don't need the whole plaintext, just the bytes you intend to change.
- There is no padding-error oracle behavior to look for here — that's the tell that distinguishes this from CBC Padding Oracle — leak the intermediate decryption value one byte at a time. If the app doesn't leak a distinguishable padding-valid/invalid signal but still trusts decrypted fields blindly, bit-flipping is the path, not the oracle.
How to exploit it
1. Locate the plaintext byte(s) you want to change and identify which ciphertext block precedes the target plaintext block (c_{i-1} for target block p_i), or the IV if the target is block 0 (p_0 = D(c_0) ⊕ IV).
2. Compute the delta and XOR it into the preceding ciphertext block at the matching offset:
c_{i-1}[j] ^= (known_pt[j] ^ desired_pt[j])
3. Accept the trade-off: block i-1 (the one you modified) decrypts to garbage. Place your edit where the collateral damage lands somewhere harmless — padding bytes, an ignored/unparsed field, or a block you already fully control as an attacker-supplied prefix. If you need clean control over an entire block's plaintext with no garbled neighbor, you need two adjacent blocks you control (garble one, target the next).
4. Flipping bits in the IV changes only the first plaintext block, with zero collateral damage elsewhere — the cleanest case when the field you want sits in block 0 and the IV is attacker-visible or attacker-supplied.
Minimal shape of the primitive:
```python def flip(prev_block, offset, known, desired): pb = bytearray(prev_block) for i, (k, d) in enumerate(zip(known, desired)): pb[offset + i] ^= k ^ d return bytes(pb)
# Conceptual example: turn ";role=user" into ";role=admn" (same length!) # by flipping the block/IV that precedes the "role=" plaintext block. forged_prev = flip(prev_block, offset_of_role_field, b"user", b"admn") ```
The same mechanic drives building a *chosen*-plaintext ciphertext when you already have a decryption oracle: set C' = D(C) ⊕ desired and chain the forged blocks backwards through the message — see CBC Padding Oracle — leak the intermediate decryption value one byte at a time for the fuller oracle-driven version of this idea.
CTR/GCM have the same underlying malleability on their keystream-XOR layer (c[i] ^= Δ flips the same plaintext bit — see CTR Nonce Reuse — when the counter mode keystream repeats), so "it's not CBC" does not mean "it's not bit-flippable." Only an authenticated mode (a verified MAC/AEAD tag checked *before* the plaintext is trusted) actually closes this off, not the choice of mode alone.
What does NOT work
- Editing without accounting for the garbled block. Every byte you flip in a ciphertext block corrupts the *entire* previous plaintext block it belongs to (since D(c_{i-1}) doesn't change, only the XOR input does — but the corrupted block is now nonsense once you re-XOR anything else against it downstream if it's not the true previous block anymore). Forgetting this and expecting the modified block itself to stay meaningful is the most common mistake — plan for exactly one sacrificial block per edit "site."
- Trying this against a MAC'd or AEAD ciphertext (GCM, encrypt-then-MAC, HMAC-wrapped CBC). The tampered ciphertext will fail authentication before decryption is even trusted; you get rejected, not a subtly-wrong plaintext. Check for integrity verification first — if it's there, this whole technique is a dead end and you need a different weakness (implementation bug in the MAC check, missing verification path, etc.), not more tampering.
- Assuming length must change. It doesn't — bit-flipping preserves ciphertext length exactly, which is exactly why it's stealthier than record-splicing tricks and easy to miss as a possibility if you're only looking for length anomalies.
- Confusing this with padding-oracle decryption. Bit-flipping only lets you *write* known deltas into plaintext you already partially know; it does not let you *read* unknown plaintext byte-by-byte. If your goal is recovering unknown plaintext rather than tampering with known fields, this is the wrong tool — reach for CBC Padding Oracle — leak the intermediate decryption value one byte at a time instead.
Related
Block Cipher Modes of Operation — the mode carries the CTF vulnerability, not the primitive CBC Padding Oracle — leak the intermediate decryption value one byte at a time CTR Nonce Reuse — when the counter mode keystream repeats ECB Detection & Cut-and-Paste — the identical-block signal and how to weaponize it
Verified against
18 claims checked against these sources
Source: Sinapsi — verified compositional memory, queryable by LLMs. Query this wiki live from your assistant over MCP, or build your own verified wiki (public, or private for your team). CC BY 4.0 — reuse with attribution to Sinapsi.