Skip to content

CWE-296: Improper Following of a Certificate's Chain of Trust

Overview

Improper following of a certificate's chain of trust occurs when applications fail to verify that an X.509 certificate chains properly to a trusted root Certificate Authority (CA). This allows attackers to use self-signed certificates, certificates signed by untrusted CAs, incomplete chains, or chains with invalid intermediate CA certificates to impersonate legitimate servers in man-in-the-middle (MITM) attacks. The vulnerability commonly arises from disabled certificate validation during development that remains in production, custom TLS implementations that skip chain verification, incorrect trust store configuration, or pinning logic that pins a certificate before validating the complete chain.

Relationship to Other CWEs

OWASP Classification

A04:2025 - Cryptographic Failures

Risk

High: An attacker already on the network path presents a certificate the client accepts without a valid chain, and the TLS session then protects nothing: credentials and session tokens sent over it are readable, and injected responses are indistinguishable from the real server's. That network position is the precondition - public Wi-Fi, a compromised network, or control of DNS or routing.

Remediation Steps

Core Principle: Always validate the complete certificate trust chain to a known trusted root CA or explicitly configured trust anchor; do not disable certificate validation or trust unverified certificates in application code.

Enable Full Certificate Chain Validation

Confirm the TLS/SSL library validates:

  • Certificate chains to a trusted root CA
  • Certificate is not self-signed unless it is the trusted root or an explicitly configured trust anchor
  • Intermediate certificates are included and valid
  • Intermediate CA certificates have appropriate Basic Constraints and key usage
  • Certificate has not expired
  • Certificate revocation status is checked when required by your client stack, PKI, or policy

Use Proper TLS Configuration

Every mainstream HTTP client validates the chain by default, so the fix is almost always the removal of an option that switched validation off, not the addition of new code. The flag has a different name in each ecosystem - a verify parameter, a reject-unauthorized option, a permissive trust manager, a callback that returns true unconditionally - and CWE-295 carries the concrete form for Python, JavaScript, Java, C# and Go.

Two cases specific to chain of trust rather than validation in general:

  • Supplying a custom CA replaces the default trust store in most clients rather than adding to it. Internal services then work while every public host fails, and the usual response is to disable validation entirely. Include the platform defaults alongside your CA.
  • Pinning before validating is not pinning. A pin check that runs first is comparing the pinned hash against some certificate in the presented chain, not against a verified leaf. An attacker attaches the pinned CA or intermediate to a chain terminating in a leaf of their own: the pin matches the certificate it was taken from, and nothing ever verifies the signatures tying that certificate to the leaf the connection actually uses. Validate the chain, then pin the verified leaf.

Pin Public Keys for Critical Connections (Advanced)

Pin expected public key/SPKI hashes only after the normal chain and hostname validation succeeds. Keep backup pins and a rotation process.

Use the Runtime's Default Trust Store

Rely on the trust store the runtime already uses rather than assembling one:

  • Python: ssl.create_default_context(), which loads the OS store; requests uses the certifi bundle instead
  • Node.js: a bundled copy of the Mozilla root list, extended with NODE_EXTRA_CA_CERTS or a carefully scoped ca option for internal clients
  • Java: the JDK's own cacerts keystore
  • .NET: the trust store of whichever operating system it is running on - the Windows certificate store, OpenSSL's CA bundle on Linux, the Keychain on macOS

None of these is guaranteed to be the machine's CA store, and whether adding a CA there reaches cacerts or Node's bundled list is a packaging question rather than a language one - so add the CA, then make one request from the runtime itself. CWE-295 has the detail; the mismatch is a routine reason validation was switched off in the first place.

Test Certificate Validation

Verify your implementation rejects invalid certificates and still accepts valid ones:

# Baseline: a valid public certificate must still connect
curl https://badssl.com/                # expect: 200

# Test with self-signed certificate (should fail)
curl https://self-signed.badssl.com/

# Test with expired certificate (should fail)
curl https://expired.badssl.com/

# Test with wrong host (should fail)
curl https://wrong.host.badssl.com/

The baseline is the test that matters most for this CWE, because the fix here is often a custom CA - and a custom CA that replaces the default trust store instead of adding to it passes all three rejection tests while breaking every public endpoint. Run all four through the application's own HTTP client as well as curl, so the code path that carried the finding is the one exercised.

Additional Resources