VAITP Dataset

← Back to the dataset

CVE-2026-69247

A Bleichenbacher-style oracle in PKCS7 decryption can lead to key recovery.

  • CVSS 8.2
  • 208
  • Cryptographic
  • Remote

cryptography is a package designed to expose cryptographic primitives and recipes to Python developers. From 44.0.0 until 50.0.0, pkcs7_decrypt_der, pkcs7_decrypt_pem, and pkcs7_decrypt_smime reported the outcome of decrypting a RecipientInfo's encryptedKey in several distinguishable ways, one of which disclosed the exact length recovered from the RSA operation. The same distinction was also observable by timing. An application that decrypts attacker-supplied EnvelopedData and reflects the outcome gives the attacker a Bleichenbacher oracle against the content-encryption key. Decryption ran as RSA PKCS#1 v1.5 decrypt of encryptedKey, build an AES cipher from the result, then AES-CBC decrypt and PKCS#7 unpad. Invalid RSA padding, a valid padding with a bad key length, a correct length with a wrong key, and the real key each failed or succeeded differently. Case 1 is reachable only where the linked library lacks implicit rejection: OpenSSL 3.0 and 3.1, LibreSSL, and BoringSSL. Exploitation requires a service that auto-decrypts untrusted EnvelopedData matching the victim certificate and answers adaptively at high volume, such as an S/MIME gateway or mail filter. This issue is fixed in 50.0.0.

CWE
208
CVSS base score
8.2
Published
2026-08-03
OWASP
A02 Cryptographic Failures
Orthogonal defect classification
Checking
Code defect classification
Incorrect Algorithm
Category
Cryptographic
Subcategory
Cryptographic Implementation Error
Accessibility scope
Remote
Impact
Information Disclosure
Affected component
cryptography
Fixed by upgrading
Yes

Solution

Upgrade cryptography to version 50.0.0 or later.

Vulnerable code sample

from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding as sym_padding
from cryptography.exceptions import InvalidTag

_SYMMETRIC_KEY_SIZE = 32

def _decrypt_recipient_key(private_key, encrypted_key, data, iv):
    try:
        # VULNERABLE: Raises a specific error if RSA padding is invalid, creating a distinguishable failure case for a Bleichenbacher oracle.
        decrypted_key = private_key.decrypt(
            encrypted_key,
            padding.PKCS1v15()
        )
    except ValueError:
        # This early exit creates an observable difference in behavior and timing.
        raise ValueError("RSA decryption failed")

    if len(decrypted_key) != _SYMMETRIC_KEY_SIZE:
        # A second distinguishable failure mode based on key length.
        raise ValueError("Invalid symmetric key length")

    cipher = Cipher(algorithms.AES(decrypted_key), modes.CBC(iv))
    decryptor = cipher.decryptor()
    try:
        padded_plaintext = decryptor.update(data) + decryptor.finalize()
        unpadder = sym_padding.PKCS7(algorithms.AES.block_size).unpadder()
        plaintext = unpadder.update(padded_plaintext) + unpadder.finalize()
    except (ValueError, InvalidTag):
        # A third distinguishable failure mode from the symmetric decryption.
        raise ValueError("Symmetric decryption failed")

    return plaintext

Patched code sample

import os
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding as sym_padding
from cryptography.exceptions import InvalidTag

_SYMMETRIC_KEY_SIZE = 32

def _decrypt_recipient_key(private_key, encrypted_key, data, iv):
    decrypted_key = None
    try:
        decrypted_key = private_key.decrypt(
            encrypted_key,
            padding.PKCS1v15()
        )
    except ValueError:
        # Don't raise, to prevent distinguishing padding errors.
        pass

    # FIX: A randomly generated key is used if RSA decryption or length validation fails, making all error paths computationally indistinguishable.
    is_key_valid = (
        decrypted_key is not None and len(decrypted_key) == _SYMMETRIC_KEY_SIZE
    )
    key_to_use = decrypted_key if is_key_valid else os.urandom(_SYMMETRIC_KEY_SIZE)

    cipher = Cipher(algorithms.AES(key_to_use), modes.CBC(iv))
    decryptor = cipher.decryptor()
    try:
        padded_plaintext = decryptor.update(data) + decryptor.finalize()
        unpadder = sym_padding.PKCS7(algorithms.AES.block_size).unpadder()
        plaintext = unpadder.update(padded_plaintext) + unpadder.finalize()
    except (ValueError, InvalidTag):
        raise ValueError("Decryption failed")

    if not is_key_valid:
        # Ensure failure even if the random key miraculously worked.
        raise ValueError("Decryption failed")

    return plaintext

Payload

import base64

# This represents a single probe payload for a Bleichenbacher-style attack.
# It is a syntactically valid S/MIME message containing a PKCS7 EnvelopedData structure.
# The 'encryptedKey' within the RecipientInfo has been manipulated by an attacker.
# An exploit involves sending thousands of variations of this payload and observing
# the server's distinct error responses or timing differences to incrementally
# decrypt a target message's content-encryption key.

b64_payload = "MIAGCSqGSIb3DQEHA6CAMIACAQAxggJAMIIC/AIBADCBnDCBlDELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pc3NvdXJpMRIwEAYDVQQHDAlTYW5kYm94Q0ExGTAXBgNVBAoMEFByb2JlIE9yZ2FuaXphdGlvbjEgMB4GA1UEAwwXd3d3LnByb2JlLmV4YW1wbGUuY29tMScwJQYJKoZIhvcNAQkBFhh2aWN0aW1AcHJvYmUuZXhhbXBsZS5jb20CAQEwDQYJKoZIhvcNAQEBBQAEgYA5M9+5pkCP2p33f5yYoTce8j92I6KbtAZqjGvjv4uWq5kGsVAxtup525agIaxpSkvxm2pPMr3uJot3bgyyok9G5Ayv2bVIMs4dLw8k2X8L7pZ0+DmjUvAcpA6j8oA2VfYce2d7C3SCtJGgnc9L+xKj+wKFy1GZGUXnI0DMYwOwggEIBgkqhkiG9w0BBwEwggEFAgEEMC3g8uC3j2X+GRy2AlA/i107vUPZ9x7J2G2I3q3qVtB9JpZ3A7AFAgECgIQwGAYJKoZIhvcNAQEIMAsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMjYwMTE1MDIwMzI0WjAjBgkqhkiG9w0BCQQxFgQU+gT4rO/NnUf7JjHqCg+tC73+xjgwga0GCSqGSIb3DQEHA0EARL5Uv8G9FzYpM3p4yv2PG8z6y7Q+4nZG/K4Jp2V8eJ8t3d5e2e8yJ9r7e+e4q3D5t8B/e9t9D4g9b7x6z5A4c5f3e/d6B+g2h/c8a+f8g7b6d5e4g3h2f1t8v7c6a+e5g4i3f2s7w5c+d6g4h3j2s7v5c+d6g4h3j2s7v5c+d6g4h3j2s7v5c+d6g4h3j2s7v5c+d6g4h3j2s7v5c+d6g4h3j2s7v5c+d6g4h3j2s7v5c+d6g4h3j2g=="

smime_message = f"""Content-Type: application/pkcs7-mime; smime-type=enveloped-data; name="smime.p7m"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="smime.p7m"

{b64_payload}
"""

print(smime_message)

Cite this entry

@misc{vaitp:cve202669247,
  title        = {{A Bleichenbacher-style oracle in PKCS7 decryption can lead to key recovery.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-69247},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-69247/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::