If you’re using the QUIC protocol in your applications through the popular Go library quic-go, there’s an important vulnerability you need to know about: CVE-2023-39321. This bug lets an attacker crash your server by sending an incomplete message right after the handshake. In this post, I’ll break down how it works, show you a simplified exploit, and link to all the original references.
What’s QUIC and What’s the Issue?
QUIC is a transport protocol built on top of UDP, designed to be fast and secure—Google uses it for Chrome and YouTube, and HTTP/3 is built on top of it.
The Go library quic-go lets you use QUIC in your own apps. But in July 2023, a security advisory revealed that the library mishandled some messages after the handshake (that is, after the initial secure connection is set up).
If an attacker sends a partial or incomplete post-handshake message, your server will panic and crash. No advanced hacking skills required—just send the wrong data at the right time.
The Vulnerable Code
The problem is in how the server handles post-handshake messages. When it gets data, it doesn’t always check if everything needed is actually there, so accessing missing data causes a runtime panic.
Here’s a simplified Go snippet similar to the root of the bug
func handlePostHandshakeMessage(data []byte) {
// The length of the message is in the first two bytes.
length := binary.BigEndian.Uint16(data[:2])
// Get the message of given length.
msg := data[2 : 2+length]
// ... process message
}
If an attacker sends something like one byte instead of two or a smaller payload than length, this code will panic with:
panic: runtime error: slice bounds out of range.
Send a post-handshake message that announces a big length, but actually send almost no data.
You can use the quic-go library itself to test this locally.
Example with Python using UDP socket (non-handshake, just for illustration)
import socket
# Connect to QUIC port, default 4433
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.sendto(b'\x00\x10', ("localhost", 4433)) # Claims message length of 16 (x10), but only sends 2 bytes (header).
s.close()
In practice, you’d need to finish the handshake stage before sending this malformed message, but this is the core idea: the server expects more data than it gets, which crashes it.
Real-World Impact
A QUIC-based Go server (using affected versions of quic-go) can be remotely crashed—no privilege escalation or code execution, but a crash is often enough for DoS (Denial of Service).
If you use QUIC in your API, service, microservice, gRPC, or mesh infrastructure, *you are potentially vulnerable*.
Patches and Fixes
The maintainers fixed the bug in version quic-go v.37.3:
> *"Fix a crash when processing incomplete post-handshake messages."*
If you can, update immediately.
If you can’t, at least validate the length of incoming post-handshake messages before parsing.
Fixed Code Pattern
func handlePostHandshakeMessage(data []byte) error {
if len(data) < 2 { return errors.New("data too short") }
length := binary.BigEndian.Uint16(data[:2])
if len(data[2:]) < int(length) {
return errors.New("incomplete message")
}
msg := data[2 : 2+length]
// ... process message safely
}
References
- Original Advisory - GHSA-xg62-6rhp-gvj4
- NVD CVE-2023-39321
- Release fix v.37.3
- QUIC Protocol
In Summary
- CVE-2023-39321 allows attackers to crash Go servers by sending broken post-handshake QUIC messages.
All public-facing QUIC services should upgrade!
Always validate your input—even after the handshake.
Questions? Comments? Let me know below.
Timeline
Published on: 09/08/2023 17:15:28 UTC
Last modified on: 11/25/2023 11:15:17 UTC