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
requestswith 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
# 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 assigningssl.CERT_NONEtoverify_modedisable certificate validation. The example disables hostname checking before assigningCERT_NONE. - After: A default client context verifies the certificate chain and hostname. Pass the correct
server_hostnamewhen wrapping the socket.
OpenSSL
Before
# 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
# 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_verifyconfiguration or disabled withSSL.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 asVERIFY_FAIL_IF_NO_PEER_CERTthat require client authentication belong to the appropriate server configuration.
Requests
Before
# Unsafe Requests code
import requests
requests.get('https://example.com', verify=False)
After
# Safe Requests code
import requests
requests.get('https://example.com')
Validation using a trusted private CA certificate:
# Safe Request code with self-signed certificate
import requests
requests.get('https://private.com', verify="/path/to/private_cert")
Explanation
- Before:
verify=Falsedisables certificate validation and permits man-in-the-middle attacks. - After:
- Remove
verify=Falseor useverify=Trueto enable certificate validation. - For a private CA, set
verifyto the trusted CA bundle’s path. Do not resolve errors by trusting arbitrary certificates or disabling validation.
- Remove
HTTPX
Before
# Unsafe HTTPX code
import httpx
httpx.get('https://example.com', verify=False)
After
# Safe HTTPX code
import httpx
httpx.get('https://example.com')
Validation using a trusted private CA certificate:
# 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.
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=Falsedisables certificate validation. - After:
- Keep server certificate verification enabled.
- To trust a private CA, pass an
SSLContextthat loads it throughverify. The client certificate and private key separately establish the client’s identity to the server.
aiohttp
Before
# 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
# 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=Falseorverify_ssl=Falsedisables certificate validation. - After:
- Remove these options or provide a trusted
ssl.SSLContextwhen necessary. The asynchronous context manager executes the request and manages the response.
- Remove these options or provide a trusted
urllib3
Before
# Unsafe urllib3 code
import urllib3
http = urllib3.PoolManager(cert_reqs='CERT_NONE')
http.request('GET', 'https://example.com')
After
# 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, setca_certsto the appropriate trusted certificate bundle.