
Avis de sécurité : le déréférencement de pointeur NULL non authentifié fait planter le serveur (TinyWeb)
Identifiant CVE attribué : CVE-2026-67184
Lorsque TinyWeb échoue à analyser la ligne de requête, il renvoie une erreur mais laisse le pointeur URL de la requête à NULL. L'appelant ignore l'erreur et transmet la requête au constructeur de réponse, qui déréférence ce pointeur NULL et fait planter le worker. Quelques requêtes malformées suffisent à faire tomber tout le serveur, et celui-ci ne récupère pas.
TnyWeb/0.0.8e48f15d (2018-11-20), où cet analyseur et ce chemin de réponse ont été introduits, jusqu'à a381da2 (2023-11-22, dernier sur master).CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:NUn attaquant distant non authentifié pouvant atteindre le port TCP d'écoute du serveur (9090 dans la configuration fournie). Aucun identifiant ni interaction utilisateur n'est requis.
HttpParser::execute() n'alloue l'objet URL qu'après avoir analysé avec succès la ligne de requête. Une version HTTP malformée fait échouer le contrôle de version et sauter vers l'étiquette d'erreur, qui renvoie -1 avant que l'allocation ne s'exécute :
// src/tiny_http/http_parser.cc:1401 (the "H" of "HTTP" is expected here)
checkOrGoError((ch == 'H')); // false -> goto error
// src/tiny_http/http_parser.cc:1692 (skipped by the goto above)
request->url = new Url;
// src/tiny_http/http_parser.cc:1750-1753
error:
LOG(Debug) << "http request content is invalid\n";
return -1;
Le HttpRequest est créé avec std::make_shared<HttpRequest>(), qui l'initialise par valeur, donc url reste NULL lorsque l'allocation est ignorée.
WebProtocol::dataReceived() enregistre la valeur de retour mais appelle buildResponse() quand même :
// src/tiny_http/http_protocol.cc:57-61
valid = m_nParser.execute(data.c_str(), begin, data.size(), m_pRequest.get());
valid_requ = (valid == -1) ? false : true;
// src/tiny_http/http_protocol.cc:75
m_nResponser.buildResponse(m_pRequest.get(), valid_requ, m_pResponse.get());
buildResponse() reçoit valid_requ == false mais ne le vérifie jamais, et déréférence le pointeur URL NULL :
// src/tiny_http/http_responser.cc:66
Url* url = req->url; // NULL
// src/tiny_http/http_responser.cc:83
if (url->field_set & (1 << HTTP_UF_PATH)) { // SIGSEGV
Le processus worker plante lors de cet accès. Il n'est pas redémarré dans un état de service, donc une fois les workers disparus, le serveur cesse de répondre.
Démarrez le serveur avec la configuration fournie (écoute sur le port 9090).
Confirmez qu'il répond à une requête normale :
curl http://TARGET:9090/
XTTP/1.1 échoue au contrôle de version après que la ligne de requête s'est déjà engagée en mode requête :printf 'GET / XTTP/1.1\r\n\r\n' | nc TARGET 9090
Un débogueur attaché à un worker montre le crash au niveau du déréférencement NULL :
#0 HttpBuilder::buildResponse (valid_requ=false) at src/tiny_http/http_responser.cc:83
#1 WebProtocol::dataReceived (data="GET / XTTP/1.1\r\n...") at src/tiny_http/http_protocol.cc:75
Un attaquant non authentifié peut faire planter les workers du serveur avec quelques requêtes courtes et maintenir le service hors ligne en les renvoyant. Il s'agit d'un déni de service.
Dans dataReceived(), lorsque valid_requ est faux, construisez une réponse d'erreur et retournez au lieu d'appeler buildResponse(). Dans buildResponse(), vérifiez que req->url n'est pas NULL avant de l'utiliser. Allouer l'objet URL avant que l'analyse ne puisse échouer comblerait également la faille.