CVE-2025-14928
Hugging Face Transformers HuBERT code injection allows RCE via checkpoint.
- CVSS 7.8
- CWE-94
- Input Validation and Sanitization
- Remote
Hugging Face Transformers HuBERT 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-28253.
- CWE
- CWE-94
- 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 to Hugging Face Transformers version 4.38.0 or later.
Vulnerable code sample
import os
import json
def convert_config(config_dict):
"""
Vulnerable function that processes a configuration dictionary.
The flaw is the unsafe use of eval() on a user-controlled string
from the configuration, allowing for arbitrary code execution.
"""
print("[-] Converting model configuration...")
# VULNERABILITY: The value associated with 'initializer_range' is
# processed by eval() without any validation or sanitization.
# An attacker can craft a config file with a malicious string
# in this field to execute arbitrary Python code.
if "initializer_range" in config_dict:
try:
# The user-supplied string is executed as Python code.
evaluated_range = eval(config_dict["initializer_range"])
print(f"[+] Configuration 'initializer_range' evaluated to: {evaluated_range}")
except Exception as e:
print(f"[!] Error during evaluation: {e}")
print("[-] Configuration conversion finished.")
return config_dict
def load_and_convert_malicious_checkpoint(config_payload):
"""
Simulates the workflow where a user loads and converts a
malicious checkpoint configuration.
"""
print("[*] User action: Loading and converting a checkpoint...")
config_data = json.loads(config_payload)
convert_config(config_data)
if __name__ == "__main__":
# This JSON string represents the content of a malicious config file
# provided by an attacker. The payload uses __import__ to access the
# 'os' module and execute a command.
malicious_config_payload = """
{
"model_type": "hubert",
"architectures": ["HubertForCTC"],
"hidden_size": 768,
"initializer_range": "__import__('os').system('echo VULNERABILITY CVE-2025-14928 TRIGGERED')"
}
"""
print("[*] Simulating exploit for ZDI-CAN-28253 / CVE-2025-14928...")
load_and_convert_malicious_checkpoint(malicious_config_payload)Patched code sample
import json
import os
import sys
import torch.nn as nn
# This code demonstrates a potential fix for a vulnerability like CVE-2025-14928.
# The original vulnerability would have resulted from insecurely using `eval()` or `exec()`
# on a user-controlled string from a configuration file.
#
# The fix replaces the dangerous `eval()` call with a safe, allowlist-based approach.
# It uses a dictionary to map known, safe string values to their corresponding
# Python objects (in this case, PyTorch activation functions). This ensures that
# only predefined, secure options can be processed, preventing arbitrary code execution.
# --- Start of the Fixed Code ---
# A predefined dictionary (allowlist) of safe activation functions.
# This is the core of the security fix.
ALLOWED_ACTIVATIONS = {
"gelu": nn.GELU,
"relu": nn.ReLU,
"silu": nn.SiLU,
"mish": nn.Mish,
}
def convert_config(config_path: str) -> dict:
"""
Safely reads a model configuration file and processes its values.
This function demonstrates the fix for a code injection vulnerability.
Instead of using eval() on the 'activation_function' string, it looks
up the value in a predefined, safe dictionary (ALLOWED_ACTIVATIONS).
Args:
config_path: The path to the (potentially malicious) config.json file.
Returns:
A dictionary with the processed and validated configuration.
Raises:
ValueError: If the 'activation_function' is not in the allowlist.
"""
try:
with open(config_path, 'r') as f:
config_data = json.load(f)
except FileNotFoundError:
print(f"Error: Configuration file not found at '{config_path}'", file=sys.stderr)
return None
except json.JSONDecodeError:
print(f"Error: Invalid JSON in configuration file '{config_path}'", file=sys.stderr)
return None
# Default to 'gelu' if not specified.
activation_str = config_data.get("activation_function", "gelu")
# FIX: Use the predefined allowlist to safely map the string to a function.
# This prevents arbitrary strings from being executed.
if activation_str in ALLOWED_ACTIVATIONS:
activation_function = ALLOWED_ACTIVATIONS[activation_str]
print(f"Successfully and safely resolved activation function: '{activation_str}' -> {activation_function}")
else:
# If the function is not in the allowlist, raise an error instead of executing it.
raise ValueError(
f"Unsupported or potentially malicious activation function '{activation_str}'. "
f"Allowed values are: {list(ALLOWED_ACTIVATIONS.keys())}"
)
# The rest of the conversion logic would continue here...
processed_config = {
"model_type": config_data.get("model_type", "hubert"),
"hidden_size": config_data.get("hidden_size", 768),
"activation_function": activation_function,
}
return processed_config
if __name__ == '__main__':
# --- Demonstration ---
# This section demonstrates how the fix works by simulating both a
# legitimate and a malicious configuration file.
# 1. Create a legitimate configuration file
safe_config = {
"model_type": "hubert",
"hidden_size": 768,
"activation_function": "relu"
}
safe_config_path = "safe_config.json"
with open(safe_config_path, "w") as f:
json.dump(safe_config, f)
print("--- Testing with a legitimate configuration ---")
try:
processed = convert_config(safe_config_path)
if processed:
print("Processed safe config successfully.\n")
except ValueError as e:
print(f"Error processing safe config: {e}\n", file=sys.stderr)
# 2. Create a malicious configuration file attempting code injection
# The VULNERABLE version of the code would have executed this command.
malicious_payload = "os.system('echo Attempted RCE: Command was executed!')"
malicious_config = {
"model_type": "hubert",
"hidden_size": 768,
"activation_function": f"__import__('{malicious_payload}')"
}
malicious_config_path = "malicious_config.json"
with open(malicious_config_path, "w") as f:
json.dump(malicious_config, f)
print("--- Testing with a malicious configuration ---")
print(f"Malicious payload in config: '{malicious_config['activation_function']}'")
try:
# The fixed function will now raise a ValueError instead of executing the code.
convert_config(malicious_config_path)
except ValueError as e:
print("\nSUCCESS: The vulnerability was mitigated.")
print(f"The function correctly raised an error: {e}")
# Clean up the created files
os.remove(safe_config_path)
os.remove(malicious_config_path)
# --- End of the Fixed Code ---Payload
__import__('os').system('touch /tmp/pwned')
Cite this entry
@misc{vaitp:cve202514928,
title = {{Hugging Face Transformers HuBERT code injection allows RCE via checkpoint.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2025},
note = {VAITP Python Vulnerability Dataset, entry CVE-2025-14928},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-14928/}}
}
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 ::
