# PYTHON-LANG-SEC-050: Unverified SSL Context Created

> **Severity:** HIGH | **CWE:** CWE-295 | **OWASP:** A02:2021

- **Language:** Python
- **Category:** Python Core
- **URL:** https://codepathfinder.dev/registry/python/lang/PYTHON-LANG-SEC-050
- **Detection:** `pathfinder scan --ruleset python/PYTHON-LANG-SEC-050 --project .`

## Description

ssl._create_unverified_context() creates an SSL context that does not verify the server's
certificate. With certificate verification disabled, a TLS connection provides only
encryption confidentiality — it does not authenticate the server, meaning an attacker on
the network path can perform a man-in-the-middle (MITM) attack by presenting any TLS
certificate and intercepting all traffic.

The leading underscore in the function name signals that it is a private, internal function
not intended for general use. It exists to provide a workaround for legacy code that
cannot use valid certificates. The public API ssl.create_default_context() is the correct
function to use, as it enables certificate verification with proper CA chain validation.

This function is sometimes used as a quick fix for certificate errors in development, then
accidentally committed to production code. Its presence in production code is always a
critical security finding.


## Vulnerable Code

```python
import ssl
import http.client
import requests as http_requests

ctx = ssl._create_unverified_context()
```

## Secure Code

```python
import ssl
import urllib.request

# INSECURE: Disabling certificate verification
# ctx = ssl._create_unverified_context()
# response = urllib.request.urlopen(url, context=ctx)

# SECURE: Use ssl.create_default_context() with proper certificate verification
def make_secure_request(url: str) -> bytes:
    ctx = ssl.create_default_context()
    with urllib.request.urlopen(url, context=ctx) as response:
        return response.read()

# SECURE: For custom CA certificates (corporate environments)
def make_request_with_custom_ca(url: str, ca_cert_path: str) -> bytes:
    ctx = ssl.create_default_context(cafile=ca_cert_path)
    with urllib.request.urlopen(url, context=ctx) as response:
        return response.read()

# SECURE: For mutual TLS (client certificate authentication)
def make_mtls_request(url: str, cert_path: str, key_path: str) -> bytes:
    ctx = ssl.create_default_context()
    ctx.load_cert_chain(certfile=cert_path, keyfile=key_path)
    with urllib.request.urlopen(url, context=ctx) as response:
        return response.read()

```

## Detection Rule (Python SDK)

```python
from rules.python_decorators import python_rule
from codepathfinder import calls, QueryType

class SSLModule(QueryType):
    fqns = ["ssl"]


@python_rule(
    id="PYTHON-LANG-SEC-050",
    name="Unverified SSL Context",
    severity="HIGH",
    category="lang",
    cwe="CWE-295",
    tags="python,ssl,unverified-context,certificate,OWASP-A02,CWE-295",
    message="ssl._create_unverified_context() disables certificate verification. Use ssl.create_default_context().",
    owasp="A02:2021",
)
def detect_unverified_ssl():
    """Detects ssl._create_unverified_context() usage."""
    return SSLModule.method("_create_unverified_context")
```

## How to Fix

- Replace ssl._create_unverified_context() with ssl.create_default_context() which enables certificate verification with the system CA store.
- If connecting to a server with a self-signed or private CA certificate, use ssl.create_default_context(cafile=path_to_ca) to specify the trusted CA.
- Never disable certificate verification as a workaround for certificate errors; fix the underlying certificate issue instead.
- In development environments, use a local CA (such as mkcert) to issue development certificates trusted by your machine rather than disabling verification.
- Audit all SSL context creation in the codebase to ensure CERT_REQUIRED is the validation mode and no custom cert verification bypass is present.

## Security Implications

- **Man-in-the-Middle Attack:** Without certificate verification, any attacker on the network path can present a
self-signed or fraudulent TLS certificate. The Python client accepts it without
validation, and the attacker can read and modify all "encrypted" traffic.

- **Credential and Session Token Interception:** Unverified TLS connections protect data from passive eavesdropping but not from
MITM attacks. Login credentials, session tokens, API keys, and sensitive data
transmitted over these connections can be captured by an attacker positioned between
the client and server.

- **Data Integrity Failure:** MITM attackers can modify request and response data in transit, injecting malicious
content into responses, altering financial transaction amounts, or replacing software
downloads with malicious binaries.

- **Regulatory Compliance Violation:** Disabling certificate verification violates PCI DSS Requirement 4.2.1 (secure
transmission of cardholder data), HIPAA technical safeguards, and most data
protection regulations that mandate proper TLS implementation.


## FAQ

**Q: Why does ssl._create_unverified_context() exist if it is insecure?**

It exists to support legacy code that needs TLS encryption without certificate
validation, such as connecting to internal development servers with self-signed
certificates before proper PKI is set up. The underscore prefix signals it is
an internal function not intended for general use. It should never appear in
production code.


**Q: How do I fix a certificate verification error without disabling verification?**

Certificate errors indicate a real problem: an expired certificate, a hostname mismatch,
an untrusted CA, or a self-signed certificate. Fix the error by: renewing the certificate,
correcting the hostname, adding the CA certificate to the trust store, or using
ssl.create_default_context(cafile=path) for private CA certificates. Never bypass
the error.


**Q: Is ssl._create_unverified_context() safe in unit tests?**

In unit tests that mock network calls or run against a local test server, disabling
verification may be acceptable. However, it is better to use a test CA with mkcert
or similar tools, or to use Python's ssl.create_default_context() with the test
server's CA certificate. This ensures tests validate the same configuration used
in production.


**Q: What is the difference between ssl._create_unverified_context() and ssl.CERT_NONE?**

Both disable certificate verification. ssl._create_unverified_context() returns a
pre-configured context with verification disabled. ssl.SSLContext() with
ctx.verify_mode = ssl.CERT_NONE achieves the same result through explicit configuration.
See PYTHON-LANG-SEC-053 for rules covering the CERT_NONE pattern.


**Q: Can I use ssl.create_default_context() with a corporate internal CA?**

Yes. ssl.create_default_context(cafile="/path/to/corporate-ca.crt") loads the corporate
CA certificate and uses it for validation. Alternatively, add the corporate CA to the
system trust store, which ssl.create_default_context() uses by default.


**Q: Is disabling verification acceptable for localhost connections?**

For localhost connections to locally controlled services, the MITM risk is low since
an attacker would need local process access to intercept the connection. However,
even localhost TLS should use proper certificates for consistency with production
configuration and to prevent errors if the connection is accidentally redirected.


## References

- [CWE-295: Improper Certificate Validation](https://cwe.mitre.org/data/definitions/295.html)
- [Python docs: ssl.create_default_context()](https://docs.python.org/3/library/ssl.html#ssl.create_default_context)
- [OWASP TLS Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Security_Cheat_Sheet.html)
- [Python SSL HOWTO](https://docs.python.org/3/library/ssl.html#ssl-howto)
- [OWASP Top 10 A02:2021 Cryptographic Failures](https://owasp.org/Top10/A02_2021-Cryptographic_Failures/)

---

Source: https://codepathfinder.dev/registry/python/lang/PYTHON-LANG-SEC-050
Code Pathfinder — Open source, type-aware SAST with cross-file dataflow analysis
