Improper certificate validation

Improper certificate validation

Description

Improper certificate validation occurs when an application does not correctly verify SSL/TLS certificates. An attacker able to intercept the connection may impersonate the peer, read data or modify traffic.

Potential impact

  • Man-in-the-middle attacks: An attacker may intercept or alter client–server communication.
  • Data exposure: Sensitive information may reach the attacker.
  • Loss of integrity: The confidentiality and integrity of communication may be compromised.

Remediation

  • Enable SSL/TLS certificate validation.
  • Use libraries such as Python’s requests with verification correctly configured.
  • Use valid certificates issued by a trusted certificate authority (CA).

Examples

These excerpts focus on certificate settings. Supply CA files and client certificate paths for your environment. Server authentication requires both chain and hostname validation; presenting a client certificate does not replace either check.

Standard Library - SSL

Before

python
# Unsafe ssl code
import ssl

unsafe_ctx_1 = ssl._create_stdlib_context()
unsafe_ctx_2 = ssl._create_unverified_context()

unsafe_ctx_3 = ssl.create_default_context()
unsafe_ctx_3.check_hostname = False
unsafe_ctx_3.verify_mode = ssl.CERT_NONE

After

# Safe ssl code
import ssl

safe_ctx_1 = ssl.create_default_context()
safe_ctx_1.verify_mode = ssl.CERT_REQUIRED

safe_ctx_2 = ssl._create_default_https_context()

Explanation:

  • Before: ssl._create_stdlib_context(), ssl._create_unverified_context(), and assigning ssl.CERT_NONE to verify_mode disable certificate validation. The example disables hostname checking before assigning CERT_NONE.
  • After: A default client context verifies the certificate chain and hostname. Pass the correct server_hostname when wrapping the socket.

OpenSSL

Before

python
# Unsafe OpenSSL code
from OpenSSL import SSL

unsafe_ctx_1 = SSL.Context(SSL.TLSv1_2_METHOD)

unsafe_ctx_2 = SSL.Context(SSL.TLSv1_2_METHOD)
unsafe_ctx_2.set_verify(SSL.VERIFY_NONE)

After

python
# Safe OpenSSL code
from OpenSSL import SSL

ctx = SSL.Context(SSL.TLSv1_2_METHOD)
ctx.set_default_verify_paths()
ctx.set_verify(SSL.VERIFY_PEER)

Explanation:

  • Before: Peer validation is absent without an appropriate set_verify configuration or disabled with SSL.VERIFY_NONE.
  • After: Load the default trust store and enable chain validation with SSL.VERIFY_PEER. Configure peer hostname validation separately. A custom callback must not ignore verification failures. Options such as VERIFY_FAIL_IF_NO_PEER_CERT that require client authentication belong to the appropriate server configuration.

Requests

Before

python
# Unsafe Requests code
import requests

requests.get('https://example.com', verify=False)

After

python
# Safe Requests code
import requests

requests.get('https://example.com')

Validation using a trusted private CA certificate:

python
# Safe Request code with self-signed certificate
import requests

requests.get('https://private.com', verify="/path/to/private_cert")

Explanation

  • Before: verify=False disables certificate validation and permits man-in-the-middle attacks.
  • After:
    • Remove verify=False or use verify=True to enable certificate validation.
    • For a private CA, set verify to the trusted CA bundle’s path. Do not resolve errors by trusting arbitrary certificates or disabling validation.

HTTPX

Before

python
# Unsafe HTTPX code
import httpx

httpx.get('https://example.com', verify=False)

After

python
# Safe HTTPX code
import httpx

httpx.get('https://example.com')

Validation using a trusted private CA certificate:

python
# Safe HTTPX code with a trusted private CA
import ssl
import httpx

ctx = ssl.create_default_context(cafile="/path/to/private_cert")
httpx.get('https://private.com', verify=ctx)

If the server requires client authentication, add a client certificate as follows. This does not replace the CA configuration used to trust the server certificate.

python
import ssl
import httpx

ctx = ssl.create_default_context()
ctx.load_cert_chain(certfile="path/to/client.pem")
client = httpx.Client(verify=ctx)
response = client.get("https://example.org")

Explanation

  • Before: verify=False disables certificate validation.
  • After:
    • Keep server certificate verification enabled.
    • To trust a private CA, pass an SSLContext that loads it through verify. The client certificate and private key separately establish the client’s identity to the server.

aiohttp

Before

python
# Unsafe aiohttp code
import aiohttp

async def main():
    async with aiohttp.ClientSession() as session:
        async with session.get("https://example.com", ssl=False):
            pass
        async with session.get("https://legacy.example.com", verify_ssl=False):
            pass

After

python
# Safe aiohttp code
import aiohttp

async def main():
    async with aiohttp.ClientSession() as session:
        async with session.get("https://example.com"):
            pass

Explanation

  • Before: ssl=False or verify_ssl=False disables certificate validation.
  • After:
    • Remove these options or provide a trusted ssl.SSLContext when necessary. The asynchronous context manager executes the request and manages the response.

urllib3

Before

python
# Unsafe urllib3 code
import urllib3

http = urllib3.PoolManager(cert_reqs='CERT_NONE')
http.request('GET', 'https://example.com')

After

python
# Safe urllib3 code
import urllib3

http = urllib3.PoolManager(cert_reqs='CERT_REQUIRED', ca_certs='/path/to/ca-bundle.crt')
http.request('GET', 'https://example.com')

Explanation:

  • Before: cert_reqs='CERT_NONE' disables certificate validation.
  • After: Use cert_reqs='CERT_REQUIRED'. If a private CA is needed, set ca_certs to the appropriate trusted certificate bundle.

References