HIGH padding oracledjangohmac signatures

Padding Oracle in Django with Hmac Signatures

Padding Oracle in Django with Hmac Signatures — how this specific combination creates or exposes the vulnerability

A padding oracle attack can occur in Django when encrypted data is authenticated with HMAC signatures in a way that reveals whether padding is valid before the signature is verified. This typically arises when decryption and signature verification are performed in separate steps, or when error handling during unpadding leaks information to the attacker.

Consider a pattern where a cookie or API parameter contains an encrypted payload concatenated with an HMAC: encrypted || '.' || hmac. If the application first verifies the HMAC using a constant-time comparison and then decrypts, this order can be safe. However, if the application decrypts first and then verifies the HMAC, timing differences in the unpadding process can become observable: an invalid padding error raised during decryption can cause an early exception, allowing an attacker to infer that the padding was valid up to a certain point. Even when HMAC is used, if decryption errors (including padding errors) are surfaced with different messages or timing than signature failures, a padding oracle may still exist.

In Django, this can happen when using low-level cryptographic primitives such as cryptography or PyCrypto without careful error handling. For example, decrypting with AES in CBC mode and then calling .unpad() raises a ValueError on invalid padding. If this error is not caught and normalized into a generic failure response, an attacker can send modified ciphertexts and observe differences in response times or status codes to recover plaintext.

An insecure implementation might look like this:

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
import hmac
import hashlib
import os

key = os.urandom(32)
iv = os.urandom(16)
signature_key = os.urandom(32)

def encrypt_then_sign(plaintext, key, signature_key, iv):
    padder = padding.PKCS7(128).padder()
    padded_data = padder.update(plaintext) + padder.finalize()
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
    encryptor = cipher.encryptor()
    ciphertext = encryptor.update(padded_data) + encryptor.finalize()
    tag = hmac.new(signature_key, ciphertext, hashlib.sha256).digest()
    return ciphertext + tag

def decrypt_and_verify(token, key, signature_key, iv):
    ciphertext = token[:-32]
    received_tag = token[-32:]
    # No constant-time compare for HMAC; timing may leak
    if not hmac.compare_digest(received_tag, hmac.new(signature_key, ciphertext, hashlib.sha256).digest()):
        raise ValueError('Invalid signature')
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
    decryptor = cipher.decryptor()
    padded_plaintext = decryptor.update(ciphertext) + decryptor.finalize()
    # This unpad can raise ValueError on bad padding, potentially observable
    unpadder = padding.PKCS7(128).unpadder()
    plaintext = unpadder.update(padded_plaintext) + unpadder.finalize()
    return plaintext

In the above, if decryptor.update(...) + decryptor.finalize() produces invalid padding, the subsequent unpadder call raises an exception that may differ from a signature failure. An attacker can exploit these timing or error-message differences to mount a padding oracle, even though HMAC is used, because the padding is processed before signature verification and its errors are not uniformly handled.

Hmac Signatures-Specific Remediation in Django — concrete code fixes

To prevent padding oracle issues when using HMAC signatures in Django, ensure that decryption and verification are performed in a way that does not leak padding errors. Always verify the HMAC before decryption, and handle all exceptions uniformly so that error responses are consistent in timing and content.

Best practice is to verify the HMAC on the raw ciphertext first, then decrypt, and catch any padding errors during unpadding, converting them into generic authentication failures.

Here is a secure pattern using cryptography and hmac.compare_digest to avoid timing leaks:

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
import hmac
import hashlib
import os

key = os.urandom(32)
signature_key = os.urandom(32)

def encrypt_then_sign(plaintext, key, signature_key, iv):
    padder = padding.PKCS7(128).padder()
    padded_data = padder.update(plaintext) + padder.finalize()
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
    encryptor = cipher.encryptor()
    ciphertext = encryptor.update(padded_data) + encryptor.finalize()
    tag = hmac.new(signature_key, ciphertext, hashlib.sha256).digest()
    return ciphertext + tag

def decrypt_and_verify(token, key, signature_key, iv):
    ciphertext = token[:-32]
    received_tag = token[-32:]
    # Constant-time HMAC verification before decryption to avoid padding oracle
    if not hmac.compare_digest(received_tag, hmac.new(signature_key, ciphertext, hashlib.sha256).digest()):
        raise ValueError('Invalid signature')
    try:
        cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
        decryptor = cipher.decryptor()
        padded_plaintext = decryptor.update(ciphertext) + decryptor.finalize()
        unpadder = padding.PKCS7(128).unpadder()
        plaintext = unpadder.update(padded_plaintext) + unpadder.finalize()
        return plaintext
    except ValueError:
        # Normalize padding errors to a generic authentication failure
        raise ValueError('Invalid token')

Additional recommendations:

  • Use Django’s django.middleware.csrf and django.contrib.auth mechanisms where possible, as they handle signing and encryption with built-in protections.
  • If you rely on JWTs or custom tokens, prefer high-level libraries that implement authenticated encryption (e.g., Fernet) and avoid manual padding and HMAC concatenation.
  • Ensure that all branches of token validation return the same HTTP status codes and similar response shapes to prevent information leakage through timing or error messages.

Frequently Asked Questions

How can I test my Django endpoints for padding oracle risks using middleBrick?
Run a scan with middleBrick against your endpoint to surface timing and error-handling findings; review the detailed report for guidance on normalizing exceptions and constant-time verification.
Does the middleBrick CLI report padding oracle findings for Hmac-signed APIs?
Yes; the CLI performs black-box checks including input validation and error handling analysis, and it maps findings to relevant standards such as OWASP API Top 10.