CVE-2025-61773
Description
pyLoad is a free and open-source download manager written in Python. In versions prior to 0.5.0b3.dev91, pyLoad web interface contained insufficient input validation in both the Captcha script endpoint and the Click'N'Load (CNL) Blueprint. This flaw allowed untrusted user input to be processed unsafely, which could be exploited by an attacker to inject arbitrary content into the web UI or manipulate request handling. The vulnerability could lead to client-side code execution (XSS) or other unintended behaviors when a malicious payload is submitted. user-supplied parameters from HTTP requests were not adequately validated or sanitized before being passed into the application logic and response generation. This allowed crafted input to alter the expected execution flow. CNL (Click'N'Load) blueprint exposed unsafe handling of untrusted parameters in HTTP requests. The application did not consistently enforce input validation or encoding, making it possible for an attacker to craft malicious requests. Version 0.5.0b3.dev91 contains a patch for the issue.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
pyload-ngPyPI | < 0.5.0b3.dev91 | 0.5.0b3.dev91 |
Affected products
1Patches
15823327d0b79strengthen Input validation in captcha script and CNL Blueprint related to unsafe handling of untrusted input in the web UI (#4624)
2 files changed · +36 −28
src/pyload/webui/app/blueprints/cnl_blueprint.py+5 −6 modified@@ -5,11 +5,11 @@ from functools import wraps from urllib.parse import unquote +import flask from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from werkzeug.utils import secure_filename -import flask -from flask.json import jsonify from pyload.core.api import Destination from pyload.core.utils.convert import to_str from pyload.core.utils.misc import eval_js @@ -86,11 +86,10 @@ def addcrypted(): "package", flask.request.form.get("source", flask.request.form.get("referer")) ) dl_path = api.get_config_value("general", "storage_folder") - dlc_filename = package.replace("/", "").replace("\\", "").replace(":", "") + ".dlc" - dlc_path = os.path.join(dl_path, dlc_filename) - dlc_path = os.path.normpath(dlc_path) + dlc_filename = secure_filename(package) + ".dlc" + dlc_path = os.path.abspath(os.path.join(dl_path, dlc_filename)) # Ensure dlc_path is within dl_path - if not os.path.abspath(dlc_path).startswith(os.path.abspath(dl_path) + os.sep): + if not dlc_path.startswith(os.path.abspath(dl_path) + os.sep): return "failed: invalid package name\r\n", 400 dlc = flask.request.form["crypted"].replace(" ", "+") with open(dlc_path, mode="wb") as fp:
src/pyload/webui/app/static/js/captcha-interactive.user.js+31 −22 modified@@ -34,20 +34,29 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ -(function() { +(function () { 'use strict'; // this function listens to messages from the pyload main page - window.addEventListener('message', function(e) { + window.addEventListener('message', function (e) { + // Restrict accepted origins to a trusted set. + const trustedOrigins = [ + "https://pyload.net", // Replace with your trusted parent origins + // Add additional origins as needed. + ]; + if (!trustedOrigins.includes(e.origin)) { + // Ignore messages from untrusted or unknown origins + return; + } let request; try { request = JSON.parse(e.data); - } catch(e) { + } catch (e) { return } - if(request.constructor === {}.constructor && request.actionCode === "pyloadActivateInteractive") { + if (request.constructor === {}.constructor && request.actionCode === "pyloadActivateInteractive") { if (request.params.script) { - const sig = new KJUR.crypto.Signature({"alg": "SHA384withRSA", "prov": 'cryptojs/jsrsa'}); + const sig = new KJUR.crypto.Signature({ "alg": "SHA384withRSA", "prov": 'cryptojs/jsrsa' }); sig.init("-----BEGIN PUBLIC KEY-----\n" + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuEHE4uAeTeEQjIwB//YH\n" + "Gl5e058aJRCRyOvApv1iC1ZQgXGHopgEd528+AtkAZKdCRkoNCWda7L/hROpZNjq\n" + @@ -60,16 +69,16 @@ sig.updateString(request.params.script.code); if (sig.verify(request.params.script.signature)) { window.gpyload = { - isVisible : function(element) { + isVisible: function (element) { const style = window.getComputedStyle(element); return !(style.width === 0 || style.height === 0 || style.opacity === 0 || - style.display ==='none' || + style.display === 'none' || style.visibility === 'hidden' ); }, - debounce : function (fn, delay) { + debounce: function (fn, delay) { let timer = null; return function () { const context = this, args = arguments; @@ -79,44 +88,44 @@ }, delay); }; }, - submitResponse: function(response) { + submitResponse: function (response) { if (typeof gpyload.observer !== 'undefined') { gpyload.observer.disconnect(); } - const responseMessage = {actionCode: "pyloadSubmitResponse", params: {response: response}}; - parent.postMessage(JSON.stringify(responseMessage),"*"); + const responseMessage = { actionCode: "pyloadSubmitResponse", params: { response: response } }; + parent.postMessage(JSON.stringify(responseMessage), "*"); }, - activated: function() { - const responseMessage = {actionCode: "pyloadActivatedInteractive"}; - parent.postMessage(JSON.stringify(responseMessage),"*"); + activated: function () { + const responseMessage = { actionCode: "pyloadActivatedInteractive" }; + parent.postMessage(JSON.stringify(responseMessage), "*"); }, - setSize : function(rect) { + setSize: function (rect) { if (gpyload.data.rectDoc.left !== rect.left || gpyload.data.rectDoc.right !== rect.right || gpyload.data.rectDoc.top !== rect.top || gpyload.data.rectDoc.bottom !== rect.bottom) { gpyload.data.rectDoc = rect; - const responseMessage = {actionCode: "pyloadIframeSize", params: {rect: rect}}; + const responseMessage = { actionCode: "pyloadIframeSize", params: { rect: rect } }; parent.postMessage(JSON.stringify(responseMessage), "*"); } }, - data : { + data: { debounceInterval: 1500, - rectDoc: {top: 0, right: 0, bottom: 0, left: 0} + rectDoc: { top: 0, right: 0, bottom: 0, left: 0 } } }; try { let scriptFunction = new Function('request', 'gpyload', '"use strict";' + request.params.script.code); scriptFunction(request, gpyload); - } catch(err) { - console.error("pyLoad: Script aborted: " + err.name + ": " + err.message + " (" + err.stack +")"); + } catch (err) { + console.error("pyLoad: Script aborted: " + err.name + ": " + err.message + " (" + err.stack + ")"); return; } if (typeof gpyload.getFrameSize === "function") { const checkDocSize = gpyload.debounce(() => { - window.scrollTo(0,0); + window.scrollTo(0, 0); var rect = gpyload.getFrameSize(); gpyload.setSize(rect); }, gpyload.data.debounceInterval); - gpyload.observer = new MutationObserver(function(mutationsList) { + gpyload.observer = new MutationObserver(function (mutationsList) { checkDocSize(); }); const js_script = document.createElement("script");
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5News mentions
0No linked articles in our index yet.