Vulnerability

Padding Oracle Attack
detect, understand, remediate

A padding oracle attack lets an attacker decrypt or forge ciphertext one byte at a time by submitting modified ciphertexts and observing whether the server reports a padding error or another distinguishable response. The cipher is real, the key is secret, but the server still leaks plaintext because it discloses, through error messages or timing, whether the decrypted padding was valid. The bug is not the encryption; it is the oracle.

No credit card required. Free plan available forever.

Severity

High

CWE ID

CWE-209

OWASP Top 10

A02:2021 - Cryptographic Failures

CVSS 3.1 Score

7.4

What is a padding oracle attack?

A padding oracle attack (CWE-209, related to CWE-310 and CWE-326) is the recovery of plaintext, or the forgery of ciphertext, by repeatedly submitting modified ciphertexts to a server that reveals whether the decrypted padding was valid. The attacker never recovers the key. They do not need to. Each crafted request peels one byte of plaintext, or each crafted block teaches the attacker how to construct a ciphertext that decrypts to a chosen plaintext. The vulnerability lives in the response channel, not in the cipher.

The class spans both transport-layer breaks (POODLE in SSL 3.0, BEAST against TLS 1.0 CBC, Lucky 13 against TLS-CBC implementations) and application-layer breaks (encrypted cookies, viewstates, tokens, and configuration blobs that are decrypted with CBC and unauthenticated). The same shape (a server that distinguishes a padding error from any other error) underpins the Bleichenbacher attack on RSA PKCS#1 v1.5 padding used in some TLS handshakes and JWE implementations.

Padding oracle attacks sit alongside but are distinct from weak cryptography, which covers the choice of primitive, mode, or key length. They are also distinct from TLS/SSL misconfiguration, which covers transport-layer settings rather than the application-layer cipher contract. This page focuses on the missing-authentication shape: ciphertext that decrypts without verifying integrity, paired with a response channel that leaks whether the decrypted bytes were structurally valid.

How a padding oracle attack works in practice

1

Identify the oracle

The attacker finds a parameter that decrypts as CBC ciphertext and produces a distinguishable response when padding is invalid: a 500 error vs a 200, a different error message, a different response time, or a different cookie behaviour.

2

Craft modified ciphertexts

For each target ciphertext block, the attacker prepends a controlled IV block and flips bytes one position at a time. Each request asks the oracle whether the modified block decrypts to valid PKCS#7 padding.

3

Recover plaintext byte by byte

A 256-request loop per byte is enough to identify the byte that produces valid padding. XOR mathematics on the modified IV recovers the plaintext byte. The attacker walks through every block to recover the full message.

4

Forge ciphertext that decrypts to chosen text

Once the attacker controls the relationship between IV bytes and plaintext bytes, they can construct ciphertexts that decrypt to specific values: an admin role flag, a different user id, an extended expiry, or a forged authentication claim.

The most common padding oracle findings on a pentest

The patterns below cover the majority of padding oracle findings that show up on web, API, and mobile engagements. Each one combines an unauthenticated cipher mode with a response channel that distinguishes padding errors from any other error.

Encrypted session cookies in CBC mode without a MAC

A cookie holds AES-CBC ciphertext containing user id, role, and expiry. The server returns 500 with a generic message when padding is invalid and 200 when padding is valid but the inner JSON parse fails. The two responses are distinguishable; the cookie is decryptable.

Encrypted query string parameters or viewstates

A reset link, a download token, or an ASP.NET-style viewstate carries CBC ciphertext in a URL parameter. The server reveals padding errors through HTTP status codes, redirect targets, or error pages. The parameter becomes a one-byte-at-a-time decryption oracle.

Custom token formats with prepended IV and CBC payload

Hand-built tokens that concatenate a random IV with AES-CBC ciphertext, then store the pair in a header or cookie, are vulnerable when the server deserialises the inner plaintext without first verifying integrity. The tester crafts requests that flip bytes and observes the inner parser response.

JWE with RSA1_5 key wrapping (Bleichenbacher)

JSON Web Encryption with RSA1_5 wrapping has been deprecated for years precisely because PKCS#1 v1.5 RSA padding leaks information through implementations that distinguish padding errors. JWE libraries that still accept the algorithm and report distinguishable errors expose a Bleichenbacher oracle.

Lucky 13 style timing oracles in TLS-CBC implementations

TLS-CBC ciphersuites have been deprecated for several reasons; Lucky 13 is one. Implementations that branch on padding validity at the byte level produce timing differences that leak plaintext over thousands of measurements. Modern stacks use AEAD ciphersuites; legacy stacks still ship with CBC enabled.

POODLE on SSL 3.0 fallback

The original POODLE attack exploited SSL 3.0's loose CBC padding contract during downgrade fallbacks. Servers that still accept SSL 3.0 connections, or accept a downgrade attempt without TLS_FALLBACK_SCSV, expose the same surface as POODLE described.

Encrypted file imports without authentication

Configuration files, license blobs, encrypted backups, and inter-service messages that use CBC without a MAC and report different errors based on padding state become offline padding oracles. The attacker submits modified files and watches the import process error stream.

Unauthenticated CTR mode treated as a substitute

Switching from CBC to CTR removes the padding oracle but leaves a different malleability vulnerability. CTR ciphertext can be flipped bit by bit; if the inner plaintext is parsed without authentication, the attacker can still rewrite individual fields. The fix is authenticated encryption, not a different unauthenticated mode.

A worked example: a CBC-encrypted session cookie

A common shape on engagements is a session cookie that encrypts a JSON blob with AES-CBC and ships it back to the client. The application decrypts on every request to read the embedded user id and role. There is no MAC and the server reports padding errors with a different status code than the inner JSON parse error.

// Vulnerable: AES-CBC, no authentication, distinguishable errors
function readSession(cookieB64: string) {
  const iv = Buffer.from(cookieB64, 'base64').slice(0, 16);
  const ct = Buffer.from(cookieB64, 'base64').slice(16);
  const decipher = createDecipheriv('aes-256-cbc', key, iv);
  let pt: Buffer;
  try {
    pt = Buffer.concat([decipher.update(ct), decipher.final()]); // PKCS#7 padding check
  } catch {
    throw new HttpError(500, 'session_padding_invalid');         // distinguishable signal
  }
  return JSON.parse(pt.toString());                              // distinct error if JSON fails
}

The pentester captures a valid session cookie, splits it into IV plus ciphertext, and writes a script that submits modified cookies one byte at a time. For each target byte, 256 requests is enough to find the value that produces a 200 (or any non-padding error). Each successful guess teaches the attacker one plaintext byte through XOR with the modified IV. After roughly 256 times the message length requests, the full plaintext is recovered. The same primitive lets the attacker forge a cookie whose plaintext decodes to { "uid": "...", "role": "admin" }, replay it against the server, and authenticate as admin without the secret key.

On a SecPortal engagement, this kind of cookie shape shows up first as a static analysis hit on the unauthenticated CBC pattern, then promotes to a manual finding once the tester confirms the oracle by sending two malformed cookies with different padding states and observing distinguishable responses. The proof of concept is the recovered plaintext, paired with a forged ciphertext whose decryption produces a chosen field value. Both pieces stay attached to the finding so the retester can reproduce the attack against the fix.

How to detect padding oracle vulnerabilities

Automated detection

  • SecPortal's code scanning runs Semgrep with rulesets that flag CBC mode usage without a paired MAC, calls to createDecipheriv with aes-*-cbc that are not wrapped by an HMAC verify, and JWE configurations that accept RSA1_5 wrapping.
  • The external scanner inspects the TLS handshake for CBC ciphersuites, SSL 3.0 acceptance, and missing TLS_FALLBACK_SCSV. These are flagged with severity, the underlying attack reference, and remediation guidance.
  • The authenticated scanner probes encrypted cookies and tokens for distinguishable responses: status code differences, response length differences, and timing differences between malformed ciphertext that fails padding versus malformed ciphertext that fails inner parsing.
  • SCA dependency scanning identifies older versions of cryptographic libraries (older Bouncy Castle, jose, jsonwebtoken, and language-bundled crypto packages) where padding oracle weaknesses have been published as CVEs and patched in later releases.

Manual testing

  • Inventory every place the application decrypts client-supplied ciphertext: cookies, headers, query parameters, viewstates, tokens, file imports, and inter-service messages. Each is a candidate oracle until proven authenticated.
  • Submit two malformed ciphertexts that differ only in padding validity. If the server returns different status codes, different error messages, or different response times, the oracle exists. Tools like padbuster, PadOracle.py, and Burp Suite extensions automate the byte-flip walk once the oracle is confirmed.
  • For TLS endpoints, use sslyze or testssl.sh to enumerate CBC ciphersuites, SSL 3.0 acceptance, and POODLE indicators. Cross-reference the result with the application stack version to determine whether the implementation is patched against Lucky 13 timing variants.
  • For JWE tokens, check the alg header. RSA1_5 (PKCS#1 v1.5 RSA wrapping) is a Bleichenbacher candidate. Submit malformed ciphertexts that decrypt to invalid PKCS#1 v1.5 structures and observe whether the error response distinguishes padding failures from other errors.
  • Inspect any custom token format. If the structure is iv plus ciphertext concatenated without an integrity tag and the inner payload is a structured format (JSON, XML, protobuf), the oracle is the inner parser. Distinguishable parse errors versus padding errors are enough to drive the attack.

How to fix padding oracle vulnerabilities

Switch to authenticated encryption (AEAD)

AES-GCM, ChaCha20-Poly1305, and XChaCha20-Poly1305 verify integrity as part of the decryption operation. A modified ciphertext fails the integrity check before any padding logic runs, and the server returns a single uniform error. The padding oracle disappears because the cipher contract no longer separates "padding invalid" from "ciphertext invalid".

If CBC is mandatory, pair it with a separate HMAC

When AEAD is not an option (legacy compatibility, hardware constraints), use AES-CBC with a separate HMAC-SHA256 under a separate key, applied in encrypt-then-MAC order. The verifier checks the HMAC first; if it fails, the response is uniform and the CBC decryption never runs. Watch for constant-time comparison on the MAC; a timing-leaky comparison reintroduces an oracle.

Return uniform errors regardless of decryption failure mode

Whether the failure is padding invalid, MAC invalid, or inner parse failure, the response should be the same status code, the same error body, and ideally the same response time. Distinguishable responses on any of those three axes are enough to drive the attack. Logging can record the underlying cause; the response cannot.

Bind associated data into the cipher

AEAD ciphers accept associated data (AAD) that is authenticated but not encrypted. Bind context (user id, expiry, key version, purpose) into the AAD so a ciphertext valid for one user cannot be replayed against another. The integrity tag covers both the ciphertext and the AAD; tampering with either fails verification.

Reject deprecated TLS ciphersuites and downgrade paths

Disable SSL 3.0, TLS 1.0, and TLS 1.1. Disable CBC ciphersuites and require AEAD ciphersuites (AES-GCM, ChaCha20-Poly1305). Enable TLS_FALLBACK_SCSV to prevent downgrade attacks against clients that support newer protocols. Modern TLS libraries default to safe configurations; legacy deployments require explicit hardening.

Migrate JWE configurations off RSA1_5

Configure JWE libraries to accept only AEAD-based wrapping algorithms (RSA-OAEP for asymmetric, A128KW/A256KW for symmetric, ECDH-ES for key agreement). Reject RSA1_5 explicitly rather than relying on the default. Older library versions accept RSA1_5 by default; verify the configuration rather than assuming the default.

Plan for crypto-agility on stored ciphertexts

Switching the algorithm in code does not retroactively re-encrypt old data. Store an algorithm identifier and a key version alongside each ciphertext so the system can decrypt legacy formats during migration and re-encrypt under the new scheme. The fix is incomplete until legacy CBC ciphertexts have been re-encrypted under AEAD and the legacy decryption path has been removed.

Reporting a padding oracle finding

Padding oracle findings are easy to dispute when the report stops at "application uses CBC without a MAC". Engineering pushes back that the encryption is in place, the key is rotated, and no key has leaked. The strongest reports name the exact endpoint, show the two distinguishable responses (padding failure vs other failure), include the recovered plaintext from a worked decryption, and demonstrate a forged ciphertext that decrypts to a chosen value when replayed against the server.

On a SecPortal engagement, the finding sits on the engagement record with the affected endpoint, the CVSS 3.1 vector calibrated to the data the cipher was protecting, the CWE-209 mapping, the request and response evidence for the oracle distinction, and the remediation guidance from this page. Pentest engagement records keep the proof of concept, the recovered plaintext sample, and the forged ciphertext sample attached, so the retest verification references the same artefacts that proved the original break. The finding triage workflow covers how to separate scanner-derived flags (a CBC import in source) from manually validated findings (a confirmed oracle on a live cookie) so the report differentiates rule hits from confirmed exploits.

Compliance impact

A pentester checklist for padding oracle attacks

The list below is the minimum coverage a tester should walk before declaring a target's encrypted parameter surface acceptable. Each item maps to a specific finding shape, with a CVSS profile that reflects the data the cipher was protecting.

  • Inventory every encrypted client-supplied input: cookies, headers, query parameters, body fields, viewstates, tokens, file imports, and inter-service messages. Treat each one as a candidate oracle until proven authenticated.
  • For each encrypted input, submit two malformed ciphertexts that differ only in padding validity. Compare status code, response length, response body, and response time. Any distinguishable difference is the oracle.
  • For confirmed oracles, run padbuster or an equivalent script to recover plaintext from one block. Document the request count per byte, the total request count for the recovery, and any rate-limiting that influenced the run.
  • Construct a forged ciphertext that decrypts to a chosen value (a different user id, an admin role, an extended expiry). Replay the forged value against the server and capture the resulting authentication or authorisation outcome as the proof of impact.
  • For TLS endpoints, run sslyze or testssl.sh to enumerate CBC ciphersuites, SSL 3.0 acceptance, BEAST mitigations, Lucky 13 indicators, and POODLE downgrade exposure. Cross-reference the implementation version against published patches.
  • For JWE tokens, decode the alg header and reject the token if the value is RSA1_5. Probe the verifier with malformed RSA-wrapped keys and observe whether padding errors are distinguishable from other errors.
  • Record the CVSS vector calibrated to the data being protected (high confidentiality and integrity for cookies that authorise actions; lower if the cipher protects only opaque session metadata), the CWE-209 mapping, and the migration plan covering algorithm switch, key rotation, and re-encryption of stored ciphertexts.

Catch padding oracles before ciphertext leaks

SecPortal probes encrypted cookies and tokens for distinguishable error responses, flags unauthenticated CBC patterns in source, and keeps the request and response evidence attached to the finding through retest. Start free.

No credit card required. Free plan available forever.