Safeguard
Vulnerability Analysis

CVE-2023-50782: Bleichenbacher timing oracle in python-cr...

CVE-2023-50782 exposes a Bleichenbacher-style timing oracle in python-cryptography's RSA PKCS1v15 decryption, letting attackers recover plaintext.

Vikram Iyer
Security Researcher
7 min read

In December 2023, the Python Cryptographic Authority (PyCA) disclosed CVE-2023-50782, a Bleichenbacher-style timing oracle in the widely used cryptography package for Python. The flaw lives in RSA decryption when applications use PKCS#1 v1.5 padding: by measuring how long the library takes to reject malformed ciphertext, a network attacker can slowly distinguish valid padding from invalid padding and, given enough trial messages, recover the plaintext of an intercepted RSA-encrypted payload without ever learning the private key. This is the same family of attack Daniel Bleichenbacher described against SSL in 1998, and the same class that powered the 2017 ROBOT research showing dozens of major TLS implementations were still exploitable nearly two decades later. CVE-2023-50782 is a reminder that padding oracles never fully die — they just move to new codebases.

cryptography is one of the most heavily depended-upon packages in the Python ecosystem. It backs TLS handling in pyOpenSSL, SSH operations in paramiko, certificate and JOSE/JWT tooling, and countless internal services that do raw RSA encrypt/decrypt for legacy protocols, S/MIME, XML encryption, or bespoke key-wrapping schemes. Any code path that calls private_key.decrypt(ciphertext, padding.PKCS1v15()) — directly or through a downstream library — inherited this timing side channel. Because the vulnerable API is buried several dependency layers deep in many applications, most engineering teams had no straightforward way to know they were exposed without inspecting their full dependency graph and how each dependency actually used RSA.

Affected Versions and Components

  • Package: cryptography (PyPI), maintained by the Python Cryptographic Authority.
  • Vulnerable code path: RSA private-key decryption using padding.PKCS1v15(). Decryption using OAEP padding (padding.OAEP(...)) is not affected by this specific oracle — OAEP's construction is designed to resist Bleichenbacher-style padding attacks, which is one reason it has long been the recommended padding scheme for new RSA encryption use cases.
  • Fix released in: cryptography 41.0.7, which PyCA shipped shortly after the advisory (GitHub Security Advisory GHSA-3ww4-gg4f-jr7f) was published. Any deployment pinned to an earlier 41.x release, or to older 3.x/4x-series releases predating the fix, remained exposed until upgraded.
  • Indirectly affected: pyOpenSSL and any other package that vendors or wraps cryptography's RSA decryption path inherit the issue until they pull in a patched cryptography release. Applications that only use cryptography for hashing, symmetric encryption, TLS via the standard library's ssl module, or RSA signing (rather than PKCS1v15 decryption) are not affected by this particular CVE.

Because cryptography sits so deep in the Python supply chain, the practical affected surface was never just "one library" — it was every service, script, and internal tool that pulled in a vulnerable version transitively, often without a developer ever writing a single line of RSA code themselves.

CVSS, EPSS, and KEV Context

The advisory was rated Medium severity, consistent with a CVSS v3.1 vector reflecting network attack vector, high attack complexity, no privileges or user interaction required, and confidentiality-only impact (no integrity or availability impact). The high attack-complexity component matters here: exploiting a timing oracle in practice requires an attacker capable of sending a very large number of trial decryption requests and precisely measuring response-time differentials, often across a network with jitter — a meaningfully harder bar than a straightforward remote code execution bug, but not one to dismiss for services that expose RSA decryption as an oracle at scale.

As of this writing, CVE-2023-50782 does not appear on CISA's Known Exploited Vulnerabilities (KEV) catalog, and public EPSS scoring for the CVE has remained low, consistent with the high complexity of a real-world timing attack and the absence of any confirmed working exploit chain in the wild. That said, EPSS and KEV status reflect observed exploitation activity, not the ceiling of what's theoretically possible — Bleichenbacher-class oracles have a long history of being weaponized years after initial disclosure once someone builds a reliable timing-measurement harness against a specific target. Treating a "Medium, no known exploitation" rating as low priority is exactly the reasoning that let ROBOT-class oracles persist silently in production TLS stacks for years.

Timeline

  • Vulnerability identified and reported to the PyCA maintainers as a timing side channel in RSA PKCS1v15 decryption within the cryptography package's Rust-backed OpenSSL bindings.
  • Coordinated fix developed by the PyCA maintainers, hardening the decryption path to reduce the observable timing discrepancy between valid and invalid padding.
  • Public disclosure: GitHub Security Advisory GHSA-3ww4-gg4f-jr7f published, with CVE-2023-50782 assigned to track the issue.
  • Patched release: cryptography 41.0.7 published to PyPI containing the fix, alongside guidance to prefer OAEP padding for RSA decryption wherever protocol constraints allow it.
  • Downstream propagation: dependent packages (pyOpenSSL and others bundling cryptography) issued or picked up updated pins in the following release cycles, gradually flowing the fix through the broader dependency graph.

The gap between "patch published" and "patch actually running in production" is where most of the real-world risk from this CVE has lived — not in the initial disclosure window, but in the long tail of pinned requirements files, container images built months before the advisory, and internal packages that vendor cryptography without an active update cadence.

Remediation Steps

  1. Upgrade cryptography to 41.0.7 or later across every service, CLI tool, CI image, and container base layer that lists it as a direct or transitive dependency. Don't stop at your application's own requirements.txt — check base images, build tooling, and any vendored virtual environments.
  2. Audit transitive exposure. Run pip show cryptography or an SBOM/dependency-graph tool against your services to confirm which are pulling in a fixed version versus an older pinned one, including through pyOpenSSL, paramiko, PyJWT's cryptography backend, and internal shared libraries.
  3. Prefer OAEP over PKCS1v15 for new RSA decryption code. Where protocol compatibility doesn't force PKCS1v15 (e.g., legacy interop requirements), migrate to padding.OAEP() to remove this class of oracle entirely rather than relying solely on timing mitigations.
  4. Re-lock and rebuild. Regenerate lockfiles (pip-compile, poetry.lock, Pipfile.lock) and rebuild container images so the patched version is actually what ships, not just what's declared in a requirements file.
  5. Check certificate and key-exchange tooling that calls into cryptography for RSA decryption in TLS termination proxies, VPN concentrators, S/MIME processors, or custom key-wrapping services — these are the highest-value targets for a Bleichenbacher-style oracle in practice.
  6. Monitor for reintroduction. Add the fixed version floor to CI dependency checks so a future downgrade, a stale internal mirror, or an unpinned transitive dependency doesn't silently reintroduce the vulnerable code path.

How Safeguard Helps

CVE-2023-50782 is a textbook case of why supply chain visibility has to go deeper than "is this package in my manifest." The vulnerable function was buried inside a cryptography backend that most engineers never call directly — it arrives through pyOpenSSL, SSH tooling, JWT libraries, and internal wrappers, which is exactly the kind of multi-hop transitive exposure that manual dependency reviews miss.

Safeguard continuously inventories every direct and transitive dependency across your services, containers, and build pipelines, and correlates that live software bill of materials against newly disclosed CVEs like this one — including advisories that originate on GitHub Security Advisories before they're fully indexed elsewhere. When a package like cryptography gets a fix for a timing oracle, Safeguard identifies every artifact in your environment still running a vulnerable version, distinguishes direct dependencies from ones inherited through libraries like pyOpenSSL, and prioritizes remediation based on where the vulnerable RSA decryption path is actually reachable in your codebase rather than flagging every instance identically.

Because Safeguard tracks provenance and version drift continuously rather than at point-in-time scan intervals, it also catches the regression case that causes many of these vulnerabilities to linger for years: a rebuilt container image, an unpinned transitive dependency, or a stale internal mirror silently reintroducing a patched CVE. For teams that need to demonstrate remediation timelines for compliance frameworks like SOC 2, Safeguard's continuous evidence trail turns "we patched this eventually" into an auditable record of when exposure was identified, tracked, and closed.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.