CVE-2025-55156
SQL Injection in pyLoad's add_package API via the add_links parameter.
- CVSS 7.8
- CWE-89
- Input Validation and Sanitization
- Remote
pyLoad is the free and open-source Download Manager written in pure Python. Prior to version 0.5.0b3.dev91, the parameter add_links in API /json/add_package is vulnerable to SQL Injection. Attackers can modify or delete data in the database, causing data errors or loss. This issue has been patched in version 0.5.0b3.dev91.
- CWE
- CWE-89
- CVSS base score
- 7.8
- Published
- 2025-08-11
- OWASP
- A03 Injection
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Input Validation and Sanitization
- Subcategory
- SQL Injection
- Accessibility scope
- Remote
- Impact
- Denial of Service (DoS)
- Affected component
- pyLoad
- Fixed by upgrading
- Yes
Solution
Upgrade pyLoad to version 0.5.0b3.dev91 or newer.
Vulnerable code sample
import sqlite3
import os
from flask import Flask, request, jsonify
# --- Mock Application Setup ---
# This simulates a simplified pyLoad-like environment with a Flask API
# and an SQLite database.
DATABASE = 'vulnerable_pyload.db'
app = Flask(__name__)
def get_db_connection():
"""Establishes a connection to the database."""
conn = sqlite3.connect(DATABASE)
conn.row_factory = sqlite3.Row
return conn
def initialize_database():
"""Initializes a clean database for the demo."""
if os.path.exists(DATABASE):
os.remove(DATABASE)
conn = get_db_connection()
# In a real app, 'users' or 'accounts' table might exist, making it a target
conn.execute('CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT, password TEXT)')
conn.execute("INSERT INTO users (username, password) VALUES ('admin', 'secret_password')")
conn.execute('CREATE TABLE packages (id INTEGER PRIMARY KEY, name TEXT, folder TEXT)')
conn.execute('CREATE TABLE links (id INTEGER PRIMARY KEY, package_id INTEGER, url TEXT)')
conn.execute("INSERT INTO packages (name, folder) VALUES ('Initial legitimate package', '/downloads/legit')")
conn.commit()
conn.close()
# --- Vulnerable Code ---
# This function represents the state of the /json/add_package endpoint
# before the patch for CVE-2025-55156.
@app.route('/json/add_package', methods=['POST'])
def add_package():
"""
This endpoint is vulnerable to SQL injection.
It takes a JSON payload with 'name' (for the package) and 'add_links' (a list of URLs).
The 'name' parameter is not sanitized before being used in an SQL query.
"""
data = request.get_json()
if not data:
return jsonify({"error": "Invalid JSON"}), 400
package_name = data.get('name')
links = data.get('add_links') # The CVE mentions this parameter.
if not package_name or not links:
return jsonify({"error": "Missing `name` or `add_links` parameters"}), 400
conn = get_db_connection()
cursor = conn.cursor()
try:
# VULNERABILITY: The `package_name` variable is directly concatenated into the SQL string.
# An attacker can craft a malicious package_name to inject arbitrary SQL commands.
#
# Example Malicious Payload for 'name':
# '); DROP TABLE users; --
#
# This would result in the following SQL being executed:
# INSERT INTO packages (name, folder) VALUES (''); DROP TABLE users; --', '/downloads/');
# This executes two separate commands: the (broken) INSERT and the malicious DROP TABLE.
sql_query = f"INSERT INTO packages (name, folder) VALUES ('{package_name}', '/downloads/')"
# The use of executescript() could also make multi-statement injections easier,
# but even cursor.execute() is vulnerable with many database drivers.
cursor.executescript(sql_query)
package_id = cursor.lastrowid
# The links themselves are added in a separate, safer manner,
# but the damage from the first query is already done.
for link in links:
cursor.execute("INSERT INTO links (package_id, url) VALUES (?, ?)", (package_id, link))
conn.commit()
response = {"message": "Package added successfully", "id": package_id}
status_code = 201
except sqlite3.Error as e:
response = {"error": "Database error", "details": str(e)}
status_code = 500
finally:
conn.close()
return jsonify(response), status_code
# --- Helper endpoint to verify the attack ---
@app.route('/show_tables', methods=['GET'])
def show_tables():
"""Helper to check which tables exist in the database."""
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
tables = [row[0] for row in cursor.fetchall()]
conn.close()
return jsonify(tables)
if __name__ == '__main__':
initialize_database()
print("Vulnerable server starting. The 'users' table exists.")
print("To test the vulnerability, send a POST request to http://127.0.0.1:5000/json/add_package")
print("With JSON body: {\"name\": \"'); DROP TABLE users; --\", \"add_links\": [\"http://example.com\"]}")
print("Then, send a GET request to http://127.0.0.1:5000/show_tables to see if 'users' table is gone.")
app.run(port=5000)Patched code sample
import sqlite3
def add_links_to_package_patched(db_path, package_id, links):
"""
Represents the patched function to add links to a package,
preventing SQL Injection by using parameterized queries.
In the vulnerable version, the 'links' parameter might have been
inserted into the SQL query using unsafe string formatting, allowing
an attacker to inject malicious SQL commands.
The fix is to use the database driver's parameter substitution mechanism
(the '?' placeholder in sqlite3), which ensures that user-supplied
input is always treated as data, never as executable code.
Args:
db_path (str): The path to the SQLite database file.
package_id (int): The ID of the package to add links to.
links (list): A list of URL strings to add.
"""
conn = None
try:
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# THE FIX: Use parameterized queries to insert data safely.
# The '?' is a placeholder that the database driver will safely
# fill with values from the 'data_to_insert' list.
# This prevents any part of the 'link' string from being
# interpreted as an SQL command.
sql_query = "INSERT INTO files (pid, link) VALUES (?, ?)"
# Prepare a list of tuples for the executemany method.
# Each tuple corresponds to one row to be inserted.
data_to_insert = [(package_id, link) for link in links]
# Use executemany for efficient and safe insertion of multiple rows.
cursor.executemany(sql_query, data_to_insert)
conn.commit()
# print(f"Successfully added {len(links)} links to package {package_id}.")
except sqlite3.Error as e:
# print(f"An error occurred: {e}")
if conn:
conn.rollback()
finally:
if conn:
conn.close()Payload
'); DROP TABLE users;--
Cite this entry
@misc{vaitp:cve202555156,
title = {{SQL Injection in pyLoad's add_package API via the add_links parameter.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2025},
note = {VAITP Python Vulnerability Dataset, entry CVE-2025-55156},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-55156/}}
}
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 ::
