Cabecera Penligente

CVE-2026-69247: Python cryptography PKCS#7 Bleichenbacher Oracle

CVE-2026-69247 is a cryptographic side-channel vulnerability in the Python cryptography package’s PKCS#7 EnvelopedData decryption support. It affects cryptography versions from 44.0.0 up to, but not including, 50.0.0. The flaw was fixed in version 50.0.0, released on July 31, 2026. The affected entry points are pkcs7_decrypt_der, pkcs7_decrypt_pemy pkcs7_decrypt_smime.

The important part of CVE-2026-69247 is not simply that “an error leaks information.” Before the fix, different outcomes while unwrapping the PKCS#7 RecipientInfo.encryptedKey could become distinguishable through exception behavior and execution time. One path even exposed the exact length recovered from the RSA operation. In an application that repeatedly decrypts attacker-controlled EnvelopedData, these distinctions can provide the feedback required for a Bleichenbacher-style adaptive chosen-ciphertext attack against the content-encryption key, or CEK.

That does no mean every Python program importing cryptography suddenly exposes its RSA private key. Exploitation requires a much narrower environment: an automated service must accept attacker-controlled PKCS#7 or S/MIME messages, select the victim certificate and private key, perform decryption repeatedly, and return enough distinguishable information for an attacker to adapt later queries. The crypto backend also matters. The pyca advisory specifically notes that one critical RSA-padding failure path is directly reachable on backends without implicit rejection, including OpenSSL 3.0 and 3.1, LibreSSL, and BoringSSL, while OpenSSL 3.2 and later already provide an important lower-level mitigation.

The practical response is nevertheless straightforward: applications using an affected release should move to cryptography 50.0.0 or later. The harder task is determining which deployments deserve emergency treatment because they expose a remotely queryable decryption oracle rather than merely containing the vulnerable dependency.

CVE-2026-69247 at a Glance

ArtículoDefender-relevant detail
CVECVE-2026-69247
Componentepyca cryptography
Vulnerable versions>= 44.0.0, < 50.0.0
Versión corregida50.0.0
Affected APIspkcs7_decrypt_der, pkcs7_decrypt_pem, pkcs7_decrypt_smime
Clase de vulnerabilidadBleichenbacher-style decryption oracle
WeaknessesCWE-208 Observable Timing Discrepancy and CWE-209 sensitive error information
Primary asset at riskConfidentiality of the RSA-wrapped content-encryption key and therefore protected message content
Authentication required by the vulnerable libraryNone inherent to the vulnerable operation
Important prerequisitesAttacker-controlled EnvelopedData, automated decryption, victim certificate match, adaptive high-volume queries, observable response differences
CVSS 4.0 CNA score8.2 High
Main correctionActualizar a cryptography >= 50.0.0

The official CVE record assigns CVSS 4.0 score 8.2 with network attack vector, high attack complexity, attack requirements present, no privileges required, no user interaction, and high confidentiality impact. The record also identifies CWE-208 and CWE-209.

Readers may encounter different severity labels elsewhere. The pyca GitHub advisory currently labels the issue Moderate, while GitLab’s advisory database publishes a CVSS 3.1 score of 5.9 Medium. Those numbers are not necessarily evidence that one source is wrong: they use different CVSS generations and place significant weight on the unusual exploitation prerequisites.

For operational prioritization, the score matters less than reachability. An internet-facing automated S/MIME gateway that decrypts arbitrary inbound messages with a long-lived RSA key is a fundamentally different risk from a developer laptop that happens to have cryptography==49.0.0 installed but never calls any PKCS#7 decryption API.

What PKCS#7 EnvelopedData Is Protecting

Understanding CVE-2026-69247 requires separating several cryptographic layers that are often compressed into the phrase “PKCS#7 encryption.”

CMS, the Cryptographic Message Syntax standardized in RFC 5652, provides container formats for signed, encrypted, authenticated, and other cryptographically processed content. EnvelopedData is the CMS structure commonly associated with sending encrypted content to one or more recipients. Instead of encrypting an entire large message directly with RSA, the sender generates a symmetric content-encryption key, encrypts the content with that key, and then separately protects the CEK for each recipient. RFC 5652 defines RecipientInfo structures and an encryptedKey field containing the protected content-encryption key.

A simplified RSA key-transport flow looks like this:

How PKCS#7 EnvelopedData Protects the Content Encryption Key

RFC 3218, written specifically to address the “Million Message Attack” against CMS implementations using RSA PKCS#1 v1.5 key transport, notes that the object being RSA-protected in CMS is a randomly generated symmetric CEK. The document describes CEKs in the historical CMS context as typically ranging from 8 to 32 bytes depending on the chosen symmetric cipher.

This architecture creates an important distinction between two kinds of padding.

RSA PKCS#1 v1.5 padding belongs to the RSA operation used to wrap the CEK.

PKCS#7 block padding belongs to the symmetric CBC encryption of the actual message content.

CVE-2026-69247 primarily concerns information leaked while processing the first category: the RSA-wrapped encryptedKey. The pyca advisory separately warns that ordinary unauthenticated EnvelopedData can also expose a second, CBC-level padding oracle under the wrong application behavior. Treating those two problems as interchangeable obscures both the fix and the residual risk.

How the Vulnerable cryptography Decryption Path Worked

Basic PKCS#7 decryption support was added to cryptography 44.0.0 on November 27, 2024. The release introduced the DER, PEM, and S/MIME decryption functions that later became the affected interfaces.

Before version 50.0.0, the relevant decryption sequence could be summarized as:

CVE-2026-69247 Oracle Path vs cryptography 50.0.0 Fix

The security issue emerged because failures at those stages did not collapse into one indistinguishable execution path.

According to the pyca security advisory, the implementation could expose four meaningfully different outcomes. Invalid RSA PKCS#1 v1.5 padding produced one decryption failure. A successfully recovered byte sequence of the wrong length caused AES key construction to fail and could disclose the recovered length. A key with the correct length but wrong value could progress to AES-CBC processing before failing at PKCS#7 unpadding. A correct CEK produced the plaintext.

Conceptually:

Probe A
encryptedKey
   |
   +--> RSA padding invalid
            |
            +--> early decryption error


Probe B
encryptedKey
   |
   +--> RSA padding appears valid
            |
            +--> CEK has wrong length
                     |
                     +--> invalid AES key size


Probe C
encryptedKey
   |
   +--> RSA padding appears valid
            |
            +--> CEK has expected length
                     |
                     +--> AES runs
                              |
                              +--> content padding fails


Legitimate message
   |
   +--> valid RSA unwrap
            |
            +--> correct CEK
                     |
                     +--> successful plaintext

There are two channels here.

The first is an explicit error channel. If the application maps these exceptions into distinguishable HTTP status codes, JSON error bodies, SMTP responses, queue results, audit events visible to a client, or another observable result, the attacker learns which internal branch occurred.

The second is a timing channel. Even if an application replaces every exception string with "decryption failed", a wrong-length CEK can terminate before the system performs AES-CBC decryption and unpadding. A correct-length wrong key does more work. When content size is attacker-controlled, the timing gap can grow with the amount of work skipped or performed. The pyca pull request explicitly identifies this timing distinction as part of the problem.

That is why cryptographic error handling is not solved by cosmetic string replacement.

A decryption endpoint can emit the same message every time and still leak a useful signal if internal paths do measurably different work.

Why One Bit of Feedback Can Be Dangerous

Bleichenbacher’s 1998 attack demonstrated an adaptive chosen-ciphertext attack against systems using RSA PKCS#1 v1.5 encryption. The remarkable property of the attack is that the oracle does not need to reveal plaintext, a key, or even a detailed parsing error. It can be enough for the target to answer a much narrower question about whether a modified ciphertext decrypts into a value satisfying a particular PKCS#1 formatting condition.

The attack is adaptive. A response to one probe influences the next probe.

That changes the security significance of tiny leaks. A single error difference looks harmless when viewed as a standalone request. Thousands or hundreds of thousands of carefully related requests can turn the same difference into a cryptographic measurement interface.

RFC 3218 describes this exact threat in the CMS context. An attacker starts with an RSA ciphertext and transforms it into related ciphertexts, sending them to a recipient that performs the private-key operation. The attacker then learns whether the resulting plaintext appears correctly formatted. By accumulating those answers, the range of possible original plaintext values can be progressively reduced. The RFC notes that the classic Million Message Attack operationally requires a very large number of requests and therefore is principally relevant to automated recipients rather than human-operated workflows.

This leads to a better mental model for CVE-2026-69247:

Attacker does not ask:

"Give me your RSA private key."

Attacker asks repeatedly:

"Please decrypt this slightly modified encryptedKey."

Victim answers indirectly:

"This failed immediately."
"This looked structurally different."
"This reached AES processing."
"This had a different recovered length."
"This took measurably longer."

Those answers become an oracle.

The private key remains inside the target. The vulnerability is that the target performs private-key operations on attacker-selected ciphertexts and leaks information about the result.

For CVE-2026-69247, the intended prize is the content-encryption key protected by RSA key transport. Recovering the CEK can expose the content encrypted under that key. The CNA accordingly assigns high confidentiality impact while assigning no direct integrity or availability impact in its CVSS 4.0 vector.

Exploitability Depends on More Than cryptography Version

Software composition analysis is useful for finding CVE-2026-69247, but a dependency match is only the first question.

The official advisory gives unusually specific exploitation requirements. The target needs to be a service that automatically decrypts untrusted EnvelopedData, uses the victim certificate and key, and responds adaptively at high volume. The advisory gives an S/MIME gateway or mail filter as representative examples.

A practical exposure analysis can therefore be divided into stages.

EscenarioQuestionWhat a positive result means
Dependency presenceEs cryptography >=44,<50 installed?Package is in the affected range
Code presenceDoes the code call a vulnerable PKCS#7 decrypt API?Vulnerable functionality may be used
ReachabilityCan untrusted input reach that API?Attacker influence becomes plausible
Key useDoes processing invoke a sensitive RSA private key?A useful oracle target exists
AutomatizaciónCan a client cause repeated decryptions without human intervention?Adaptive querying may be practical
ObservabilityCan the client distinguish errors, outputs, status, or timing?Oracle feedback may exist
Backend conditionDoes the crypto backend expose the relevant RSA failure distinction?Practical exploitability increases
Rate feasibilityCan enough probes be made without throttling or blocking?Large-query attack becomes more realistic

A scanner usually answers only the first row.

A useful security review needs to answer the rest.

The OpenSSL 3.2 implicit-rejection nuance

The backend distinction is especially important.

The pyca advisory states that the invalid-RSA-padding case is directly reachable only where the linked cryptographic library does not provide implicit rejection, naming OpenSSL 3.0, OpenSSL 3.1, LibreSSL, and BoringSSL. With OpenSSL 3.2 and later, invalid RSA PKCS#1 v1.5 padding in the default provider does not simply return the traditional padding-check error. Instead, OpenSSL produces a synthetic plaintext value to make the application-visible result harder to distinguish.

OpenSSL documents this behavior as a specific defense against Bleichenbacher attacks. Its current RSA documentation says that since OpenSSL 3.2.0, the default provider uses implicit rejection for PKCS#1 v1.5 decryption and generates a pseudorandom result when padding validation fails.

That matters because many users install cryptography from official binary wheels rather than building it against an arbitrary system library. The cryptography 44.0.0 changelog states that its Windows, macOS, and Linux wheels were built with OpenSSL 3.4.0, already newer than the 3.2 implicit-rejection boundary.

Therefore, the following statement is too simplistic:

Every installation of cryptography 44 through 49 exposes the same remotely exploitable Bleichenbacher oracle.

The official data does not support that conclusion.

A more accurate statement is:

Versions 44.0.0 through 49.x contain the vulnerable PKCS#7 error-handling implementation, but whether an application exposes the complete practical oracle described by the advisory depends on its runtime backend and application behavior.

This distinction can explain why a dependency scanner reports CVE-2026-69247 on a service while a controlled runtime investigation fails to reproduce the decisive RSA-padding distinction.

It does no mean teams should stay on an affected version. Version 50.0.0 moves the mitigation into the cryptography PKCS#7 layer itself instead of depending on backend behavior. That produces a safer and more portable security property.

What OpenSSL Implicit Rejection Actually Changes

Traditional RSA PKCS#1 v1.5 decryption APIs have a dangerous failure mode: malformed padding can produce an error while properly padded data produces plaintext. If the caller exposes that difference, the caller has built the fundamental primitive required by a Bleichenbacher oracle.

Implicit rejection changes the contract.

Instead of:

bad RSA padding
      |
      v
explicit failure

the cryptographic provider behaves more like:

bad RSA padding
      |
      v
generate synthetic result
      |
      v
continue through caller logic

OpenSSL’s documentation describes its implementation as returning a deterministic pseudorandom plaintext for padding failures by default, specifically so application code does not have to implement all side-channel-resistant handling itself. It also warns that callers using providers without implicit rejection remain responsible for side-channel-safe handling.

This design follows a much older protocol lesson.

RFC 3218 recommends treating malformed PKCS#1 messages as though they had produced a plausible CEK. Instead of immediately returning a unique RSA formatting error, the recipient substitutes a random CEK of the expected length and continues processing. Eventually the message fails in the same general way that a correctly formatted RSA block containing the wrong CEK would have failed. The RFC specifically notes that this strategy also helps prevent timing distinctions because the failure paths perform roughly the same work.

There is an important operational warning here: implicit rejection at the crypto-provider layer does not authorize the application to recreate a new oracle higher in the stack.

Consider:

Crypto backend:
    all RSA padding failures look the same

Application:
    "wrong recipient key" -> HTTP 400
    "AES decrypt failed"  -> HTTP 422
    "padding invalid"     -> HTTP 500

The application has just reintroduced distinguishability.

Cryptographic side-channel resistance is an end-to-end property. The network-visible behavior matters, not just the exception raised by one function.

How cryptography 50.0.0 Fixes CVE-2026-69247

En cryptography 50.0.0 release notes state that the PKCS#7 decryption functions no longer expose distinguishable errors or timing while unwrapping a RecipientInfo.encryptedKey, and that a random key is substituted on failure according to RFC 3218.

The patch changes the flow in an important way.

Before the private key is used, the implementation resolves the content-encryption algorithm. That tells it what CEK length should be expected.

It then prepares a random key of exactly that length.

If RSA decryption succeeds and produces a key of the correct length, the real candidate key can be used. If RSA decryption fails in the attacker-controlled way, or if the returned CEK has the wrong length, the implementation substitutes the random key instead. AES construction and content processing then continue using a correctly sized value.

The patched pattern is roughly:

Parse content-encryption algorithm
            |
            v
Determine expected CEK length
            |
            v
Generate random substitute CEK
            |
            v
Attempt RSA PKCS#1 v1.5 unwrap
       /                \
 success                attacker-controlled failure
   |                              |
   v                              v
Check length                use random CEK
   |
   +---- wrong length ------------+
   |                               |
correct                            |
   |                               |
   +---------------+---------------+
                   |
                   v
        construct symmetric cipher
                   |
                   v
          decrypt content
                   |
                   v
             same work

The implementation diff shows exactly this strategy: generate random bytes for the required key size, perform the private-key decrypt, use the recovered key only when its length matches the expected key size, and otherwise substitute the random key. Attacker-controlled ValueError cases are deliberately kept from becoming an observable early exit.

This addresses both explicit error classification and a major timing difference.

If an incorrect CEK always has an acceptable size, then AES construction proceeds instead of revealing “RSA produced N bytes” through an invalid-key-size exception.

If failure still proceeds through content decryption work, the attacker loses the easy distinction between “RSA failed immediately” and “RSA looked valid enough to reach AES.”

The patch is a good example of why secure cryptographic error handling often requires synthetic success at an inner layer followed by generic failure later, rather than an immediate exception at the precise point where malformed cryptographic structure was detected.

The Fix Does Not Authenticate PKCS#7 EnvelopedData

CVE-2026-69247 has a second lesson that is easy to miss.

The official pyca advisory explicitly states that one padding-oracle problem remains outside the scope of the CVE fix: ordinary EnvelopedData does not authenticate its encrypted content. If an attacker can modify encryptedContent and learn whether the resulting CBC plaintext has valid padding, that success/failure signal can become a classic CBC padding oracle.

The latest cryptography documentation now includes a direct warning. It says that an application decrypting attacker-supplied EnvelopedData and exposing whether decryption succeeded through errors, status codes, or timing can provide a CBC padding oracle capable of recovering plaintext. The documentation describes this as a property of PKCS#7 rather than something that can be completely repaired inside the library.

The two problems should be kept separate.

PropertyCVE-2026-69247 RSA oracleResidual CBC padding oracle
Attacker mutatesRecipientInfo.encryptedKeyencryptedContent
Relevant asymmetric mechanismRSA PKCS#1 v1.5None in the oracle step
Relevant symmetric modeDownstream AES-CBC processing helps distinguish outcomesCBC itself is central
Information goalRecover information about wrapped CEKRecover message plaintext
Causa principalDistinguishable CEK unwrap outcomesUnauthenticated CBC content plus observable padding validity
Fixed by cryptography 50.0.0Yes, for the reported implementation issueNo
Application design still matters

This distinction also clarifies a naming trap.

People sometimes call CVE-2026-69247 a “PKCS#7 padding oracle vulnerability.” That shorthand is understandable because the vulnerable APIs are PKCS#7 APIs and padding behavior participates in the observable path. But the disclosed CVE specifically concerns a Bleichenbacher oracle around RSA PKCS#1 v1.5 key transport inside PKCS#7 EnvelopedData.

A conventional CBC PKCS#7 padding oracle is a different cryptographic attack.

Security teams should make that distinction in tickets and incident reports because the remediation scope differs. Upgrading the Python package fixes CVE-2026-69247. It does not convert unauthenticated EnvelopedData into an authenticated-encryption protocol.

Where protocol design is under your control, confidentiality should normally be paired with cryptographic integrity rather than relying on CBC encryption plus hidden errors. If legacy CMS or S/MIME interoperability forces continued use of older constructions, application behavior around decryption must be treated as part of the cryptographic security boundary.

Safe PoC — Understanding the Oracle Without Attacking RSA

The following demonstration intentionally does no implement Bleichenbacher’s algorithm, manipulate real RSA ciphertexts, create malicious S/MIME messages, contact a server, or use a real certificate.

It is a local toy model showing why three distinguishable decryption paths are dangerous.

The simulated vulnerable function has three attacker-controlled outcomes:

  • RSA-format failure returns quickly.
  • A wrong-length CEK fails after a little more work.
  • A correct-length but incorrect CEK performs expensive “content decryption” before failing.

The code uses synthetic labels instead of cryptographic material.

import time
from dataclasses import dataclass


@dataclass
class ToyProbe:
    outcome: str


def vulnerable_toy_decrypt(probe: ToyProbe) -> str:
    """
    Educational simulation only.

    This function performs no RSA, PKCS#7, S/MIME, or real decryption.
    It intentionally creates distinguishable failure paths.
    """

    if probe.outcome == "rsa_padding_failure":
        time.sleep(0.002)
        return "Decryption failed"

    if probe.outcome == "wrong_cek_length":
        time.sleep(0.006)
        return "Invalid key size: 19 bytes"

    if probe.outcome == "correct_length_wrong_key":
        # Simulate additional symmetric decryption and unpadding work.
        time.sleep(0.025)
        return "Invalid padding bytes"

    if probe.outcome == "valid":
        time.sleep(0.025)
        return "plaintext"

    raise ValueError("unknown toy probe")


for outcome in [
    "rsa_padding_failure",
    "wrong_cek_length",
    "correct_length_wrong_key",
    "valid",
]:
    start = time.perf_counter()

    result = vulnerable_toy_decrypt(ToyProbe(outcome))

    elapsed_ms = (time.perf_counter() - start) * 1000

    print(
        f"{outcome:28s} "
        f"{elapsed_ms:8.2f} ms "
        f"{result}"
    )

A typical run might produce visibly different categories:

rsa_padding_failure             ~2 ms   Decryption failed
wrong_cek_length                ~6 ms   Invalid key size: 19 bytes
correct_length_wrong_key       ~25 ms   Invalid padding bytes
valid                          ~25 ms   plaintext

Nothing here is cryptographically useful by itself. The point is the classification.

An attacker does not need an endpoint to say:

YOUR RSA PLAINTEXT WAS 19 BYTES LONG

if timing already separates a 2 ms path from a 25 ms path.

Likewise, making the message generic while keeping the timing difference does not remove the oracle:

def still_bad_toy_decrypt(probe: ToyProbe) -> str:
    if probe.outcome == "rsa_padding_failure":
        time.sleep(0.002)

    elif probe.outcome == "wrong_cek_length":
        time.sleep(0.006)

    elif probe.outcome == "correct_length_wrong_key":
        time.sleep(0.025)

    else:
        time.sleep(0.025)

    return "Decryption failed"

The output string is now identical, but execution time still contains information.

A safer conceptual pattern is:

import os
import time


EXPECTED_KEY_SIZE = 32


def fixed_toy_decrypt(probe: ToyProbe) -> str:
    """
    Educational simulation only.

    The random bytes represent the RFC 3218-style substitute-key idea.
    No real cryptographic operation is performed.
    """

    substitute_key = os.urandom(EXPECTED_KEY_SIZE)

    # Pretend that every failure yields a usable-length key.
    if probe.outcome == "valid":
        candidate_key = b"A" * EXPECTED_KEY_SIZE
    else:
        candidate_key = substitute_key

    # Simulate equivalent downstream work for every candidate.
    _ = candidate_key
    time.sleep(0.025)

    if probe.outcome == "valid":
        return "plaintext"

    return "Decryption failed"

This second function illustrates the security principle used by RFC 3218 and by the cryptography 50.0.0 repair: do not expose whether the RSA key-unwrapping stage failed in one particular way. Substitute an appropriately shaped value and continue far enough that attacker-controlled failure cases converge.

This demonstration is deliberately unsuitable for attacking a real service. It does not contain the arithmetic required for a Bleichenbacher adaptive ciphertext search, does not parse CMS, and does not produce probe messages. Its purpose is to help defenders understand what they are trying to eliminate when testing application error behavior.

How to Check Whether Your Python Environment Is in the Affected Range

Start with local package inventory.

python -m pip show cryptography

Or inspect the version from Python:

import cryptography

print(cryptography.__version__)

A version from 44.0.0 through 49.x is within the CVE’s affected package range. Version 50.0.0 contains the fix.

Do not stop there.

Check the linked OpenSSL runtime where relevant:

import ssl
import cryptography

print("cryptography:", cryptography.__version__)
print("OpenSSL:", ssl.OPENSSL_VERSION)

Be careful interpreting that output. Python’s ssl module and the cryptography package can be packaged or linked differently in unusual environments, containers, distributions, or custom builds. Treat the command as inventory evidence rather than a universal proof of which provider handled a specific PKCS#7 operation.

Next, search the application source for the affected functions.

Using ripgrep:

rg 'pkcs7_decrypt_(der|pem|smime)' .

Also search imports:

rg 'serialization.*pkcs7|from cryptography.*pkcs7|import pkcs7' .

If there are no direct matches, inspect internal wrappers and libraries. A large codebase may centralize decryption behind names such as:

decrypt_message
decrypt_smime
process_encrypted_mail
unwrap_attachment
decrypt_cms
open_secure_message
decode_envelope

The question is not simply whether the package is installed.

The question is whether hostile bytes can reach one of the vulnerable decryption behaviors.

A Reachability Review for S/MIME and CMS Services

For an automated mail gateway, document the path from network input to private-key use.

A useful trace looks like:

SMTP ingress
    |
    v
MIME parser
    |
    v
Detect application/pkcs7-mime
    |
    v
Extract S/MIME EnvelopedData
    |
    v
Select recipient certificate
    |
    v
Load private key
    |
    v
pkcs7_decrypt_smime
    |
    v
Success or exception
    |
    v
SMTP response / queue action / API result / log

Now ask where an attacker can observe differences.

Does malformed encrypted mail produce a different SMTP response?

Does one failure immediately reject the message while another sends it into a slow malware scan?

Does a REST wrapper return 400 for one exception and 500 for another?

Does one branch retry while another permanently rejects?

Does the client have access to a job-status endpoint?

Does response latency differ systematically?

Are detailed cryptographic exceptions exposed through a webhook?

Can the sender submit thousands of messages without throttling?

Those are application-security questions, not merely cryptography-library questions.

The CVE’s official exploit model assumes an automated service capable of processing high-volume adaptive messages. That requirement should materially affect triage.

Why Rate Limiting Matters but Does Not Replace the Patch

RFC 3218’s classic Million Message Attack description emphasizes that the attack requires a large number of interactions. Its historical estimate is roughly 2^20 messages and responses for the classic method, which is one reason automated recipients are the natural target.

Rate limiting therefore changes economics.

An S/MIME decryption API accepting 10,000 requests per second presents a different oracle surface from a workflow that allows one message per authenticated business transaction.

But rate limiting is a secondary control.

It should not be treated as a substitute for cryptography >= 50.0.0 porque:

  • Improved attack techniques may alter practical query requirements.
  • Multiple source addresses may distribute queries.
  • Internal attackers can sometimes avoid internet-edge limits.
  • Message queues can provide high throughput even if the public endpoint appears slow.
  • Side-channel leakage remains a design defect even when exploitation is expensive.
  • Rate controls are frequently changed for operational reasons.

The durable fix is to eliminate the distinguishable cryptographic behavior.

What Defenders Should Look for in Logs

There is no single log entry that proves CVE-2026-69247 exploitation.

A Bleichenbacher oracle is an interaction pattern. Detection therefore benefits from correlation.

Potential signals include a sudden concentration of PKCS#7 or S/MIME decryption failures associated with the same recipient identity, certificate, mailbox, or key.

Repeated messages may have similar overall structure while containing small differences in the encrypted recipient-key field.

A source may generate substantially more decryption attempts than legitimate senders.

Historic logs may contain clusters of errors such as invalid RSA decryption, invalid AES key sizes, padding failures, malformed EnvelopedData, or decryption failures.

If an application previously exposed exact AES key-size errors, those historical messages are particularly relevant because the pyca advisory identifies wrong-length disclosure as one of the distinguishable outcomes.

Example investigation fields:

timestamp
source identity
source IP
mail sender
recipient mailbox
recipient certificate fingerprint
message identifier
PKCS#7 content type
message size
encryptedKey size
decryption result category
application response code
processing duration
retry count
queue outcome

Do not overinterpret one padding error.

Ordinary corrupted mail, expired certificates, misconfigured clients, transport damage, wrong keys, and malformed MIME can all generate decryption failures.

The useful signal is repetition and structure: many closely related failures targeted at a cryptographic recipient, especially when request frequency and ciphertext variation are inconsistent with normal user behavior.

Timing Detection Is Harder Than Error Detection

Timing side channels are difficult to confirm from normal application telemetry.

Internet latency introduces noise.

Garbage collection introduces noise.

CPU scheduling introduces noise.

Mail scanning, antivirus engines, queue backpressure, DNS resolution, logging, storage, and downstream API calls can all dominate cryptographic execution time.

This cuts both ways.

A noisy network can make exploitation harder, but defenders should not conclude that a timing oracle is harmless because one manual request took 120 ms and another took 127 ms.

Attackers interested in a statistical side channel collect distributions, not anecdotes.

Defenders testing their own systems should use an isolated staging environment and controlled workloads. The goal is not to reconstruct a production exploit. It is to verify that attacker-controlled cryptographic failure classes do not produce stable externally distinguishable behavior.

Useful defensive questions include:

Do all malformed encryptedKey classes return the same external status?

Do they produce the same response body?

Do they follow the same queue path?

Do they perform comparable downstream processing?

Are private exception details hidden from untrusted users?

Does one malformed class consistently terminate much earlier?

Does rate limiting activate before large adaptive query volumes become possible?

Do not run a high-volume oracle experiment against production merely to obtain confidence in the patch. Package upgrade, code review, runtime inventory, and low-volume controlled testing provide a safer validation path.

Remediation — Upgrade First

The primary remediation is:

cryptography >= 50.0.0

The project’s changelog explicitly identifies CVE-2026-69247 as a security issue fixed in 50.0.0. GitLab’s advisory likewise lists 50.0.0 as the fixed release and recommends upgrading to that version or later.

For a requirements file:

cryptography>=50.0.0

For a constraints-based deployment:

cryptography>=50.0.0

For a direct upgrade:

python -m pip install --upgrade 'cryptography>=50.0.0'

After the update, verify the installed runtime rather than assuming a lockfile change reached production:

python -m pip show cryptography

Containerized environments require rebuilding and redeploying the image. A source repository can contain a corrected dependency specification while a long-running container still has the old wheel installed.

Similarly, serverless layers, packaged desktop applications, offline appliances, bundled Python runtimes, and copied virtual environments can preserve older dependencies long after the main project file changes.

Do Not Treat Backend Mitigation as Your Permanent Fix

A team may discover:

cryptography 49.x
OpenSSL 3.4.x

and correctly observe that OpenSSL 3.2+ implements implicit rejection for RSA PKCS#1 v1.5 decryption.

That is useful exposure context.

It is not a reason to remain on cryptography 49.

The pyca fix moves defensive behavior closer to the PKCS#7 logic that understands the expected CEK and its length. That matters for portability across OpenSSL versions and alternate cryptographic backends. The official fix explicitly covers both RSA decryption failure and wrong-length recovered CEKs before continuing through an equivalent downstream path.

Security should not depend accidentally on whichever crypto provider a future package build happens to select.

Reduce Decryption-Oracle Exposure

The patch is necessary, but applications handling hostile encrypted content should also reconsider the service boundary.

Avoid returning raw cryptographic exceptions to clients.

Bad:

{
  "error": "Invalid key size 19 for AES"
}

Better:

{
  "error": "Unable to process encrypted message"
}

Even the second response is not sufficient if status codes or timing remain distinguishable, but it removes one unnecessary explicit leak.

Similarly, avoid structures such as:

RSA error        -> HTTP 400
AES error        -> HTTP 422
padding error    -> HTTP 500
certificate miss -> HTTP 404

when all four are reachable through attacker-controlled cryptographic input.

External callers generally need to know that processing failed, not exactly which mathematical validation step failed.

Internally, detailed diagnostics may still be valuable for troubleshooting, but access to those logs should be controlled and the logging path itself should not dramatically alter the externally observable response.

Separate Cryptographic Failure From Business Feedback

Mail infrastructure frequently creates side channels unintentionally because cryptographic parsing is only one stage of a much larger pipeline.

Por ejemplo:

Message A
RSA unwrap fails
-> immediate SMTP reject

Message B
RSA unwrap succeeds with bad CEK
-> AES decrypt
-> content parse
-> malware scanner
-> delayed quarantine notification

Even if the cryptographic library raises one generic exception, the business workflow can reveal which stage was reached.

A safer architecture minimizes information returned synchronously:

Inbound encrypted message
        |
        v
generic acceptance or rejection boundary
        |
        v
internal controlled processing
        |
        v
uniform external failure policy

The exact architecture depends on operational requirements, but the principle is stable: do not let untrusted clients use internal crypto-state transitions as a measurement interface.

RSA PKCS#1 v1.5 Should Be Treated as Legacy Risk

RFC 3218 was published in January 2002 specifically because the Bleichenbacher attack remained relevant to CMS. It recommends random CEK substitution for malformed RSA PKCS#1 v1.5 blocks and discusses OAEP as an upgrade path.

OpenSSL’s current documentation likewise recommends OAEP for new RSA encryption uses and explicitly warns that PKCS#1 v1.5 decryption failures can leak information sufficient for a Bleichenbacher padding-oracle attack.

Compatibility can force older mechanisms to remain in S/MIME, CMS, hardware tokens, enterprise PKI, or legacy message formats.

That does not make them equivalent to modern chosen-ciphertext-resistant designs.

Whenever a new protocol is being designed rather than inherited, the better architectural question is not:

How can we safely expose PKCS#1 v1.5 decryption errors?

It is:

Why are we choosing a legacy RSA v1.5 key-transport design at all?

Where compatibility requires it, RFC 3218-style defenses become mandatory engineering rather than optional hardening.

Related CVE — CVE-2012-0884

CVE-2026-69247 is not the first time CMS, PKCS#7, and Bleichenbacher behavior have collided.

OpenSSL records CVE-2012-0884 as a weakness in its CMS and PKCS#7 code that could be exploited using Bleichenbacher’s PKCS#1 v1.5 attack, also known as the Million Message Attack. OpenSSL specifically stated that users performing CMS, PKCS#7, or S/MIME decryption were affected, while SSL and TLS applications were not affected by that particular CVE.

The historical similarity matters.

Both issues demonstrate that secure RSA implementation is not only about modular exponentiation or key size. The protocol surrounding the private-key operation must avoid becoming an oracle.

A library can use mathematically correct RSA and still expose confidentiality through failure classification.

Related CVE — CVE-2026-42768

An even closer comparison arrived in 2026.

OpenSSL disclosed CVE-2026-42768, a Bleichenbacher-style weakness in CMS_descifrar y PKCS7_descifrar involving multiple RecipientInfo structures. OpenSSL’s advisory describes scenarios where attacker-authored CMS or S/MIME messages can abuse processing of multiple key-transport recipient entries and obtain useful distinctions from error codes and decryption output.

OpenSSL assessed that issue as Low severity because it was not aware of applications exposing the precise remote interaction required and considered such applications unlikely.

CVE-2026-42768 and CVE-2026-69247 are not the same bug.

Their implementation paths differ.

Their affected software differs.

Their exact oracle construction differs.

But the common engineering failure is important:

attacker-controlled CMS structure
          |
          v
victim RSA private-key operation
          |
          v
subtly different internal outcomes
          |
          v
observable external distinction
          |
          v
Bleichenbacher-style oracle

Finding similar oracle bugs repeatedly across independent implementations more than two decades after RFC 3218 is a reminder that cryptographic protocol integration remains difficult even when the underlying primitive is well understood.

Related CVE — CVE-2025-7071

CVE-2025-7071 is useful because it illustrates the other kind of “padding oracle” that readers may mistakenly merge with CVE-2026-69247.

Oberon microsystem’s advisory describes a timing side channel in the ocrypto library’s AES-CBC PKCS#7 padding removal. The implementation’s timing differed between valid and invalid padding, allowing an attacker capable of sending many ciphertext probes and measuring responses to recover plaintext. NVD lists affected ocrypto versions from 3.1.0 up to, but not including, 3.9.2.

That is a symmetric CBC padding oracle.

CVE-2026-69247 is primarily an RSA PKCS#1 v1.5 CEK-unwrapping oracle inside a PKCS#7 workflow.

The defense themes overlap — eliminate observable decryption distinctions — but the cryptographic objects under attack differ.

Dependency Scanning Is Necessary but Not Sufficient

CVE-2026-69247 is a good example of why vulnerability management increasingly needs both component inventory and reachability analysis.

An SBOM or dependency scanner can confidently identify:

cryptography==49.0.0

and associate it with CVE-2026-69247.

That is valuable.

But the scanner usually cannot prove:

Attacker-controlled S/MIME messages reach pkcs7_decrypt_smime
AND
the target certificate is selected
AND
a private RSA key performs decryption
AND
the runtime backend exposes the relevant distinction
AND
the application reveals that distinction remotely
AND
high-volume adaptive requests are feasible

Those are runtime and architecture properties.

This creates three possible operational mistakes.

The first is underreaction:

“The package is only a library, so this cannot matter.”

An automated mail gateway may make the vulnerable API directly reachable from hostile traffic.

El segundo es overreaction:

“The dependency scanner says High, therefore remote CEK recovery is proven.”

A local utility with no untrusted PKCS#7 input may contain the package but provide no oracle whatsoever.

The third is false closure:

“We upgraded the file in Git, therefore production is fixed.”

The running environment may still contain an older wheel.

The strongest validation process combines software inventory, code reachability, runtime backend evidence, controlled functional tests, external error analysis, and deployment verification.

For organizations already using automated authorized security-validation workflows, this is also the stage where agentic tooling can be useful: correlating dependency findings with code paths, runtime evidence, controlled retesting, and reproducible reports instead of treating every CVE match as equivalent. Penligente positions its workflow around vulnerability verification, tool orchestration, evidence capture, and reproducible reporting; those capabilities can support the surrounding validation process, but they do not replace the required cryptography upgrade or prove this oracle exists solely from package presence.

A Practical Validation Workflow

A defensible workflow for CVE-2026-69247 can look like this.

Step 1 — Inventory

Record:

application
environment
cryptography version
Python version
installation method
container image digest
operating system
crypto backend
deployment owner

Step 2 — Establish function reachability

Search for:

pkcs7_decrypt_der
pkcs7_decrypt_pem
pkcs7_decrypt_smime

Then trace wrappers until you know which externally controlled inputs can reach the call.

Step 3 — Identify key usage

Determine which certificate and private key are supplied.

Ask:

Is the key long lived?
Is it shared across a mail gateway cluster?
Is it stored in software, HSM, KMS, or smartcard?
Can external messages trigger private-key operations automatically?

The storage location does not automatically remove the oracle. A hardware-protected key can remain vulnerable to a chosen-ciphertext protocol attack if the application repeatedly asks the hardware to perform the private-key operation and leaks the outcome.

Step 4 — Establish backend behavior

Record the actual deployed backend.

Do not infer it only from a developer workstation.

Container images, Linux distribution packages, source builds, FIPS builds, embedded systems, and enterprise Python distributions can differ.

Step 5 — Review external failure behavior

In a staging environment you control, feed ordinary malformed test messages rather than a weaponized Bleichenbacher probe sequence.

Verify whether distinct classes of invalid encrypted messages produce:

different status codes
different response bodies
different SMTP codes
different queue actions
different webhook outcomes
different retry behavior
obvious processing-time classes

Step 6 — Upgrade

Deploy cryptography >= 50.0.0.

Step 7 — Repeat the same observations

Confirm the deployment actually changed.

The relevant output is evidence such as:

Production image digest:
Installed cryptography version:
Runtime backend:
Reachable API:
External error policy:
Patch deployment time:
Post-patch validation time:
Evidence owner:

That is more useful than closing a ticket with the sentence “package bumped.”

Common Mistakes When Responding to CVE-2026-69247

Assuming every use of cryptography is affected

The vulnerable functionality is specifically the PKCS#7 EnvelopedData decryption path added in version 44. Applications using only Fernet, X.509 parsing, hashing, TLS, signing, or unrelated APIs are not automatically exposing this CVE merely because they import the package.

Treating S/MIME support as proof of exploitability

S/MIME handling narrows the search, but you still need attacker-controlled decryption, private-key use, repeated queries, and observable distinctions.

Looking only at exception strings

Timing can carry the oracle even when messages are normalized.

Looking only at timing

A precise error code may provide a much stronger signal than timing.

Treating OpenSSL 3.2+ as equivalent to cryptography 50

They are different mitigations at different layers. Upgrade the Python package.

Assuming the CVE fix solves all EnvelopedData oracle risks

The pyca documentation explicitly warns that unauthenticated EnvelopedData can still create a CBC padding oracle if an application exposes decryption success.

Performing a massive production test

A high-volume adaptive cryptographic probe can create operational load, interfere with mail processing, trigger security controls, and cross authorization boundaries. Validation should remain controlled and scoped.

How to Prioritize CVE-2026-69247

A practical severity model should account for architecture.

Highest priority

Patch immediately when most of these conditions are true:

cryptography 44–49
+
PKCS#7 decryption API reachable
+
internet or partner-controlled EnvelopedData
+
automatic S/MIME or CMS processing
+
long-lived RSA recipient key
+
high request throughput
+
observable decryption outcomes
+
backend without adequate implicit rejection

High patch priority but lower immediate exploit confidence

Examples:

cryptography 44–49
+
reachable S/MIME decryption
+
OpenSSL 3.2+ implicit rejection

or:

cryptography 44–49
+
internal-only automated CMS decryption

The package still needs updating, but exploitation likelihood depends on additional evidence.

Routine remediation

Examples include development tools where the affected package is installed but none of the three PKCS#7 decryption functions are reachable.

Even here, routine remediation should not become indefinite deferral. Security libraries are foundational dependencies, and leaving known defects in place creates future exposure if functionality changes.

Is There Evidence of Active Exploitation

The current public record does not establish active exploitation of CVE-2026-69247.

The CVE record’s CISA ADP enrichment, updated August 4, 2026, marks exploitation as ninguno and automatable as no, while classifying technical impact as partial.

Current Tenable vulnerability metadata also states that no known exploits are available.

Those observations should be interpreted carefully.

“No known exploit” does not mean “not exploitable.”

“Exploitation: none” in a vulnerability-enrichment record does not prove that no private research, unpublished PoC, or later attack exists.

It means defenders should not claim confirmed exploitation without evidence.

As of August 11, 2026, the stronger reason to patch is the quality of the technical disclosure and the availability of an upstream fix, not evidence of an active mass-exploitation campaign.

Why This Bug Matters Beyond One Python Package

CVE-2026-69247 is a useful case study in the difference between cryptographic primitives and cryptographic protocols.

RSA can be mathematically correct.

AES can be mathematically correct.

PKCS#7 parsing can be functionally correct.

The application can still be insecure because the composition leaks state.

The vulnerable sequence combines:

RSA PKCS#1 v1.5
+
CEK length validation
+
AES key construction
+
CBC decryption
+
PKCS#7 unpadding
+
application error handling

Each layer creates a possible branch.

An adversary does not care which engineering team owns the branch. The network exposes one combined machine.

This is why side-channel review must cross abstraction boundaries.

A cryptographic API should be evaluated not only for whether it computes the right answer, but for what an attacker learns when the answer is wrong.

PREGUNTAS FRECUENTES

What is CVE-2026-69247?

  • It is a Bleichenbacher-style side-channel vulnerability in the PKCS#7 EnvelopedData decryption functions of Python’s pyca cryptography package.
  • The affected functions are pkcs7_decrypt_der, pkcs7_decrypt_pemy pkcs7_decrypt_smime.
  • Older versions could expose different outcomes when unwrapping an RSA PKCS#1 v1.5-protected content-encryption key.
  • Those differences could appear through explicit errors and through timing.
  • The vulnerability was introduced with PKCS#7 decryption support in 44.0.0 and fixed in 50.0.0.

Which cryptography versions are vulnerable?

  • The CNA lists cryptography >= 44.0.0, < 50.0.0 as affected.
  • Version 50.0.0 contains the upstream correction.
  • Applications should upgrade rather than relying solely on backend-specific mitigations.
  • After changing dependency files, verify the version running in the actual deployed environment.

Does every Python application using cryptography become exploitable?

  • No.
  • The application must reach one of the affected PKCS#7 decryption APIs.
  • Attacker-controlled EnvelopedData must reach the decryption path.
  • A useful recipient certificate and private key must participate.
  • The attacker needs repeated adaptive interactions and an observable distinction in errors, output, status, or timing.
  • Backend behavior also affects practical exploitation.

Does OpenSSL 3.2 or later remove the risk?

  • OpenSSL 3.2+ significantly changes practical exposure because its default provider uses implicit rejection for RSA PKCS#1 v1.5 padding failures.
  • That behavior prevents a simple lower-level padding-failure distinction.
  • The pyca advisory specifically notes this mitigation.
  • It is not equivalent to applying the cryptography 50.0.0 fix.
  • Upgrade the Python package so the PKCS#7 layer independently handles bad or wrong-length CEKs in the RFC 3218 style.

Is CVE-2026-69247 the same as a normal PKCS#7 CBC padding oracle?

  • No.
  • CVE-2026-69247 centers on the RSA PKCS#1 v1.5-wrapped CEK inside RecipientInfo.encryptedKey.
  • A classic CBC padding oracle instead manipulates symmetric ciphertext and observes whether decrypted block padding is valid.
  • cryptography 50.0.0 fixes the reported RSA key-unwrapping oracle.
  • The project separately warns that unauthenticated PKCS#7 EnvelopedData can still expose a CBC padding oracle if applications reveal decryption success.

How can defenders safely verify exposure?

  • Check whether the deployed cryptography version is between 44.0.0 and 49.x.
  • Search the source for pkcs7_decrypt_der, pkcs7_decrypt_pemy pkcs7_decrypt_smime.
  • Trace whether untrusted S/MIME or CMS content can reach those calls.
  • Record the runtime crypto backend.
  • Inspect external error handling and controlled staging timing without running a full Bleichenbacher attack.
  • Upgrade to 50.0.0 or later and repeat the validation.
  • Avoid high-volume adaptive probing against production systems.

Is CVE-2026-69247 being exploited in the wild?

  • No confirmed active-exploitation campaign is established in the current authoritative public record reviewed here.
  • CISA ADP enrichment for the CVE listed exploitation as ninguno on August 4, 2026.
  • Tenable currently lists no known exploit.
  • Those indicators do not prove exploitation is impossible or will not emerge later.
  • Patch based on technical exposure and upstream remediation rather than waiting for a KEV entry or public weaponized PoC.

Closing Assessment

CVE-2026-69247 is not a generic claim that Python cryptography is broken, and it is not simply another dependency version alert.

It is a specific failure to make RSA PKCS#1 v1.5 CEK-unwrapping outcomes indistinguishable inside PKCS#7 EnvelopedData processing.

The affected package range is clear: cryptography 44.0.0 through 49.x. The corrected release is 50.0.0. The immediate remediation is therefore easy to state.

The harder security work is identifying whether an application turned that library defect into a real oracle.

Automated S/MIME gateways, mail filters, CMS processors, and similar services deserve the closest review because they naturally combine untrusted encrypted messages, private-key operations, machine-speed processing, and repeatable responses. Backend-specific implicit rejection may reduce practical exposure in many deployments, particularly those already using modern OpenSSL, but it should be treated as defense in depth rather than a reason to leave the vulnerable cryptography implementation installed.

Finally, upgrading does not remove the broader protocol lesson. The project now explicitly warns that ordinary unauthenticated EnvelopedData can itself become a CBC padding oracle when applications reveal whether decryption succeeded.

The durable rule is therefore broader than one CVE: never allow an attacker to turn private cryptographic failure states into a repeatable query interface.

Comparte el post:
Entradas relacionadas
es_ESSpanish