In early 2023, a serious vulnerability was found in curl, affecting versions prior to 7.88., known as CVE-2023-23916. This bug exposes clients to potential denial-of-service attacks because of how curl handles certain HTTP compression scenarios, especially with “chained” encodings.

If you’re a developer, sysadmin, or anyone who uses or operates software using curl, understanding this vulnerability could help you better secure your systems.

curl can accept server responses that are compressed multiple times with different algorithms.

- There’s supposed to be a cap on how many times responses can be decompressed (“decompression chain” length).
- Due to the way this cap was set — per header, not per request — a malicious server could set many headers with compression instructions, tricking curl into using up all available memory.
- This leads to a "malloc bomb": either your system locks up under memory pressure or curl simply fails with an out-of-memory error.

HTTP Compression

HTTP responses can be compressed (think: gzip, deflate, etc.) to save bandwidth. The server signals compression using the Content-Encoding header, like:

Content-Encoding: gzip

But HTTP also allows multiple encodings, like so

Content-Encoding: br, gzip

Or (the problematic case) by repeating the Content-Encoding header

Content-Encoding: br
Content-Encoding: deflate
Content-Encoding: gzip

Chained Compression in curl

curl tries to decompress the response as many times as the headers say. To avoid unbounded CPU/memory usage, curl tried to cap this to 5 decompression operations per header.

But here’s the bug: if a server returns 100 headers, each telling curl to decompress once, there is no global limit. Only per header!

A simplified pseudo-snippet shows the root of the bug

#define MAX_ENCODINGS 5

void process_encodings(headers) {
    foreach (header in headers) {
        if (header.name == "Content-Encoding") {
            int link_count = ;
            foreach (encoding in header.value.split(',')) {
                if (++link_count > MAX_ENCODINGS)
                    break; // Only caps this header's encodings!
                decompress_with(encoding);
            }
        }
    }
    // What if there are 100 Content-Encoding headers? Each gets 5 tries!
}

Malloc Bomb

A “malloc bomb” is an attack where a program gets tricked into allocating a *huge* amount of memory, potentially crashing the process or even the whole system.

In this bug, a server can return hundreds or thousands of Content-Encoding headers, each nesting another decompression. As curl tries to recursively decompress the chain, memory consumption skyrockets.

You can try this in a controlled environment using a simple HTTP server

# python3 -m pip install flask
from flask import Flask, Response
app = Flask(__name__)

@app.route('/')
def index():
    headers = {}
    # 100 Content-Encoding headers, each with 'gzip'
    for i in range(100):
        headers[f'Content-Encoding-{i}'] = 'gzip'
    # A small gzip-compressed body
    body = b'\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03Kb\xca\xcfLQ\x04\x00\xa6\x94\x12\x15\x06\x00\x00\x00'
    return Response(body, headers=headers, mimetype='application/octet-stream')

app.run(port=808)

Now, request using vulnerable curl (<7.88.)

curl --compressed http://localhost:808/

You may see curl using lots of RAM or fail with an “out of memory” error.

curl now globally caps the “decompression chain” for all headers together, not per header.

Upgrade your curl to at least 7.88..  
Distributions patched this in February 2023.

References and More Reading

- Official curl advisory
- NVD entry for CVE-2023-23916
- curl Changelog v7.88.
- HackerOne Report

Consider server-side controls to limit Content-Encoding headers if you run APIs using curl.

- This vulnerability is a reminder: resource limits must be global, not per item, to stop attackers from bypassing your protection using repetition tricks.

Stay safe, and keep your infrastructure updated!

*Original research and writing by [Your Name], June 2024. Please link back or cite when sharing.*

Timeline

Published on: 02/23/2023 20:15:00 UTC
Last modified on: 03/09/2023 19:15:00 UTC