Safeguard
Vulnerability Analysis

CVE-2020-10109: Denial of service in Twisted via 100-cont...

CVE-2020-10109 lets attackers hang Twisted's HTTP server with malformed 100-continue requests, exhausting resources until it stops responding.

Aman Khan
AppSec Engineer
7 min read

A malformed HTTP request that never resolves can be just as damaging as a crash. CVE-2020-10109 is a denial-of-service vulnerability in the Twisted networking framework's twisted.web HTTP server, where a client that sends a HEAD request with an Expect: 100-continue header and a request body can cause the server-side connection handling to hang. An attacker who opens enough of these connections can tie up server resources indefinitely, degrading or denying service to legitimate users, without needing authentication or any special access.

Twisted is a widely used, event-driven networking engine for Python. It underpins the HTTP client/server stack in projects far beyond its own name recognition — Scrapy's crawler engine, Buildbot's CI infrastructure, and a range of custom async services all build on twisted.web or Twisted's lower-level protocol machinery. A hang bug in the HTTP request path isn't a niche problem confined to people who deployed "Twisted the web server" — it's a problem for anything that imports Twisted to speak HTTP.

What the vulnerability is

HTTP/1.1's Expect: 100-continue mechanism exists so a client can ask a server "are you willing to accept this request body?" before actually sending the body. The server is supposed to respond with an interim 100 Continue status, after which the client sends the body, or the server can reject the request outright.

HEAD requests are a bit of a special case in HTTP: they ask the server to compute a response as if it were a GET, but return only headers, never a body. Twisted's twisted.web HTTP server did not correctly account for the interaction between the 100-continue producer/consumer flow and a HEAD request. When a client sent a HEAD request that included both a body and an Expect: 100-continue header, the internal producer responsible for eventually completing or tearing down the connection never got signaled to stop. The result was a connection that neither resolved nor timed out cleanly — a resource that stayed allocated on the server for as long as the attacker cared to leave it open.

That's a classic ingredient for denial of service: no crash, no memory corruption, just a small, cheap request that costs the attacker almost nothing but consumes a server-side resource (a connection, a file descriptor, associated buffers and reactor bookkeeping) that never gets freed. Repeat it enough times, in parallel, and a server can exhaust its connection pool or thread/reactor capacity, leaving it unable to serve legitimate traffic.

Affected versions and components

  • Component: twisted.web, the HTTP server implementation shipped as part of the Twisted Python package.
  • Affected versions: Twisted releases prior to 20.3.0.
  • Fixed version: 20.3.0, which corrected the 100-continue/HEAD interaction so the producer is properly stopped and the connection can be resolved normally.

Because Twisted is a dependency rather than a standalone application most teams deploy directly, the practical exposure here depends on what you built on top of it. Anything exposing a twisted.web server (or a framework layered on it) to untrusted network input — public APIs, webhooks receivers, proxied internal services reachable from less-trusted network segments — inherits this exposure until Twisted itself is upgraded.

CVSS, EPSS, and KEV context

NVD's entry for CVE-2020-10109 scores it as a CVSS v3.1 base score in the High range, reflecting a network-exploitable, low-complexity issue that requires no privileges or user interaction and results in a high impact to availability, with no impact to confidentiality or integrity. In plain terms: an unauthenticated attacker on the network can trigger it with a single crafted request, and the resulting damage is purely to uptime, not to data.

EPSS scoring for this CVE has stayed low, consistent with how these kinds of connection-hang DoS bugs tend to behave in practice — they require sustained, repeated requests to produce meaningful impact rather than a single payload that reliably drops a service, so they see far less automated scanning and exploitation activity than remote code execution bugs. CVE-2020-10109 does not appear in CISA's Known Exploited Vulnerabilities (KEV) catalog, and there is no public record we're aware of indicating active, widespread exploitation. That absence from KEV is useful context, but it isn't a reason to leave the fix off a patch backlog — availability-impacting bugs in a networking library are exactly the kind of finding that gets rediscovered by anyone stress-testing your endpoints, whether they're a security researcher or an attacker probing for a cheap way to degrade your service.

Timeline

  • Twisted's maintainers identified and fixed the 100-continue/HEAD handling issue alongside other HTTP-handling hardening work in the twisted.web server.
  • The fix shipped in Twisted 20.3.0, released in April 2020.
  • CVE-2020-10109 was assigned and published to track the issue, allowing downstream consumers and vulnerability scanners to identify affected dependency versions.
  • As with most dependency-level CVEs, the practical remediation timeline for any given organization runs well past the upstream fix date — it depends on when each downstream project bumped its Twisted pin, when that update reached a deployed build, and when scanning caught any instances still running an older version.

We're not going to invent a more granular day-by-day disclosure timeline than what's reliably documented upstream; the reliable anchor points are the affected version range, the fixed release, and the CVE record itself.

Remediation steps

  1. Upgrade Twisted to 20.3.0 or later. This is the definitive fix — the producer/consumer lifecycle for 100-continue handling was corrected so HEAD requests with bodies no longer leave a dangling, unresolved connection state.
  2. Audit transitive dependencies. Twisted is frequently pulled in indirectly through frameworks and tools (crawlers, CI/CD daemons, message queues, custom async services). Check your dependency tree — not just your direct requirements.txt or pyproject.toml — for any Twisted version below 20.3.0.
  3. If an immediate upgrade isn't possible, add a compensating control at the edge. A reverse proxy or load balancer (nginx, HAProxy, an API gateway) placed in front of the Twisted-based service can normalize or strip Expect headers, reject HEAD requests carrying a body, and enforce request/connection timeouts — reducing the attack surface while the underlying upgrade is scheduled.
  4. Set connection and idle timeouts at the application or infrastructure layer. Even independent of this specific CVE, aggressive but sane timeouts limit the blast radius of any future "connection never resolves" class of bug.
  5. Re-scan after patching. Confirm the new Twisted version is actually what's running in production, not just what's declared in a manifest — lockfile drift and multi-stage build caching are common reasons a documented fix doesn't make it to a running service.

How Safeguard Helps

CVE-2020-10109 is a good illustration of why supply chain visibility has to go deeper than "what's in requirements.txt." Twisted rarely appears as a direct, top-level dependency in most organizations' inventories — it shows up nested three or four layers down, pulled in by a crawler library, a CI tool, or an internal service framework nobody remembers choosing Twisted for. That's exactly the kind of exposure that's easy to miss with point-in-time audits and easy to catch with continuous software composition analysis.

Safeguard continuously inventories the full dependency graph across your codebases and build artifacts — direct and transitive — and maps known CVEs like this one against the exact versions actually running in your environment, not just what's declared in a manifest. When a vulnerability like CVE-2020-10109 is disclosed, or when your build pulls in a service that transitively depends on an old Twisted release, Safeguard flags it with the affected component, the fixed version, and enough context (CVSS severity, exploitation likelihood, KEV status) to help you prioritize the fix against everything else competing for engineering time.

Because availability-impacting bugs like this one rarely trigger the same urgency as remote code execution, they're exactly the class of finding that quietly ages in a backlog. Safeguard's continuous monitoring closes that gap — surfacing the dependency, tracking whether the fix has actually shipped to production, and giving security and engineering teams a shared, verifiable record that the exposure was addressed rather than just noted and forgotten.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.