VAITP Dataset

← Back to the dataset

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.

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 ::