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 ::
