VAITP Dataset

← Back to the dataset

CVE-2025-14926

Hugging Face Transformers convert_config vulnerable to RCE via code injection.

  • CVSS 7.8
  • CWE-94
  • Input Validation and Sanitization
  • Remote

Hugging Face Transformers SEW convert_config Code Injection Remote Code Execution Vulnerability. This vulnerability allows remote attackers to execute arbitrary code on affected installations of Hugging Face Transformers. User interaction is required to exploit this vulnerability in that the target must convert a malicious checkpoint. The specific flaw exists within the convert_config function. The issue results from the lack of proper validation of a user-supplied string before using it to execute Python code. An attacker can leverage this vulnerability to execute code in the context of the current user. Was ZDI-CAN-28251.

CVSS base score
7.8
Published
2025-12-23
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
Hugging Face
Fixed by upgrading
Yes

Solution

Upgrade Hugging Face Transformers to version 4.29.0 or later.

Vulnerable code sample

import os

# This class represents a configuration object that would be loaded from a
# checkpoint's config.json file in a real-world scenario.
class ModelConfig:
    def __init__(self, config_data):
        # The attacker crafts a malicious config file where 'conversion_code_to_run'
        # contains a malicious string.
        self.model_type = config_data.get("model_type")
        self.architectures = config_data.get("architectures")
        
        # The malicious payload is embedded in a seemingly innocuous config field.
        # The default 'pass' value makes it look like an optional hook.
        self.conversion_code_to_run = config_data.get("conversion_code_to_run", "pass")
        self.new_config_values = {}

# The specific flaw exists within the convert_config function.
def convert_config(config: ModelConfig):
    """
    This function simulates the vulnerable logic before a fix was applied.
    The issue results from the lack of proper validation of a user-supplied string
    before using it to execute Python code via `exec`.
    """
    # Some legitimate-looking conversion logic might happen here.
    # For example, mapping old configuration keys to new keys.
    config.new_config_values["model_architecture"] = config.model_type
    print(f"[*] Converting config for model type: {config.model_type}")

    # THE VULNERABLE CODE
    # The code from the config file is executed without any validation or sanitization.
    # An attacker can place any Python code in the 'conversion_code_to_run' field
    # of the config file. The exec() call on this unvalidated user-supplied
    # string is the core of the remote code execution vulnerability.
    print(f"[!] Executing conversion code from config: {config.conversion_code_to_run}")
    exec(config.conversion_code_to_run)

    # The function might continue with other operations after the malicious execution.
    print("[*] Conversion logic finished.")
    return config.new_config_values

Patched code sample

The specified CVE does not exist. The following Python code is a hypothetical example that demonstrates how a vulnerability matching the described pattern (arbitrary code execution via an unsafe `eval` on a configuration string) would be fixed. The fix involves replacing the unsafe `eval` with a dispatch table (an "allow-list") that maps expected string values to safe, pre-defined functions.

```python
import os

# The vulnerability described (e.g., CVE-2025-14926) would stem from
# unsafely executing code from a configuration file, for instance, using `eval()`.
# The proper fix is to replace such arbitrary code execution with a safe,
# allow-listed approach, as demonstrated below.

# 1. Define a set of pre-approved, safe operations.
# These functions are known to be safe and do not execute arbitrary strings.
def safe_log_model_details(config):
    """A safe, approved function that logs model details."""
    model_type = config.get("model_type", "unknown")
    print(f"[INFO] Processing model of type: {model_type}")

def safe_check_config_version(config):
    """A safe, approved function that checks a version number."""
    version = config.get("version", 1.0)
    if version < 2.0:
        print(f"[WARN] Configuration version {version} is considered legacy.")

# 2. Create an "allow-list" dispatch table.
# This maps expected string inputs from the config to the pre-vetted safe functions.
# Any input not in this dictionary will be rejected.
ALLOWED_POST_CONVERSION_HOOKS = {
    "log_details": safe_log_model_details,
    "check_version": safe_check_config_version,
}

# 3. Implement the patched `convert_config` function.
def convert_config(config_data: dict) -> dict:
    """
    Converts a model configuration, securely handling post-conversion hooks.
    This function represents the patched version that prevents code injection.
    
    The vulnerable version would have used `eval()` on 'post_conversion_hook'.
    This fixed version uses a safe dispatch table instead.
    """
    print("Starting configuration conversion...")
    converted_config = config_data.copy()
    converted_config["is_converted"] = True
    print("Core conversion logic finished.")

    # --- The Security Fix Implementation ---
    hook_name = config_data.get("post_conversion_hook")

    if hook_name:
        print(f"Found post-conversion hook request: '{hook_name}'")
        
        # Securely fetch the function from the allow-list.
        # .get() returns None if the key doesn't exist.
        safe_function_to_run = ALLOWED_POST_CONVERSION_HOOKS.get(hook_name)

        if safe_function_to_run:
            # If the hook name is in our allow-list, execute the corresponding safe function.
            print(f"Executing allowed hook: '{hook_name}'...")
            safe_function_to_run(converted_config)
        else:
            # If the hook name is not in the allow-list, reject it.
            # This is the crucial step that prevents arbitrary code execution.
            print(f"[SECURITY ERROR] Disallowed or unknown hook: '{hook_name}'. Skipping execution.")
    
    print("Configuration conversion process complete.\n")
    return converted_config

# --- Demonstration of the Fix ---

if __name__ == "__main__":
    # Example 1: A benign configuration with a valid, allowed hook.
    # The fix allows this to execute as intended.
    benign_config = {
        "model_type": "SEW",
        "version": 1.0,
        "post_conversion_hook": "check_version"
    }

    print("--- 1. Testing with a Benign Configuration ---")
    convert_config(benign_config)

    # Example 2: A malicious configuration attempting Remote Code Execution.
    # The payload attempts to use `os.system` to print a message.
    # In a vulnerable system using `eval`, this would execute.
    # The fixed `convert_config` will identify it as a disallowed hook and reject it.
    malicious_config = {
        "model_type": "malicious-model",
        "version": 99.0,
        "post_conversion_hook": "__import__('os').system('echo VULNERABILITY EXPLOITED!')"
    }

    print("--- 2. Testing with a Malicious Configuration ---")
    convert_config(malicious_config)

    # Example of what the vulnerable code might have looked like (FOR ILLUSTRATION ONLY - DO NOT USE):
    #
    # def vulnerable_convert_config(config_data: dict):
    #     hook_name = config_data.get("post_conversion_hook")
    #     if hook_name:
    #         # UNSAFE: Executes any string from the config file, leading to RCE.
    #         eval(hook_name)
    #     return config_data

Payload

{
  "model_type": "sew",
  "architectures": [
    "__import__('os').system('id')"
  ]
}

Cite this entry

@misc{vaitp:cve202514926,
  title        = {{Hugging Face Transformers convert_config vulnerable to RCE via code injection.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2025},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2025-14926},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-14926/}}
}
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 ::