VAITP Dataset

← Back to the dataset

CVE-2025-58760

Unauthenticated path traversal in Tautulli /image allows arbitrary file read.

  • CVSS 7.5
  • CWE-23
  • Input Validation and Sanitization
  • Remote

Tautulli is a Python based monitoring and tracking tool for Plex Media Server. The `/image` API endpoint in Tautulli v2.15.3 and earlier is vulnerable to path traversal, allowing unauthenticated attackers to read arbitrary files from the application server's filesystem. In Tautulli, the `/image` API endpoint is used to serve static images from the application's data directory to users. This endpoint can be accessed without authentication, and its intended purpose is for server background images and icons within the user interface. Attackers can exfiltrate files from the application file system, including the `tautulli.db` SQLite database containing active JWT tokens, as well as the `config.ini` file which contains the hashed admin password, the JWT token secret, and the Plex Media Server token and connection details. If the password is cracked, or if a valid JWT token is present in the database, an unauthenticated attacker can escalate their privileges to obtain administrative control over the application. Version 2.16.0 contains a fix for the issue.

CVSS base score
7.5
Published
2025-09-09
OWASP
A01 Broken Access Control
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Input Validation and Sanitization
Subcategory
Path Traversal
Accessibility scope
Remote
Impact
Information Disclosure
Affected component
Tautulli
Fixed by upgrading
Yes

Solution

Upgrade Tautulli to version 2.16.0 or later.

Vulnerable code sample

import os
from flask import Flask, send_file, abort

# This is a simplified representation of a web application like Tautulli.
# The code demonstrates the path traversal vulnerability described in the CVE.
# It is NOT the actual source code from Tautulli but is designed to
# functionally represent the described flaw.

app = Flask(__name__)

# --- Setup vulnerable environment ---
# In a real Tautulli instance, this 'data' directory would already exist.
# We create it here for a self-contained, runnable example.
# It simulates the directory structure where sensitive files are stored
# alongside legitimate public-facing images.

DATA_ROOT = os.path.abspath('data')
IMAGE_DIR = os.path.join(DATA_ROOT, 'images')

if not os.path.exists(IMAGE_DIR):
    os.makedirs(IMAGE_DIR)

# Create a dummy config.ini file that the attacker wants to steal.
with open(os.path.join(DATA_ROOT, 'config.ini'), 'w') as f:
    f.write('[main]\n')
    f.write('api_key = a_very_secret_plex_token\n')
    f.write('jwt_secret = another_super_secret_string\n')
    f.write('admin_hash = a_bcrypt_or_sha1_hashed_password\n')

# Create a dummy database file.
with open(os.path.join(DATA_ROOT, 'tautulli.db'), 'w') as f:
    f.write('SQLite format 3\n')
    f.write('...some binary data...\n')
    f.write('...eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4ifQ.s_some_active_jwt_token...\n')

# Create a legitimate image file for intended use.
with open(os.path.join(IMAGE_DIR, 'background.jpg'), 'w') as f:
    f.write('pretend this is image data')

# --- Vulnerable Code ---
@app.route('/image/<path:filename>')
def get_image(filename):
    """
    This endpoint is intended to serve images from the 'data/images' directory.
    It is unauthenticated.

    THE VULNERABILITY:
    The user-provided 'filename' is directly joined with the base path.
    No sanitization or path validation is performed. An attacker can provide
    a 'filename' like '../../config.ini' to traverse directories and access
    files outside of the intended IMAGE_DIR.
    """
    # Vulnerable line: User input is directly used to construct a file path.
    file_path = os.path.join(IMAGE_DIR, filename)

    # The application checks if the constructed path exists and is a file.
    # However, it doesn't check if the resolved path is still within the
    # intended IMAGE_DIR. This allows the traversal to succeed.
    if os.path.isfile(file_path):
        try:
            return send_file(file_path)
        except Exception as e:
            # In a real app, this might be logged.
            print(f"Error sending file: {e}")
            abort(500)
    else:
        # If the file doesn't exist, return a 404.
        abort(404)

if __name__ == '__main__':
    print("Vulnerable server starting on http://127.0.0.1:8181")
    print("Demonstrate the vulnerability by accessing:")
    print("  - Legitimate image: http://127.0.0.1:8181/image/background.jpg")
    print("  - Traversal to config.ini: http://127.0.0.1:8181/image/../../config.ini")
    print("  - Traversal to tautulli.db: http://127.0.0.1:8181/image/../../tautulli.db")
    # Note: In a real deployment, Tautulli might run on a different port.
    # We use 8181 as it's the default Tautulli port.
    app.run(host='0.0.0.0', port=8181)

Patched code sample

import os
import pathlib

def get_safe_image_path(base_directory, requested_filename):
    """
    Constructs a safe file path for an image, preventing path traversal attacks.

    This function represents the fix for CVE-2025-58760. It ensures that
    the file path requested by the user resolves to a location *inside* the
    intended base_directory.

    Args:
        base_directory (str): The absolute path to the safe directory
                              where images are stored.
        requested_filename (str): The filename provided by the user.

    Returns:
        pathlib.Path: An absolute Path object to the requested file if it is
                      safe and exists.
        None: If a path traversal attempt is detected or the file does not exist.
    """
    # 1. Create an absolute path for the intended base directory.
    # This ensures a consistent and secure root for all file operations.
    safe_base = pathlib.Path(base_directory).resolve()

    # 2. Join the base directory with the user-provided filename.
    # This creates the initial path to the requested file.
    requested_path = safe_base / requested_filename

    # 3. Resolve the combined path to its canonical form. This is the crucial step
    # that processes any path traversal sequences like '..' or symbolic links.
    # For example, '/app/data/images/../../config.ini' becomes '/app/config.ini'.
    resolved_path = requested_path.resolve()

    # 4. The security check: Verify that the resolved, canonical path is still
    # a child of (or the same as) the safe base directory.
    # The `is_relative_to()` method, available in Python 3.9+, provides a
    # robust way to check for this relationship.
    try:
        is_safe = resolved_path.is_relative_to(safe_base)
    except AttributeError:
        # Fallback for Python versions older than 3.9
        is_safe = str(resolved_path).startswith(str(safe_base))

    if not is_safe:
        # Path traversal attempt detected. The resolved path is outside the
        # allowed directory. Deny the request.
        return None

    # 5. Final check to ensure the path points to an actual file.
    if not resolved_path.is_file():
        return None

    # If all checks pass, the path is safe to use for file operations.
    return resolved_path

Payload

/image?id=../../../../config.ini

Cite this entry

@misc{vaitp:cve202558760,
  title        = {{Unauthenticated path traversal in Tautulli /image allows arbitrary file read.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2025},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2025-58760},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-58760/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::