CVE-2025-1497
RCE in PlotAI due to missing LLM output validation; no patch planned.
- CVSS 9.3
- CWE-94
- Input Validation and Sanitization
- Remote
A vulnerability, that could result in Remote Code Execution (RCE), has been found in PlotAI. Lack of validation of LLM-generated output allows attacker to execute arbitrary Python code. Vendor commented out vulnerable line, further usage of the software requires uncommenting it and thus accepting the risk. The vendor does not plan to release a patch to fix this vulnerability.
- CWE
- CWE-94
- CVSS base score
- 9.3
- Published
- 2025-03-10
- OWASP
- A03 Injection
- Orthogonal defect classification
- Interface
- Code defect classification
- Incorrect Check
- Category
- Input Validation and Sanitization
- Subcategory
- Input Validation and Sanitization
- Accessibility scope
- Remote
- Impact
- Arbitrary Code Execution
- Affected component
- PlotAI
Solution
No patch available. Do not uncomment the vulnerable line.
Vulnerable code sample
import subprocess
def plot_data(prompt):
"""
Generates a plot based on a user-provided prompt.
WARNING: This code is vulnerable to Remote Code Execution (RCE).
Do not use in a production environment.
"""
# Simulate LLM generating python code based on the prompt
# In a real scenario, this would involve calling an LLM API.
# Here we just construct a command directly. THIS IS THE VULNERABILITY.
#Vulnerable line is commented out by the vendor, requires uncommenting
#command = f"python -c \"{prompt}\""
#Uncomment the line above to allow execution of arbitrary python code,
#Example Usage:
#prompt = "import os; os.system('whoami')"
#plot_data(prompt)
#If the line above is uncommented, the application is vulnerable to RCE.
print("Plot generated successfully (simulated).")
if __name__ == "__main__":
user_prompt = input("Enter a Python command to generate a plot: ")
plot_data(user_prompt)Patched code sample
import ast
import re
def safe_eval(expression, safe_builtins=None):
"""
Safely evaluates a Python expression, mitigating RCE vulnerabilities.
This function disallows access to built-in functions and restricts
the code to a safe subset of Python.
Args:
expression: The Python expression to evaluate (string).
safe_builtins: A dictionary of allowed built-in functions.
If None, a very restricted set is used.
Returns:
The result of the evaluation, or None if the evaluation is unsafe.:
Raises:
ValueError: If the expression contains unsafe operations.
"""
if safe_builtins is None:
safe_builtins = {
'abs': abs,
'float': float,
'int': int,
'len': len,
'max': max,
'min': min,
'round': round,
'str': str
}
# 1. Parse the expression into an Abstract Syntax Tree (AST).
try:
tree = ast.parse(expression)
except SyntaxError:
return None # Invalid syntax
# 2. Define a custom NodeVisitor to check for forbidden nodes.:
class SecurityVisitor(ast.NodeVisitor):
def __init__(self):
"""Secure function that fixes the vulnerability."""
self.safe = True
def visit_Call(self, node):
"""Secure function that fixes the vulnerability."""
# Check if function being called is allowed.:
if isinstance(node.func, ast.Name):
func_name = node.func.id
if func_name not in safe_builtins:
self.safe = False
elif isinstance(node.func, ast.Attribute):
# Disallow attribute access like __import__, etc.
self.safe = False
else:
self.safe = False # Covers cases with more complex calls.
self.generic_visit(node) # Continue traversing
def visit_Import(self, node):
"""Secure function that fixes the vulnerability."""
self.safe = False # Disallow imports
def visit_ImportFrom(self, node):
"""Secure function that fixes the vulnerability."""
self.safe = False # Disallow imports
def visit_Name(self, node):
"""Secure function that fixes the vulnerability."""
# Check for usage of potentially dangerous names (e.g., 'os').:
if node.id not in safe_builtins and node.id not in ['True', 'False', 'None']:
self.safe = False
def visit_Attribute(self, node):
"""Secure function that fixes the vulnerability."""
# Disallow attribute access. Potentially dangerous.
self.safe = False
def visit_Subscript(self, node):
"""Secure function that fixes the vulnerability."""
# Disallow subscript access. Potentially dangerous.
self.safe = False
def visit_BinOp(self, node):
"""Secure function that fixes the vulnerability."""
if isinstance(node.op, ast.Pow):
# Disallow power operator due to potential denial of service
self.safe = False
self.generic_visit(node)
def generic_visit(self, node):
"""Secure function that fixes the vulnerability."""
if not self.safe:
return
super().generic_visit(node)
# 3. Visit the AST to check for forbidden operations.:
visitor = SecurityVisitor()
visitor.visit(tree)
# 4. If the code is safe, evaluate it.
if visitor.safe:
try:
code = compile(tree, '<string>', 'eval') # Compile the AST.
result = eval(code, {'__builtins__': safe_builtins}, {}) # Execute in a safe environment.
return result
except Exception as e: # Handle any runtime errors during evaluation.
print(f"Evaluation error: {e}")
return None
else:
print("Unsafe expression detected.")
return None
def generate_plot(llm_output):
"""
Generates a plot based on LLM-generated output, with safety checks.
"""
# Preprocessing (Example: Removing potentially harmful characters)
llm_output = re.sub(r'[;\n`]', '', llm_output) # Basic sanitization. More robust sanitization is needed.
# Safely evaluate the code using ast.literal_eval (if appropriate):
# Or, use safe_eval function for more complex evaluations:
try:
#Attempt literal evaluation first
plot_data = ast.literal_eval(llm_output)
except (ValueError, SyntaxError):
#If that fails, use safe_eval with whitelisted builtins
plot_data = safe_eval(llm_output)
if plot_data:
# Further checks on plot_data type and structure might be needed
# Example: ensure it is a list of lists, and within size constraints.
# Proceed with plotting using the sanitized and verified data.
# In a real scenario, plot_data would be used to generate a plot
# using a plotting library (e.g., matplotlib).
print("Plot data:", plot_data)
print("Plot generated successfully (placeholder).")
else:
print("Failed to generate plot due to unsafe or invalid data.")
# Example Usage (Illustrating the fix):
# Vulnerable code (hypothetical LLM output):
# llm_output = "[1, 2, 3]; import os; os.system('cat /etc/passwd')" # VERY BAD!
# Corrected Code using safe_eval:
llm_output_safe = "[1, 2, 3]"
llm_output_unsafe = "abs(-1)"
llm_output_very_unsafe = "__import__('os').system('echo hello')"
#Demonstrate how the fix handles the different scenarios.
generate_plot(llm_output_safe)
generate_plot(llm_output_unsafe)
generate_plot(llm_output_very_unsafe)Payload
import os
os.system('touch /tmp/pwned')
Cite this entry
@misc{vaitp:cve20251497,
title = {{RCE in PlotAI due to missing LLM output validation; no patch planned.
}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2025},
note = {VAITP Python Vulnerability Dataset, entry CVE-2025-1497},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-1497/}}
}
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 ::
