Skip to content

Unauthenticated Session Hijacking via Race Condition on Global Session Buffer

High
PromoFaux published GHSA-9ff5-f3v5-2xc7 May 16, 2026

Package

Pi-Hole FTL

Affected versions

>=6.0

Patched versions

6.6.1

Description

Summary

Pi-hole FTL versions 6.0 through 6.6.0 contain a race condition vulnerability in the HTTP session management subsystem, introduced with the v6.0 rewrite of the embedded CivetWeb-based web server.

A global character buffer pi_hole_extra_headers is used to pass Set-Cookie response headers (containing the administrator session ID) from the API authentication layer to the CivetWeb HTTP engine. Because CivetWeb dispatches requests across a pool of worker threads (50 by default) and no synchronization mechanism protects this shared buffer, a race window exists between the write of the SID performed by the administrator's thread and the read performed by another thread.

An unauthenticated attacker on the local network who sends concurrent requests to public API endpoints can receive a Set-Cookie header intended for an active administrator session, obtaining full administrative access without credentials.

Details

The shared global buffer (src/webserver/http-common.c)

The first problem lies in the declaration of the buffer used to pass additional HTTP headers, specifically the Set-Cookie header carrying the administrator's session ID, from the API authentication layer to the CivetWeb HTTP engine.

In versions v6.0 through v6.6.0, this buffer is a plain global variable:

char pi_hole_extra_headers[PIHOLE_HEADERS_MAXLEN] = { 0 };

A global variable in C exists at a single memory location shared by the entire process. CivetWeb launches 50 worker threads by default to handle concurrent HTTP requests, and all of them access this same buffer. There is no synchronization mechanism, no mutex, no semaphore, no atomic, guaranteeing that only one thread at a time writes to or reads from it.

The unsynchronized assignment in check_client_auth() (src/api/auth.c)

This is the core of the vulnerability from a data flow perspective. The function check_client_auth() validates the client's cookie SID and assigns the corresponding session to the connection. This assignment is performed with no lock held:

// No mutex. Any other thread can modify auth_data[]
// between this line and the point where api->session->sid is used.
api->session = &auth_data[user_id];

The race window is as follows: thread A (administrator) calls check_client_auth(), obtains a pointer to slot auth_data[user_id], and continues toward building the HTTP response. Before it reaches the point of reading api->session->sid to build the Set-Cookie header, thread B (attacker), handling a concurrent request to a public endpoint, can access the same slot in auth_data[] or reach the response header construction phase first and read the contents of pi_hole_extra_headers.

The unprotected write in send_api_auth_status() (src/api/auth.c)

The final link in the chain is the function that builds the Set-Cookie header and writes it into the global buffer. This operation directly places the administrator's SID into pi_hole_extra_headers for CivetWeb to include in the HTTP response, with no protection over the shared buffer:

snprintf(pi_hole_extra_headers, sizeof(pi_hole_extra_headers),
         "Set-Cookie: sid=%s; SameSite=Lax; Path=/; Max-Age=%u; HttpOnly\r\n",
         auth_data[user_id].sid, ...);

The exact exploitation sequence is: thread A writes the administrator's SID into pi_hole_extra_headers. CivetWeb, in the process of building the HTTP response for thread A, should read that buffer and attach it as a header. But if between the snprintf write and CivetWeb's read, thread B, handling the attacker's request, reaches the HTTP response header construction phase first and reads pi_hole_extra_headers, it will receive the Set-Cookie containing the administrator's SID in its own response, obtaining full administrative access without having provided any credentials.

How the attached PoC works

The attached PoC (poc_session_race_min.py) was developed specifically to demonstrate the exploitability of this vulnerability in controlled environments. The script simultaneously simulates the two actors involved in the attack: a legitimate administrator browsing the web interface and an attacker on the same network attempting to capture their session.

The script is divided into two phases that run in parallel within the same process.

Phase 1 - Admin simulator

This phase replicates the behavior of a legitimate administrator using the Pi-hole web interface. The script authenticates against /api/auth with the provided credentials and, once the SID is obtained, launches a dedicated thread that continuously makes GET requests to dashboard endpoints (/api/stats/summary, /api/config, /api/dns/blocking, /api/history, among others) with variable intervals to simulate real human browsing.

The purpose of this phase is to ensure that the global buffer pi_hole_extra_headers receives continuous writes of the administrator's SID, which is exactly what happens when a real administrator browses the interface in production.

poc_session_race_min.py

Phase 2 - Attacker

This phase replicates the attacker on the local network. N concurrent threads are launched (100 by default) that flood the three public Pi-hole endpoints without authentication: /api/info/login, /api/auth and /api/docs. None of these threads provide credentials or a SID of any kind.

Every received response is inspected for a Set-Cookie: sid= header. When the timing is favorable and one of the attacker's threads reaches the HTTP response header construction phase in CivetWeb at the exact moment the buffer contains the administrator's SID, that thread receives the administrator's cookie in its own HTTP response, without having requested it and without any credentials.

When a SID is captured, the script immediately verifies it against /api/auth to confirm it grants valid administrative access, and prints ready-to-use curl commands.

image

Impact

An unauthenticated attacker on the local network who exploits this vulnerability obtains a valid administrator SID with which they can authenticate as administrator on the Pi-hole web interface and take full control of its configuration, including the ability to modify DNS settings, disable ad and malware blocking, and access all network data.

Fix (24/04/2026)

The vulnerability was fixed in FTL v6.6.1 (April 24, 2026) as part of PR #2835, which addressed a series of thread-safety issues that were causing crashes (SIGSEGV) under concurrent load. The security fix was incidental.

The technical fix consisted of two complementary changes. The first and most direct was promoting the global buffer pi_hole_extra_headers to _Thread_local storage, giving each CivetWeb worker thread its own private copy of the buffer instead of sharing a single memory location:

- char pi_hole_extra_headers[PIHOLE_HEADERS_MAXLEN] = { 0 };
+ _Thread_local char pi_hole_extra_headers[PIHOLE_HEADERS_MAXLEN] = { 0 ];

With this change, the thread that writes the administrator's SID and the thread that builds the HTTP response are always the same thread, by design of CivetWeb's threading model, so the buffer write and read always occur within the same execution context, eliminating the race window entirely.

The second complementary change introduced a pthread_mutex_t auth_lock mutex protecting all concurrent access to the session array auth_data[], and replaced the live pointer into the array with a value copy of the session struct, so that API handlers work on a private copy rather than live shared memory.

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
Required
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

CVE ID

CVE-2026-44693

Weaknesses

Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition')

The product contains a concurrent code sequence that requires temporary, exclusive access to a shared resource, but a timing window exists in which the shared resource can be modified by another code sequence operating concurrently. Learn more on MITRE.

Credits