
Security Advisory: Неаутентифицированное разыменование NULL-указателя приводит к сбою сервера (TinyWeb)
Assigned CVE ID: CVE-2026-67184
Когда TinyWeb не может разобрать строку запроса, он возвращает ошибку, но оставляет указатель URL запроса равным NULL. Вызывающий код игнорирует ошибку и передаёт запрос построителю ответа, который разыменовывает этот NULL-указатель и приводит к падению рабочего процесса (worker). Несколько некорректных запросов кладут весь сервер, и он не восстанавливается.
TnyWeb/0.0.8e48f15d (2018-11-20), где были введены этот парсер и путь обработки ответа, по a381da2 (2023-11-22, последний в 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:NНеаутентифицированный удалённый злоумышленник, который может достучаться до слушающего TCP-порта сервера (9090 в поставляемой конфигурации). Никакие учётные данные или взаимодействие с пользователем не требуются.
HttpParser::execute() выделяет объект URL только после успешного разбора строки запроса. Некорректная HTTP-версия приводит к тому, что проверка версии не проходит и происходит переход к метке ошибки, которая возвращает -1 до выполнения выделения:
// 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;
Объект HttpRequest создаётся с помощью std::make_shared<HttpRequest>(), который выполняет value-инициализацию, поэтому url остаётся равным NULL, когда выделение пропускается.
WebProtocol::dataReceived() записывает возвращаемое значение, но в любом случае вызывает buildResponse():
// 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() получает valid_requ == false, но никогда не проверяет это и разыменовывает NULL-указатель URL:
// 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
Рабочий процесс падает при этом обращении. Он не перезапускается в обслуживающее состояние, поэтому, когда все рабочие процессы исчезают, сервер перестаёт отвечать.
Запустите сервер с поставляемой конфигурацией (слушает порт 9090).
Убедитесь, что он отвечает на обычный запрос:
curl http://TARGET:9090/
XTTP/1.1 не проходит проверку версии после того, как строка запроса уже перевела сервер в режим запроса:printf 'GET / XTTP/1.1\r\n\r\n' | nc TARGET 9090
Отладчик, подключённый к рабочему процессу, показывает падение при разыменовании 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
Неаутентифицированный злоумышленник может вызвать падение рабочих процессов сервера несколькими короткими запросами и удерживать сервис в офлайне, повторно отправляя их. Это отказ в обслуживании.
В dataReceived(), когда valid_requ имеет значение false, сформируйте ответ об ошибке и вернитесь вместо вызова buildResponse(). В buildResponse() проверяйте req->url на NULL перед его использованием. Выделение объекта URL до того, как разбор может завершиться ошибкой, также закрыло бы эту брешь.