CVE-2024-32880
pyLoad allows upload to arbitrary folder lead to RCE
pyload is an open-source Download Manager written in pure Python. An authenticated user can change the download folder and upload a crafted template to the specified folder lead to remote code execution. There is no fix available at the time of publication.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- High
- User interaction
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- High
- Availability
- High
Exploit likelihood
What each source reports
CVE List Score used
9.1
pyLoad allows upload to arbitrary folder lead to RCE
pyload is an open-source Download Manager written in pure Python. An authenticated user can change the download folder and upload a crafted template to the specified folder lead to remote code execution. There is no fix available at the time of publication.
- Record
CVE-2024-32880- CVSS 3.1
- 9.1 Critical
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H - Weakness
- CWE-434
- Updated
- 2026-11-02
EUVDEUVD-2024-1078
9.1
pyload is an open-source Download Manager written in pure Python. An authenticated user can change the download folder and upload a crafted template to the specified folder lead to remote code execution. There is no fix available at the time of publication.
- Record
EUVD-2024-1078- CVSS 3.1
- 9.1 Critical
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H - Updated
- 2026-11-03
GitHub AdvisoriesGHSA-3f7w-p8vr-4v5f
9.1
pyLoad allows upload to arbitrary folder lead to RCE
Summary
An authenticated user can change the download folder and upload a crafted template to the specified folder lead to remote code execution
Details
example version: 0.5 file:src/pyload/webui/app/blueprints/app_blueprint.py
@bp.route("/render/<path:filename>", endpoint="render")
def render(filename):
mimetype = mimetypes.guess_type(filename)[0] or "text/html"
data = render_template(filename)
return flask.Response(data, mimetype=mimetype)
So, if we can control file in the path "pyload/webui/app/templates" in latest version and path in "module/web/media/js"(the difference is the older version0.4.20 only renders file with extension name ".js"), the render_template func will works like SSTI(server-side template injection) when render the evil file we control.
in /settings page and the choose option general/general, where we can change the download folder.
Also, we can find the pyLoad install folder in /info page So, we can change the value of Download folder to the template path. Then through /json/add_package we can upload a crafted template file to RCE.
@bp.route("/json/add_package", methods=["POST"], endpoint="add_package")
# @apiver_check
@login_required("ADD")
def add_package():
api = flask.current_app.config["PYLOAD_API"]
package_name = flask.request.form.get("add_name", "New Package").strip()
queue = int(flask.request.form["add_dest"])
links = [l.strip() for l in flask.request.form["add_links"].splitlines()]
pw = flask.request.form.get("add_password", "").strip("\n\r")
try:
file = flask.request.files["add_file"]
if file.filename:
if not package_name or package_name == "New Package":
package_name = file.filename
file_path = os.path.join(
api.get_config_value("general", "storage_folder"), "tmp_" + file.filename
)
file.save(file_path)
links.insert(0, file_path)
except Exception:
pass
urls = [url for url in links if url.strip()]
pack = api.add_package(package_name, urls, queue)
if pw:
data = {"password": pw}
api.set_package_data(pack, data)
return jsonify(True)
PoC
First login into the admin page, then visit the info page to get the path of pyload installation folder. Second, change the download folder to PYLOAD_INSTALL_DIR/ webui/app/templates/ Third, upload crafted template file through /json/add_package through parameter add_file the content of crafted template file and its filename is "341.html":
{{x.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")}}
Last, visit http://TARGET/render/tmp_341.html to trigger the RCE
Impact
It is a RCE vulnerability and I think it affects all versions. In earlier version 0.4.20, the trigger difference is the pyload installation folder path difference and the upload file must with extension ".js" . The render js code in version 0.4.20:
@route("/media/js/<path:re:.+\.js>")
def js_dynamic(path):
response.headers['Expires'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
time.gmtime(time.time() + 60 * 60 * 24 * 2))
response.headers['Cache-control'] = "public"
response.headers['Content-Type'] = "text/javascript; charset=UTF-8"
try:
# static files are not rendered
if "static" not in path and "mootools" not in path:
t =…
- Record
GHSA-3f7w-p8vr-4v5f- CVSS 3.1
- 9.1 Critical
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H - Weakness
- CWE-434
- Updated
- 2026-11-02
NVD
9.1
pyload is an open-source Download Manager written in pure Python. An authenticated user can change the download folder and upload a crafted template to the specified folder lead to remote code execution. There is no fix available at the time of publication.
- Record
CVE-2024-32880- CVSS 3.1
- 9.1 Critical
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H - CVSS 3.1
- 7.2 High
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-434
- Updated
- 2026-11-02
OSVGHSA-3f7w-p8vr-4v5f
9.1
pyLoad allows upload to arbitrary folder lead to RCE
Summary
An authenticated user can change the download folder and upload a crafted template to the specified folder lead to remote code execution
Details
example version: 0.5 file:src/pyload/webui/app/blueprints/app_blueprint.py
@bp.route("/render/<path:filename>", endpoint="render")
def render(filename):
mimetype = mimetypes.guess_type(filename)[0] or "text/html"
data = render_template(filename)
return flask.Response(data, mimetype=mimetype)
So, if we can control file in the path "pyload/webui/app/templates" in latest version and path in "module/web/media/js"(the difference is the older version0.4.20 only renders file with extension name ".js"), the render_template func will works like SSTI(server-side template injection) when render the evil file we control.
in /settings page and the choose option general/general, where we can change the download folder.
Also, we can find the pyLoad install folder in /info page So, we can change the value of Download folder to the template path. Then through /json/add_package we can upload a crafted template file to RCE.
@bp.route("/json/add_package", methods=["POST"], endpoint="add_package")
# @apiver_check
@login_required("ADD")
def add_package():
api = flask.current_app.config["PYLOAD_API"]
package_name = flask.request.form.get("add_name", "New Package").strip()
queue = int(flask.request.form["add_dest"])
links = [l.strip() for l in flask.request.form["add_links"].splitlines()]
pw = flask.request.form.get("add_password", "").strip("\n\r")
try:
file = flask.request.files["add_file"]
if file.filename:
if not package_name or package_name == "New Package":
package_name = file.filename
file_path = os.path.join(
api.get_config_value("general", "storage_folder"), "tmp_" + file.filename
)
file.save(file_path)
links.insert(0, file_path)
except Exception:
pass
urls = [url for url in links if url.strip()]
pack = api.add_package(package_name, urls, queue)
if pw:
data = {"password": pw}
api.set_package_data(pack, data)
return jsonify(True)
PoC
First login into the admin page, then visit the info page to get the path of pyload installation folder. Second, change the download folder to PYLOAD_INSTALL_DIR/ webui/app/templates/ Third, upload crafted template file through /json/add_package through parameter add_file the content of crafted template file and its filename is "341.html":
{{x.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")}}
Last, visit http://TARGET/render/tmp_341.html to trigger the RCE
Impact
It is a RCE vulnerability and I think it affects all versions. In earlier version 0.4.20, the trigger difference is the pyload installation folder path difference and the upload file must with extension ".js" . The render js code in version 0.4.20:
@route("/media/js/<path:re:.+\.js>")
def js_dynamic(path):
response.headers['Expires'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
time.gmtime(time.time() + 60 * 60 * 24 * 2))
response.headers['Cache-control'] = "public"
response.headers['Content-Type'] = "text/javascript; charset=UTF-8"
try:
# static files are not rendered
if "static" not in path and "mootools" not in path:
t =…
- Record
GHSA-3f7w-p8vr-4v5f- CVSS 3.1
- 9.1 Critical
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H - Weakness
- CWE-434
- Updated
- 2026-11-02
OSVPYSEC-2026-492
9.1
pyLoad allows upload to arbitrary folder lead to RCE
Summary
An authenticated user can change the download folder and upload a crafted template to the specified folder lead to remote code execution
Details
example version: 0.5 file:src/pyload/webui/app/blueprints/app_blueprint.py
@bp.route("/render/<path:filename>", endpoint="render")
def render(filename):
mimetype = mimetypes.guess_type(filename)[0] or "text/html"
data = render_template(filename)
return flask.Response(data, mimetype=mimetype)
So, if we can control file in the path "pyload/webui/app/templates" in latest version and path in "module/web/media/js"(the difference is the older version0.4.20 only renders file with extension name ".js"), the render_template func will works like SSTI(server-side template injection) when render the evil file we control.
in /settings page and the choose option general/general, where we can change the download folder.
Also, we can find the pyLoad install folder in /info page So, we can change the value of Download folder to the template path. Then through /json/add_package we can upload a crafted template file to RCE.
@bp.route("/json/add_package", methods=["POST"], endpoint="add_package")
# @apiver_check
@login_required("ADD")
def add_package():
api = flask.current_app.config["PYLOAD_API"]
package_name = flask.request.form.get("add_name", "New Package").strip()
queue = int(flask.request.form["add_dest"])
links = [l.strip() for l in flask.request.form["add_links"].splitlines()]
pw = flask.request.form.get("add_password", "").strip("\n\r")
try:
file = flask.request.files["add_file"]
if file.filename:
if not package_name or package_name == "New Package":
package_name = file.filename
file_path = os.path.join(
api.get_config_value("general", "storage_folder"), "tmp_" + file.filename
)
file.save(file_path)
links.insert(0, file_path)
except Exception:
pass
urls = [url for url in links if url.strip()]
pack = api.add_package(package_name, urls, queue)
if pw:
data = {"password": pw}
api.set_package_data(pack, data)
return jsonify(True)
PoC
First login into the admin page, then visit the info page to get the path of pyload installation folder. Second, change the download folder to PYLOAD_INSTALL_DIR/ webui/app/templates/ Third, upload crafted template file through /json/add_package through parameter add_file the content of crafted template file and its filename is "341.html":
{{x.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")}}
Last, visit http://TARGET/render/tmp_341.html to trigger the RCE
Impact
It is a RCE vulnerability and I think it affects all versions. In earlier version 0.4.20, the trigger difference is the pyload installation folder path difference and the upload file must with extension ".js" . The render js code in version 0.4.20:
@route("/media/js/<path:re:.+\.js>")
def js_dynamic(path):
response.headers['Expires'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
time.gmtime(time.time() + 60 * 60 * 24 * 2))
response.headers['Cache-control'] = "public"
response.headers['Content-Type'] = "text/javascript; charset=UTF-8"
try:
# static files are not rendered
if "static" not in path and "mootools" not in path:
t =…
- Record
PYSEC-2026-492- CVSS 3.1
- 9.1 Critical
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H - Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
pyload-ngPyPI |
through 0.5.0 | None known |
pyload-ngPyPI |
through 0.5.0, plus listed versions 0.5.0a5.dev5280.5.0a5.dev5320.5.0a5.dev5350.5.0a5.dev5360.5.0a5.dev537 Show 45 moreShow less0.5.0a5.dev5390.5.0a5.dev5400.5.0a5.dev5450.5.0a5.dev5620.5.0a5.dev5640.5.0a5.dev5650.5.0a6.dev5700.5.0a6.dev5780.5.0a6.dev5870.5.0a7.dev5960.5.0a8.dev6020.5.0a9.dev6150.5.0a9.dev6290.5.0a9.dev6320.5.0a9.dev6410.5.0a9.dev6430.5.0a9.dev6550.5.0a9.dev8060.5.0b1.dev10.5.0b1.dev20.5.0b1.dev30.5.0b1.dev40.5.0b1.dev50.5.0b2.dev100.5.0b2.dev110.5.0b2.dev120.5.0b2.dev90.5.0b3.dev1000.5.0b3.dev1010.5.0b3.dev130.5.0b3.dev140.5.0b3.dev170.5.0b3.dev180.5.0b3.dev190.5.0b3.dev200.5.0b3.dev210.5.0b3.dev220.5.0b3.dev240.5.0b3.dev260.5.0b3.dev270.5.0b3.dev280.5.0b3.dev290.5.0b3.dev300.5.0b3.dev310.5.0b3.dev32 |
None known |
History
| Recorded | Changed | Sources |
|---|---|---|
| aliases | EUVD | |
| first indexed | CVE List, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2024-32880 is a critical vulnerability (CVSS 9.1) in Pyload, published April 24, 2024. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 1.35%, the 71st percentile. No source names a fixed version.
References
Left out here: 56 versions and the rest of 3 source descriptions. The JSON record has them.