In April 2019, maintainers of urllib3 — the HTTP client library underpinning pip, requests, botocore, and thousands of downstream Python packages — disclosed CVE-2019-11324, a certificate validation flaw that could let SSL/TLS connections succeed even when they should have failed. Under specific configurations, urllib3 versions before 1.24.2 would silently trust certificates issued by CAs outside the certificate set an application explicitly configured, undermining the entire point of pinning to a custom trust store. For any service that relies on urllib3's ca_certs, ca_certs_dir, or ssl_context parameters to restrict which certificate authorities are trusted — a common pattern in internal PKI, mutual TLS, and zero-trust service meshes — this bug meant an attacker holding a certificate from any CA in the operating system's default trust store could potentially intercept traffic that the application believed was locked down to a narrower, trusted set.
What went wrong
urllib3 wraps Python's ssl module to manage TLS handshakes for every HTTPS request it makes. When a caller supplied a custom CA bundle via ca_certs or ca_certs_dir, or passed a pre-built ssl_context, the reasonable expectation was that urllib3 would validate the peer certificate strictly against that custom set and reject anything not signed by one of those CAs. Instead, in versions prior to 1.24.2, urllib3's SSL wrapping logic in util/ssl_.py could still load and honor the operating system's default certificate store in addition to the caller-specified certificates. The practical effect: certificate verification appeared to succeed, but the acceptance criteria were broader than the caller intended. A connection presenting a certificate chain rooted in a CA trusted by the OS — but never authorized by the application's own restricted CA list — could pass as "verified."
This is classified as CWE-295: Improper Certificate Validation, and it's a textbook example of why supply-chain security teams care about transitive dependencies as much as direct ones: most engineering teams never call urllib3 directly, they call requests, pip, an AWS SDK, or a dozen other libraries that vendor or depend on it.
Affected versions and components
- Affected: urllib3 releases prior to 1.24.2.
- Fixed in: urllib3 1.24.2, released April 2019.
- Trigger conditions: applications or libraries that explicitly pass
ssl_context,ca_certs, orca_certs_dirto urllib3'sHTTPSConnectionPool/PoolManagerin order to restrict trusted CAs, rather than relying on urllib3's default (unmodified) trust behavior. - Downstream exposure: because urllib3 is vendored inside
pipand widely imported byrequests,botocore, and countless internal HTTP clients, the vulnerable code shipped far beyond anyone who typedpip install urllib3directly. Distro-maintained Python packages (Debian, Ubuntu, Red Hat/Fedora, SUSE, Amazon Linux) all carried patched builds and issued their own advisories, which is a strong signal of how deep the dependency tree went.
Severity, exploit context, and KEV status
NVD scores CVE-2019-11324 as CVSS v3.0 base score 7.5 (High) — AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N — reflecting a network-exploitable flaw with low attack complexity, no privileges or user interaction required, and a high integrity impact (a forged or unintended certificate chain can be trusted). The legacy CVSS v2.0 score of 5.0 (Medium) reflects the older scoring model's more conservative treatment of integrity-only impacts. Some newer scoring feeds have recalculated the issue under CVSS v4.0 in the high range as well, consistent with the v3 assessment.
This CVE is not listed in CISA's Known Exploited Vulnerabilities (KEV) catalog, and there is no public evidence of widespread in-the-wild exploitation — the flaw requires an attacker to already be positioned as a man-in-the-middle and to hold or forge a certificate from a CA in the target's OS trust store, which is a nontrivial bar. EPSS scoring for CVEs this old tends to sit low, since exploit prediction models weight recency and active-exploitation signals heavily. That said, low EPSS doesn't mean low relevance: this is exactly the class of "silent trust downgrade" bug that matters most for organizations running internal CAs, mutual TLS between microservices, or air-gapped/on-prem environments where the entire security model depends on not trusting the public CA ecosystem. If your threat model assumes urllib3 rejects anything outside your custom CA bundle, this bug quietly broke that assumption for years in unpatched deployments.
Timeline
- Discovered/reported: Identified by security researchers evaluating urllib3's SSL context handling logic in early 2019.
- April 2019: Fix landed upstream; urllib3 1.24.2 released, correcting the CA-loading logic so that specifying
ssl_context,ca_certs, orca_certs_dirno longer results in the OS default trust store being loaded alongside the caller's intended set. - April 18–19, 2019: CVE-2019-11324 published in NVD and the GitHub Advisory Database (GHSA-mh33-7rrq-662w).
- Mid-to-late 2019: Downstream distributions and vendors issued their own advisories and patched builds — Red Hat (RHSA-2019:3335, RHSA-2019:3590), Ubuntu (USN-3990-1), Debian, Fedora, and SUSE all shipped fixes tracking the same upstream patch.
- 2019–2020: Package ecosystems that vendor urllib3 (notably
pip) tracked and resolved the issue in their own vendored copies, sometimes with a longer tail than the standalone PyPI package. - Ongoing: The CVE remains a standard finding in SCA (software composition analysis) scans of any codebase or container image still pinned to a pre-1.24.2 urllib3, more than six years after disclosure — a reminder of how long transitively-pinned dependencies can persist in build systems, base images, and lockfiles.
Remediation steps
- Upgrade urllib3 to 1.24.2 or later. For most modern applications, pinning to any current 1.x or 2.x release resolves this and a long list of subsequent CVEs (including the unrelated but frequently co-occurring CVE-2018-20060 authorization-header-leak and CVE-2019-11236 CRLF-injection issues fixed in the same era).
- Audit indirect dependencies, not just direct ones. Check
requests,botocore/boto3,pipitself, and any internal HTTP wrapper libraries for a vendored or pinned urllib3 version.pip list,pip-audit,poetry show --tree, or an SBOM diff will surface transitive pins that a simplerequirements.txtgrep misses. - Regenerate and re-verify lockfiles. After bumping the version, regenerate
requirements.txt/poetry.lock/Pipfile.lockand confirm no other package re-pins an older urllib3 as a hard dependency. - Re-audit custom CA/mTLS configurations. If your services pass
ca_certs,ca_certs_dir, or a customssl_contextto urllib3 (directly or viarequests'verify=parameter pointed at a custom bundle), explicitly test that connections presenting certificates from CAs outside your intended set are rejected post-upgrade. Don't assume the fix is effective without a negative test. - Rebuild and redeploy affected container images. Base images built before mid-2019 (or any image that froze a pre-2019 Python dependency layer) should be rebuilt from current base layers, since the vulnerable library may be baked into an OS package layer rather than an application-level dependency.
- Extend the check to CI/CD and build tooling itself. Because
pipvendors urllib3, olderpipbinaries used in build pipelines can carry the vulnerable code even when application-level dependencies are current — patch the build environment, not just the application.
How Safeguard Helps
Six-year-old CVEs like this one don't stay fixed by accident — they resurface constantly in stale container base images, vendored dependencies, and forgotten build tooling, which is exactly the kind of drift Safeguard is built to catch continuously rather than at a single point in time. Safeguard's reachability analysis goes beyond "urllib3 is present" to determine whether your code actually exercises the vulnerable ssl_context/ca_certs code path, so security teams can separate real risk from noise instead of chasing every transitive hit. Griffin AI correlates the finding against your SBOM, dependency graph, and runtime context to explain the exploit conditions in plain language and prioritize fixes by actual exposure. Safeguard generates and ingests SBOMs across your services and container images to catch vendored or pinned copies of urllib3 that traditional manifest scans miss, and where a safe upgrade path exists, Safeguard can open an auto-fix pull request that bumps the dependency and regenerates lockfiles — turning a finding into a merged fix without manual triage.
Sources: