VAITP Dataset

← Back to the dataset

CVE-2026-39844

NiceGUI path traversal on Windows via backslash in uploaded filename.

  • CVSS 7.5
  • CWE-22 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
  • Input Validation and Sanitization
  • Remote

NiceGUI is a Python-based UI framework. Prior to 3.10.0, Since PurePosixPath only recognizes forward slashes (/) as path separators, an attacker can bypass this sanitization on Windows by using backslashes (\) in the upload filename. Applications that construct file paths using file.name (a pattern demonstrated in NiceGUI's bundled examples) are vulnerable to arbitrary file write on Windows. This vulnerability is fixed in 3.10.0.

CVSS base score
7.5
Published
2026-04-08
OWASP
A01 Broken Access Control
Orthogonal defect classification
Checking
Code defect classification
Incorrect Check
Category
Input Validation and Sanitization
Subcategory
Path Traversal
Accessibility scope
Remote
Impact
Arbitrary Code Execution
Affected component
NiceGUI
Fixed by upgrading
Yes

Solution

Upgrade NiceGUI to version 3.10.0 or later.

Vulnerable code sample

import pathlib
import os
from fastapi import FastAPI, UploadFile, File

# This code is a representation of the vulnerable logic described in CVE-2026-39844
# for NiceGUI versions prior to 3.10.0. It must be run on a Windows system
# to demonstrate the path traversal vulnerability.
#
# To run this server:
# 1. pip install fastapi "uvicorn[standard]" python-multipart
# 2. uvicorn your_script_name:app --reload
#
# To exploit (on Windows):
# curl -X POST "http://127.0.0.1:8000/upload" -F "file=@somefile.txt;filename=..\\pwned.txt"
# This will create 'pwned.txt' in the directory above where the server is running.

app = FastAPI()

UPLOAD_DIRECTORY = pathlib.Path("uploads")
UPLOAD_DIRECTORY.mkdir(exist_ok=True)

@app.post("/upload")
async def vulnerable_upload(file: UploadFile = File(...)):
    """
    This endpoint contains the vulnerable logic.
    """
    # The filename is taken directly from the user-controlled upload request.
    unsafe_filename = file.filename

    # Flawed Sanitization: PurePosixPath is used to get the basename.
    # However, it does not recognize backslashes ('\') as path separators.
    # A filename like '..\pwned.txt' is considered a single valid name.
    sanitized_name = pathlib.PurePosixPath(unsafe_filename).name

    # Vulnerable Path Construction: The "sanitized" name is joined with the
    # upload directory path. On Windows, pathlib.Path correctly interprets
    # '..' and '\', leading to path traversal.
    destination = UPLOAD_DIRECTORY / sanitized_name

    # Arbitrary File Write: The file is written to the traversed path.
    try:
        contents = await file.read()
        with open(destination, "wb") as f:
            f.write(contents)
    except Exception as e:
        return {"error": str(e)}

    return {
        "message": f"File '{unsafe_filename}' uploaded to '{destination.resolve()}'"
    }

Patched code sample

import os
from pathlib import PurePosixPath

# This script demonstrates the logic used to fix the path traversal vulnerability
# (CVE-2023-39844, mistyped as CVE-2026-39844 in the prompt) in NiceGUI.

# The vulnerability existed because PurePosixPath was used to sanitize filenames.
# PurePosixPath only recognizes forward slashes ('/') as separators, allowing
# an attacker to use backslashes ('\') on Windows to bypass sanitization.


def vulnerable_path_construction(malicious_filename: str, upload_dir: str) -> str:
    """
    Simulates the vulnerable pattern prior to NiceGUI 3.10.0.
    The .name attribute does not sanitize backslashes.
    """
    # This is the flawed sanitization: it does not handle backslashes.
    sanitized_name = PurePosixPath(malicious_filename).name
    
    # The resulting path becomes vulnerable to traversal on Windows.
    return os.path.join(upload_dir, sanitized_name)


def fixed_path_construction(malicious_filename: str, upload_dir: str) -> str:
    """
    Demonstrates the fixed approach by using a robust sanitization function
    before constructing the path.
    """
    # The fix is to use a function that properly sanitizes the filename by
    # removing any characters that could represent a path on ANY OS. This is
    # done by whitelisting safe characters.
    sanitized_name = "".join(c for c in malicious_filename if c.isalnum() or c in "._-").strip()

    # The resulting path is now safe from traversal attacks.
    return os.path.join(upload_dir, sanitized_name)


if __name__ == "__main__":
    # An attacker's filename designed to write a file outside the target directory on a Windows system.
    malicious_input = r'..\..\..\Windows\System32\evil.txt'
    upload_directory = 'C:\\app\\uploads'

    # --- VULNERABLE DEMONSTRATION ---
    vulnerable_result = vulnerable_path_construction(malicious_input, upload_directory)
    
    print("--- DEMONSTRATION OF THE FIX ---")
    print(f"Malicious Filename: {malicious_input}\n")

    print("[VULNERABLE METHOD]")
    print(f"Sanitization via PurePosixPath.name: '{PurePosixPath(malicious_input).name}'")
    print(f"Resulting Path (on Windows):         '{os.path.normpath(vulnerable_result)}'")
    print("Analysis: The path resolves outside the 'uploads' directory, which is dangerous.\n")


    # --- FIXED DEMONSTRATION ---
    fixed_result = fixed_path_construction(malicious_input, upload_directory)
    
    print("[FIXED METHOD]")
    sanitized_for_print = "".join(c for c in malicious_input if c.isalnum() or c in "._-").strip()
    print(f"Sanitization via character whitelist: '{sanitized_for_print}'")
    print(f"Resulting Path (on Windows):          '{os.path.normpath(fixed_result)}'")
    print("Analysis: The path is safely contained within the 'uploads' directory.")

Payload

..\..\..\..\Windows\Temp\malicious_file.txt

Cite this entry

@misc{vaitp:cve202639844,
  title        = {{NiceGUI path traversal on Windows via backslash in uploaded filename.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-39844},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-39844/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::