In May 2021, the maintainers of urllib3 — the HTTP client library that sits underneath requests, pip, botocore, and a large share of the Python ecosystem's networking stack — shipped a quiet but important fix for a Regular Expression Denial of Service (ReDoS) flaw tracked as CVE-2021-33503. The bug lives in the URL-parsing logic urllib3 uses to split a URL into its component parts (scheme, userinfo, host, port, path). A specially crafted URL — one an attacker can often influence simply by controlling a redirect target, a webhook callback, an image URL, or any other user-supplied endpoint that a vulnerable service fetches — could force the parser into catastrophic backtracking, pinning a CPU core at 100% and stalling or crashing the process that tried to parse it. Because urllib3 is vendored deep inside thousands of dependency trees, the practical blast radius of this CVE was far larger than a single library update suggests, and many organizations are still running affected versions today without realizing it.
What the vulnerability actually is
urllib3's parse_url() function is responsible for breaking a URL string into its logical pieces before the library builds a connection. To do this efficiently across edge cases (IPv6 literals, userinfo credentials, unusual port formatting, etc.), older urllib3 releases relied on a single, densely constructed regular expression to identify the "authority" component — the user:password@host:port segment of a URL. Regexes built this way are prone to a well-known class of bug: when a pattern contains ambiguous, overlapping quantifiers, the regex engine can be forced to try an exponential number of backtracking paths before it fails to match.
An attacker doesn't need code execution or network access to trigger this — they only need to get a malicious string in front of a call to parse_url(). In practice that means:
- A webhook, callback URL, or redirect target that an application fetches on the attacker's behalf.
- A URL embedded in a document, feed, or API payload that gets passed to
requests.get()downstream. - Any service that accepts a "fetch this URL" parameter from an untrusted caller — SSRF-adjacent surface area that already shows up on most red-team checklists.
By padding the authority portion of the URL with a long, carefully chosen sequence of characters that almost — but never quite — matches the pattern, the attacker forces the regex engine into worst-case backtracking. A single request with a URL a few hundred characters long can consume seconds to minutes of CPU time on the parsing thread, and because Python's re module runs backtracking on the calling thread, this is a straightforward way to degrade or hang a worker process, exhaust a thread pool, or tip over a service that processes URLs on a hot path (proxies, crawlers, image fetchers, link previewers, API gateways built on urllib3-based clients).
This is a classic ReDoS (CWE-1333: Inefficient Regular Expression Complexity) pattern, and it's a good reminder that denial-of-service bugs don't require memory corruption or logic flaws — a single poorly bounded regex in a widely reused library is enough.
Affected versions and components
- urllib3: all 1.x releases prior to 1.26.5 are affected. Because urllib3 1.x was the dominant branch in production for years (urllib3 2.0 did not exist yet in 2021), this covers an enormous swath of real-world deployments.
- Indirect exposure through popular dependents, including but not limited to:
requests(bundles/depends on urllib3 for nearly all HTTP calls)botocore/boto3(AWS SDK for Python)pip(vendors urllib3 internally for package downloads)- Kubernetes Python client, Docker SDK for Python, and many other infrastructure tools
- Countless internal services, crawlers, webhook processors, and API clients built on
requestsor urllib3 directly
Because urllib3 is frequently vendored or pinned as a transitive dependency several layers deep, a straightforward pip list or top-level requirements.txt review often misses it entirely — teams need to resolve the full dependency graph, including packages that bundle their own copy of urllib3, to know their real exposure.
Severity, exploit likelihood, and KEV status
- CVSS v3.1: NVD scores CVE-2021-33503 at 7.5 (High), reflecting a network-exploitable, low-complexity, no-authentication-required denial-of-service vector (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H). No confidentiality or integrity impact — this is purely an availability issue.
- EPSS: Exploit prediction scores for this CVE have historically sat in the low-to-moderate range. It's not the kind of bug that lends itself to mass, opportunistic internet scanning the way an RCE would, because exploitation requires an application-specific delivery path (a place where attacker-influenced URLs reach
parse_url()). That said, low EPSS should not be read as "safe to ignore" — SSRF-style delivery paths for this bug are common in real applications, and DoS conditions are cheap for an attacker to attempt repeatedly. - CISA KEV: As of this writing, CVE-2021-33503 does not appear on CISA's Known Exploited Vulnerabilities catalog. There is no confirmed evidence of widespread active exploitation, but the absence from KEV should not be conflated with absence of risk — plenty of ReDoS bugs cause real production incidents without ever generating the kind of signal that lands a CVE on that list.
Timeline
- ~Early 2021 — The vulnerable regex pattern in
urllib3/util/url.pyis identified and reported through coordinated disclosure to the urllib3 maintainers. - May 24, 2021 — urllib3 1.26.5 is released, replacing the vulnerable authority-parsing regex with a safer, non-backtracking implementation and hardening
parse_url()against pathological inputs. - June 2021 — CVE-2021-33503 is published in the NVD, along with the corresponding GitHub Security Advisory, formally documenting the ReDoS weakness and affected version range.
- Ongoing — Downstream projects that vendor or pin urllib3 (
pip,botocore, various Linux distro packages) issue their own follow-on updates over the following months as they pick up the patched release.
Remediation
- Upgrade urllib3 to 1.26.5 or later (or to a current 2.x release, all of which include the fix). This is a low-risk, backward-compatible patch — there is no reason to delay it behind a broader dependency refresh.
- Audit your full dependency graph, not just top-level requirements. Use
pip show,pipdeptree, or an SBOM tool to confirm every path by which urllib3 enters your environment — directly, viarequests, viabotocore, or vendored inside another package — and confirm each resolves to a patched version. - Pin and re-lock. Update
requirements.txt,Pipfile.lock,poetry.lock, or equivalent, and rebuild any container images or Lambda layers that bake in a frozen dependency set rather than resolving at deploy time. - Identify reachable call paths. Look specifically at services that parse externally supplied URLs: webhook receivers, link previewers, image proxies, SSRF-adjacent "fetch this resource" endpoints, and crawlers. These are the components where this CVE actually translates into exploitable risk.
- Add defensive limits regardless of patch status. Enforce maximum URL length at the ingress layer, apply request timeouts around any outbound fetch, and rate-limit endpoints that accept attacker-controlled URLs. These controls reduce blast radius for this bug and for the next parsing-related CVE that comes along.
- Re-scan after patching. Confirm the resolved version in your lockfiles and running containers actually matches what you intended — transitive pins and stale base images are a common reason "fixed" dependencies reappear in production weeks later.
How Safeguard Helps
Safeguard is built to close the gap between "a CVE exists in my dependency tree" and "I know whether it actually matters." Our reachability analysis traces whether attacker-influenced input can actually reach the vulnerable parse_url() code path in your specific services, so teams can prioritize the webhook handler or crawler that's genuinely exposed over the internal batch job that never parses untrusted URLs. Griffin, Safeguard's AI-driven analysis engine, correlates that reachability signal with CVSS, EPSS, and KEV context — plus how urllib3 entered your stack (direct dependency, requests, botocore, or vendored copy) — to produce a risk ranking that reflects your actual exposure, not just NVD severity. Continuous SBOM generation and ingestion give you a live, dependency-graph-aware inventory that surfaces every vendored or transitive copy of urllib3 across your fleet, including the ones a simple pip list would never catch. And where remediation is straightforward, Safeguard can open an auto-fix pull request that bumps urllib3 to a patched release and updates the relevant lockfile, so the fix ships as fast as your review process allows rather than sitting in a backlog.