CVE-2026-21892
SQL Injection in parsl-visualize via URL allows for data exfiltration.
- CVSS 7.3
- CWE-89
- Input Validation and Sanitization
- Remote
Parsl is a Python parallel scripting library. A SQL Injection vulnerability exists in the parsl-visualize component of versions prior to 2026.01.05. The application constructs SQL queries using unsafe string formatting (Python % operator) with user-supplied input (workflow_id) directly from URL routes. This allows an unauthenticated attacker with access to the visualization dashboard to inject arbitrary SQL commands, potentially leading to data exfiltration or denial of service against the monitoring database. Version 2026.01.05 fixes the issue.
- CWE
- CWE-89
- CVSS base score
- 7.3
- Published
- 2026-01-08
- OWASP
- A03 Injection
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Input Validation and Sanitization
- Subcategory
- SQL Injection
- Accessibility scope
- Remote
- Impact
- Data Theft
- Affected component
- Parsl
- Fixed by upgrading
- Yes
Solution
Upgrade Parsl to version 2026.01.05 or later.
Vulnerable code sample
import pandas as pd
import sqlite3
from flask import Flask, request
# In a real application, this would be part of a larger Flask app setup.
app = Flask(__name__)
# This is a simplified stand-in for the real DB path logic.
def get_db_path(workflow_id):
return f'monitoring_{workflow_id}.db'
@app.route('/workflows/<workflow_id>/<run_id>', methods=['GET'])
def get_workflow_info(workflow_id, run_id):
"""
Returns workflow information. This function is vulnerable.
The 'run_id' is taken from the URL and inserted directly into the SQL query.
"""
sql_conn = sqlite3.connect(get_db_path(workflow_id))
# VULNERABLE LINE: Unsafe f-string formatting with user-supplied 'run_id'
df_workflow = pd.read_sql_query(f"SELECT * FROM workflow WHERE run_id = '{run_id}'", sql_conn)
sql_conn.close()
return df_workflow.to_json()
@app.route('/workflows/<workflow_id>/<run_id>/tasks/all', methods=['GET'])
def get_all_tasks_for_workflow(workflow_id, run_id):
"""
Returns all tasks for a given workflow. This function is also vulnerable.
"""
sql_conn = sqlite3.connect(get_db_path(workflow_id))
# VULNERABLE LINE: User-supplied 'run_id' is formatted directly into the query string.
script = f"SELECT task_id, task_func_name, task_time_submitted, task_time_running, task_time_returned, task_elapsed_time, task_status_name FROM task WHERE run_id = '{run_id}'"
df_tasks = pd.read_sql_query(script, sql_conn)
sql_conn.close()
return df_tasks.to_json(orient='records')
@app.route('/workflows/search', methods=['GET'])
def search_workflows_by_name():
"""
Searches for workflows by name. This is another example of the vulnerability pattern.
The 'workflow_name' is taken from a query parameter.
"""
workflow_name = request.args.get('workflow_name')
# VULNERABLE LINE: Using the Python '%' operator for string formatting with user input
sql_query = "SELECT * FROM workflow WHERE workflow_name = '%s'" % workflow_name
# In a real application, this query would be executed against the database
# For demonstration, we'll just return the query that would be run.
# db.execute(sql_query)
return {"vulnerable_query": sql_query}Patched code sample
import sqlite3
from flask import Flask, jsonify, request
# This code represents a conceptual fix. The actual parsl-visualize code
# would be part of a larger application.
# --- Application Setup ---
app = Flask(__name__)
# In-memory SQLite database for demonstration purposes
# In a real Parsl deployment, this would connect to the monitoring database.
# check_same_thread=False is used for this simple Flask example.
db_conn = sqlite3.connect(":memory:", check_same_thread=False)
def setup_database():
"""Create a table and insert some sample data."""
cursor = db_conn.cursor()
cursor.execute('''
CREATE TABLE workflows (
id TEXT PRIMARY KEY,
workflow_name TEXT NOT NULL,
status TEXT NOT NULL,
run_time REAL
)
''')
sample_data = [
('2024-08-15-abcde', 'science_run_1', 'completed', 3600.5),
('2024-08-16-fghij', 'data_processing', 'failed', 120.2),
]
cursor.executemany('INSERT INTO workflows VALUES (?, ?, ?, ?)', sample_data)
db_conn.commit()
setup_database()
# --- The Fixed Route ---
# This route demonstrates the secure way to handle user input in a SQL query.
@app.route('/workflow/<string:workflow_id>')
def get_workflow_details(workflow_id):
"""
Fetches workflow details from the database for a given workflow_id.
This function fixes the SQL Injection vulnerability by using a
parameterized query. The database driver handles the safe substitution
of the 'workflow_id' variable, preventing malicious SQL from being executed.
"""
cursor = db_conn.cursor()
# The SQL query uses a '?' placeholder for the user-supplied input.
# This is the standard way to create parameterized queries with sqlite3.
sql_query = "SELECT id, workflow_name, status, run_time FROM workflows WHERE id = ?"
# The user-supplied 'workflow_id' is passed as a separate parameter
# to the execute method. The database driver ensures it is treated as a
# literal value and not as executable SQL code.
cursor.execute(sql_query, (workflow_id,))
result = cursor.fetchone()
if result:
# Return the found data
return jsonify({
"id": result[0],
"workflow_name": result[1],
"status": result[2],
"run_time": result[3],
})
else:
# Return an error if not found
return jsonify({"error": "Workflow not found"}), 404
if __name__ == '__main__':
# To test this fix:
# 1. Run this Python script.
# 2. Access http://127.0.0.1:5000/workflow/2024-08-15-abcde in your browser.
# - This should return the data for 'science_run_1'.
# 3. Try to inject SQL by accessing a URL like:
# http://127.0.0.1:5000/workflow/x'%20OR%201=1;%20--
# - This will correctly result in a "Workflow not found" error,
# demonstrating that the injection attack failed. The input was
# treated as a single, literal string.
app.run(debug=False)Payload
' UNION SELECT name, tbl_name, sql, 1, 1 FROM sqlite_master --
Cite this entry
@misc{vaitp:cve202621892,
title = {{SQL Injection in parsl-visualize via URL allows for data exfiltration.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-21892},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-21892/}}
}
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 ::
