CWE-295: Improper Certificate Validation
Overview
Improper certificate validation occurs when an application fails to correctly verify the authenticity of SSL/TLS certificates during secure connections. This undermines the entire purpose of HTTPS by allowing attackers to intercept encrypted communications (man-in-the-middle attacks), impersonate legitimate servers, and steal credentials or sensitive data in transit.
SSL/TLS certificates serve two critical purposes: authentication (prove the server is who it claims to be) and encryption (establish a secure encrypted channel). When certificate validation is disabled or improperly implemented, encryption alone is insufficient - you may be encrypting data to an attacker's server.
OWASP Classification
A07:2025 - Authentication Failures
Risk
Certificate validation failures create severe security exposures:
- Man-in-the-middle (MITM) attacks: Attackers intercept all encrypted traffic
- Credential theft: Username/password pairs transmitted to attacker
- Session hijacking: Session tokens captured and reused
- Data exfiltration: Sensitive data read from encrypted connections
- API key exposure: Authentication tokens for external services stolen
- Payment data theft: Credit card numbers and financial information compromised
- Compliance violations: Fails PCI-DSS, SOC 2, HIPAA encryption requirements
MITM attacks are trivial to execute on public WiFi networks, and certificate validation failures make them undetectable to users.
Common Validation Failures
Certificate validation failures typically fall into these categories:
- Disabling validation entirely: Accepting all certificates regardless of validity
- Hostname verification bypass: Not checking hostname matches certificate
- Chain verification failure: Not verifying certificate chain to trusted root CA
- Ignoring certificate status: Accepting expired or revoked certificates
- Missing revocation checks: Not checking Certificate Revocation Lists (CRL) or OCSP
Language-Specific Guidance
For detailed, language-specific remediation guidance including vulnerable and secure code patterns:
- JavaScript/Node.js - https module rejectUnauthorized, axios httpsAgent, node-fetch configuration, TLS module, certificate pinning
- Python - requests verify=False, urllib3 cert_reqs, SSL context configuration, httpx, aiohttp validation
Remediation Steps
Core principle: TLS must validate certificates correctly (chain + hostname); never disable verification in production.
Locate the improper certificate validation in your code
- Review the flaw details to identify where certificate validation is disabled or bypassed
- Identify HTTPS connection points: API calls to external services, database connections over SSL/TLS, email servers, webhooks
- Search for dangerous patterns: "verify=False", "rejectUnauthorized: false", "TrustAllCertificates", "InsecureTrustManager"
- Check configuration files: application.properties, config files for certificate validation settings
Use default certificate validation (Primary Defense)
- Enable platform default validation: Use standard HTTPS libraries with default settings (they validate correctly by default)
- Let the platform handle validation: Don't implement custom certificate validation logic
- Trust OS certificate store: Rely on operating system's trusted root certificate authorities
- Remove bypass code: Delete any "verify=False", "rejectUnauthorized: false", custom TrustManagers that accept all certificates
- Never disable validation: Even in development, use self-signed certificates properly instead of disabling validation
Validate certificate chain and hostname properly
If custom validation is required, verify ALL aspects:
- Certificate signed by trusted CA: Check chain to root certificate authority
- Certificate not expired: Validate notBefore and notAfter dates
- Certificate not revoked: Check Certificate Revocation List (CRL) or OCSP
- Hostname matches certificate: Verify hostname matches Subject Alternative Name (SAN) or Common Name (CN)
- Certificate purpose matches usage: Ensure certificate is for Server Authentication
- Strong signature algorithm: Ensure SHA-256 or better (not MD5/SHA-1)
Remove development bypass code and use certificate pinning for high-security
- Remove development shortcuts: Delete "trust all certificates" classes, "ignore SSL errors" flags, custom TrustManagers
- Search codebase: Grep for "trust all", "accept all", "disable validation", "InsecureTrustManager"
- Remove environment bypass: Delete environment variables or debug flags that skip certificate checks
- Certificate pinning (optional): For critical connections, pin expected certificate public key hash, reject even valid-but-unexpected certificates
Monitor and audit SSL/TLS connections
- Review code for all HTTPS connection points and their validation configuration
- Log SSL/TLS handshake failures (may indicate MITM attempts or expired certificates)
- Monitor certificate expiration dates and renew before expiry
- Alert on certificate validation bypasses if found in code reviews
- Use static analysis tools to detect disabled validation
Test the certificate validation fix thoroughly
- Test with valid certificate from trusted CA (should connect successfully)
- Test with self-signed certificate (should reject unless explicitly trusted)
- Test with expired certificate (should reject)
- Test with wrong hostname (should reject due to hostname mismatch)
- Test with revoked certificate (should reject if CRL/OCSP checking enabled)
- Re-scan with security scanner to confirm the issue is resolved
1. Test with Valid Certificate
Your application should successfully connect to properly configured HTTPS servers.
Test with Self-Signed Certificate
# Connect to server with self-signed cert (common in testing)
curl https://self-signed.badssl.com/
# Expected: Certificate verification error
Expected result: Connection should fail with certificate verification error (unless you've explicitly added the CA to trust store).
Test with Expired Certificate
# Connect to server with expired cert
curl https://expired.badssl.com/
# Expected: Certificate has expired error
Expected result: Connection refused due to expired certificate.
Test Hostname Mismatch
Expected result: Connection refused due to hostname mismatch.
Automated Testing
Use test sites:
https://badssl.com/(various certificate issues)https://self-signed.badssl.com/(self-signed)https://expired.badssl.com/(expired)https://wrong.host.badssl.com/(hostname mismatch)https://revoked.badssl.com/(revoked certificate)
All should fail validation in your application
Code Review Verification
Verify:
- No TrustAllCertificates implementations remain
- No certificate validation bypass flags
- Default validation is used
- No SSL error suppression
- Production config differs from development
Migration Considerations
IMPORTANT: Enabling certificate validation may break integrations with self-signed certificates or expired certificates.
What Breaks
- Dev/staging environments: Often use self-signed certificates
- Internal APIs: May have self-signed or corporate CA certificates
- Legacy systems: Old servers with expired certificates
- Partner integrations: Third-party APIs with certificate issues
- Testing environments: Mock servers with invalid certificates
Migration Approach
Gradual Rollout (Recommended)
-
Phase 1: Log validation failures without blocking
-
Phase 2: Identify problematic endpoints from logs
-
Phase 3: Fix certificate issues or add trusted CAs
# Add corporate CA to trust store
import certifi
import ssl
def create_ssl_context():
context = ssl.create_default_context(cafile=certifi.where())
# Add corporate CA certificate
context.load_verify_locations('corporate-ca.pem')
return context
- Phase 4: Enable enforcement
def verify_certificate(hostname, cert):
if not check_certificate_validity(cert):
logger.error(f"Blocked connection to {hostname}: invalid certificate")
raise SSLError("Certificate validation failed")
Environment-Specific Configuration
import os
import requests
def get_api_data(url):
# Only disable verification in development
verify_ssl = os.getenv('ENVIRONMENT') != 'development'
if not verify_ssl:
logger.warning(f"SSL verification disabled for {url} (dev mode)")
response = requests.get(url, verify=verify_ssl)
return response.json()
Add Specific Certificates to Trust Store
# Python
import ssl
import certifi
ssl_context = ssl.create_default_context(cafile=certifi.where())
ssl_context.load_verify_locations('internal-ca.pem')
# Java
import java.security.KeyStore;
import javax.net.ssl.TrustManagerFactory;
// Load custom trust store
KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load(new FileInputStream("truststore.jks"), password);
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(trustStore);
Rollback Procedures
If certificate validation breaks critical services:
- Feature flag rollback:
if config.get('STRICT_CERT_VALIDATION'):
verify_certificate(hostname, cert)
else:
logger.warning("Certificate validation bypassed (feature flag)")
- Allowlist specific hosts temporarily:
CERT_VALIDATION_EXEMPT = [
'legacy-api.internal.company.com',
'old-partner-api.example.com'
]
def should_verify_cert(hostname):
return hostname not in CERT_VALIDATION_EXEMPT
- Add trusted CA quickly:
# Add corporate CA to system trust store
cp corporate-ca.crt /usr/local/share/ca-certificates/
update-ca-certificates
Testing Recommendations
- Test connections to production APIs (valid certificates)
- Test rejection of self-signed certificates
- Test rejection of expired certificates
- Test hostname mismatch detection
- Test corporate CA certificates work
- Test dev environment bypass still works
- Document all endpoints with custom certificates
Certificate Testing Checklist:
import pytest
import requests
def test_valid_certificate_works():
# Should connect successfully
response = requests.get('https://www.google.com', verify=True)
assert response.status_code == 200
def test_self_signed_certificate_rejected():
# Should fail with SSL error
with pytest.raises(requests.exceptions.SSLError):
requests.get('https://self-signed.badssl.com/', verify=True)
def test_expired_certificate_rejected():
# Should fail with SSL error
with pytest.raises(requests.exceptions.SSLError):
requests.get('https://expired.badssl.com/', verify=True)
def test_corporate_ca_works():
# Add corporate CA
context = create_ssl_context_with_corporate_ca()
# Should work with corporate cert
response = requests.get('https://internal-api.company.com', verify=context)
assert response.status_code == 200
Dynamic Scan Guidance
For guidance on remediating this CWE when detected by dynamic (DAST) scanners:
- Dynamic Scan Guidance - Analyzing DAST findings and mapping to source code