CVE-2024-9016
RCE in dtale <= 3.13.1 via unsanitized query parameters in run_query.
- CVSS 8.8
- CWE-94
- Input Validation and Sanitization
- Remote
man-group dtale version <= 3.13.1 contains a vulnerability where the query parameters from the request are directly passed into the run_query function without proper sanitization. This allows for unauthenticated remote command execution via the df.query method when the query engine is set to 'python'.
- CWE
- CWE-94
- CVSS base score
- 8.8
- Published
- 2025-03-20
- OWASP
- A03 Injection
- Orthogonal defect classification
- Interface
- Code defect classification
- Incorrect Functionality
- Category
- Input Validation and Sanitization
- Subcategory
- Command Injection
- Accessibility scope
- Remote
- Impact
- Arbitrary Code Execution
- Affected component
- dtale
- Fixed by upgrading
- Yes
Solution
Upgrade to version > 3.13.1
Vulnerable code sample
from flask import Flask, request, jsonify
import pandas as pd
import subprocess
app = Flask(__name__)
# Sample DataFrame (in a real application, this would come from somewhere)
data = {'col1': [1, 2, 3], 'col2': ['a', 'b', 'c']}
df = pd.DataFrame(data)
@app.route('/query', methods=['GET'])
def query_data():
query = request.args.get('q') # Get the 'q' parameter from the URL
query_engine = request.args.get('engine', 'numexpr') #Get the 'engine' parameter from the URL, default is numexpr.
if query:
try:
if query_engine == 'python':
#Vulnerable code. Executes arbitrary code passed in through the q parameter
result = df.query(query, engine='python')
else:
result = df.query(query, engine='numexpr') #Original default.
return jsonify(result.to_dict(orient='records'))
except Exception as e:
return jsonify({'error': str(e)}), 400
else:
return jsonify({'error': 'Query parameter "q" is required'}), 400
if __name__ == '__main__':
app.run(debug=True)Patched code sample
import pandas as pd
from typing import Any, Dict
def safe_run_query(df: pd.DataFrame, query: str, query_engine: str = 'numexpr') -> pd.DataFrame:
"""
Safely executes a query on a Pandas DataFrame.
Args:
df: The Pandas DataFrame to query.
query: The query string.
query_engine: The query engine to use ('numexpr' or 'pandas').
Returns:
The result of the query as a DataFrame.
Raises:
ValueError: If the query engine is not supported.
Exception: If the query execution fails (e.g., due to invalid query).
"""
if query_engine not in ['numexpr', 'pandas']:
raise ValueError("Unsupported query engine. Must be 'numexpr' or 'pandas'.")
try:
if query_engine == 'numexpr':
# Using numexpr is generally safer, but it has its limitations.
# If numexpr fails, a fallback mechanism to pandas (with restrictions) can be implemented.
result = df.query(query, engine='numexpr') # Still use query, but restrict engine.
elif query_engine == 'pandas':
# Mitigation: Implement input validation and sanitization here.
# Specifically, implement an allow-list or block-list approach to prevent injection attacks.
# A robust solution requires detailed analysis of the possible injection vectors.
# For example:
# 1. Check that query only contains allowed characters (e.g., alphanumeric, basic operators).
# 2. Parse the query to ensure it doesn't contain malicious function calls (e.g., os.system).
# 3. Limit the length of the query to prevent buffer overflows.
# 4. Consider a sandbox environment for executing the pandas query.
# Placeholder for sanitization (replace with actual implementation):
sanitized_query = sanitize_query(query) # Call to sanitization function
if sanitized_query is not None:
result = df.query(sanitized_query, engine='python') # Restrict engine to python, but sanitize
else:
raise ValueError("Query failed sanitization.")
return result
except Exception as e:
raise Exception(f"Query execution failed: {e}")
def sanitize_query(query: str) -> str:
"""
Sanitizes the query string to prevent command injection.
This is a placeholder and should be replaced with a robust implementation.
For example:
1. Check if the query contains potentially dangerous functions (e.g., import, os.system).
2. Use an allow list of characters and operators.
3. Implement a parser to analyze the query structure.
Args:
query: The query string to sanitize.
Returns:
The sanitized query string, or None if sanitization fails.
"""
# Basic example: Reject queries containing 'import' or 'os.system'.
if 'import' in query or 'os.system' in query:
return None # Reject query
# More robust example using a regular expression and allow list
allowed_characters = r"^[a-zA-Z0-9\s+\-*/().&|<>=!]+$"
import re
if not re.match(allowed_characters, query):
return None
return query # Return sanitized queryPayload
`import os; os.system('cat /etc/passwd')`
Cite this entry
@misc{vaitp:cve20249016,
title = {{RCE in dtale <= 3.13.1 via unsanitized query parameters in run_query.
}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2025},
note = {VAITP Python Vulnerability Dataset, entry CVE-2024-9016},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2024-9016/}}
}
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 ::
