GHSA-C3GC-9PF2-84GG
Vulnerability from github – Published: 2026-05-06 17:54 – Updated: 2026-05-06 17:54Summary
pyload-ng WebUI returns full Python traceback details to clients on unhandled exceptions.
Because /web/<path:filename> is reachable without authentication and renders attacker-controlled template names, an unauthenticated user can reliably trigger a server exception (for example by requesting a non-existent template) and receive internal stack traces in the HTTP response.
Details
The issue is caused by the combination of:
- Unauthenticated template-render route:
src/pyload/webui/app/blueprints/app_blueprint.py:32-36@bp.route("/web/<path:filename>", endpoint="web")data = render_template(filename)with user-controlledfilename-
no
@login_required(...)on this route -
Global exception handler exposes traceback to response:
src/pyload/webui/app/handlers.py:14-27tb = traceback.format_exc()messages.extend(tb.split('\n'))-
returned in rendered error page for all exceptions
-
Error page renders all
messages: src/pyload/webui/app/themes/modern/templates/base.html:217-219- loops over
messagesand prints them in response HTML
So any unhandled exception can disclose internal implementation details (stack frames, source paths, exception metadata) to remote unauthenticated clients.
This is a core behavior issue in default WebUI error handling
PoC
#!/usr/bin/env python3
from __future__ import annotations
import re
import shutil
import tempfile
import traceback
from pathlib import Path
ROOT = Path(__file__).resolve().parent / "pyload" / "src" / "pyload"
def read_text(rel: str) -> str:
return (ROOT / rel).read_text(encoding="utf-8")
def route_has_no_login_required(app_blueprint: str) -> bool:
m = re.search(
r'@bp\\.route\\("/web/<path:filename>", endpoint="web"\\)\\s*'
r"def render\\(filename\\):(?P<body>.*?)(?:\\n\\n@bp\\.route|\\Z)",
app_blueprint,
re.DOTALL,
)
if not m:
return False
block_start = max(0, m.start() - 200)
block = app_blueprint[block_start:m.end()]
return "@login_required(" not in block
def main() -> None:
workdir = Path(tempfile.mkdtemp(prefix="pyload-traceback-infoleak-"))
try:
app_blueprint = read_text("webui/app/blueprints/app_blueprint.py")
handlers = read_text("webui/app/handlers.py")
base_template = read_text("webui/app/themes/modern/templates/base.html")
unauth_web_route = '/web/<path:filename>' in app_blueprint and route_has_no_login_required(app_blueprint)
user_controlled_template_name = "render_template(filename)" in app_blueprint
handler_uses_traceback = "traceback.format_exc()" in handlers
handler_appends_trace = "messages.extend(tb.split('\\n'))" in handlers
global_exception_handler = "(Exception, handle_exception_error)" in handlers
template_renders_messages = "{% for message in messages %}" in base_template and "{{message}}" in base_template
leaked_traceback_keyword = False
leaked_exception_type = False
try:
raise RuntimeError("forced-poc-error")
except Exception:
tb = traceback.format_exc()
messages = [f"Error 500: forced-poc-error"]
messages.extend(tb.split("\\n"))
joined = "\\n".join(messages)
leaked_traceback_keyword = "Traceback (most recent call last)" in joined
leaked_exception_type = "RuntimeError: forced-poc-error" in joined
repro_success = all(
[
unauth_web_route,
user_controlled_template_name,
handler_uses_traceback,
handler_appends_trace,
global_exception_handler,
template_renders_messages,
leaked_traceback_keyword,
leaked_exception_type,
]
)
print("unauth_web_route=", unauth_web_route)
print("user_controlled_template_name=", user_controlled_template_name)
print("handler_uses_traceback=", handler_uses_traceback)
print("handler_appends_trace=", handler_appends_trace)
print("global_exception_handler=", global_exception_handler)
print("template_renders_messages=", template_renders_messages)
print("leaked_traceback_keyword=", leaked_traceback_keyword)
print("leaked_exception_type=", leaked_exception_type)
print("traceback_infoleak_repro_success=", repro_success)
finally:
shutil.rmtree(workdir, ignore_errors=True)
print("cleanup_done=True")
if __name__ == "__main__":
main()
Observed result:
unauth_web_route= True
user_controlled_template_name= True
handler_uses_traceback= True
handler_appends_trace= True
global_exception_handler= True
template_renders_messages= True
leaked_traceback_keyword= True
leaked_exception_type= True
traceback_infoleak_repro_success= True
cleanup_done=True
Impact
- Vulnerability type: Information disclosure (stack trace / internal path leakage).
- Attack surface: unauthenticated WebUI request path.
- Exposes internal error details that help attackers map application internals and improve exploit reliability for follow-on attacks.
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "pyload-ng"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.5.0b3.dev100"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-44226"
],
"database_specific": {
"cwe_ids": [
"CWE-209"
],
"github_reviewed": true,
"github_reviewed_at": "2026-05-06T17:54:20Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "### Summary\n`pyload-ng` WebUI returns full Python traceback details to clients on unhandled exceptions.\n\nBecause `/web/\u003cpath:filename\u003e` is reachable without authentication and renders attacker-controlled template names, an unauthenticated user can reliably trigger a server exception (for example by requesting a non-existent template) and receive internal stack traces in the HTTP response.\n\n### Details\nThe issue is caused by the combination of:\n\n1. Unauthenticated template-render route:\n- `src/pyload/webui/app/blueprints/app_blueprint.py:32-36`\n - `@bp.route(\"/web/\u003cpath:filename\u003e\", endpoint=\"web\")`\n - `data = render_template(filename)` with user-controlled `filename`\n - no `@login_required(...)` on this route\n\n2. Global exception handler exposes traceback to response:\n- `src/pyload/webui/app/handlers.py:14-27`\n - `tb = traceback.format_exc()`\n - `messages.extend(tb.split(\u0027\\n\u0027))`\n - returned in rendered error page for all exceptions\n\n3. Error page renders all `messages`:\n- `src/pyload/webui/app/themes/modern/templates/base.html:217-219`\n - loops over `messages` and prints them in response HTML\n\nSo any unhandled exception can disclose internal implementation details (stack frames, source paths, exception metadata) to remote unauthenticated clients. \n\nThis is a core behavior issue in default WebUI error handling\n\n### PoC\n```python\n#!/usr/bin/env python3\nfrom __future__ import annotations\n\nimport re\nimport shutil\nimport tempfile\nimport traceback\nfrom pathlib import Path\n\n\nROOT = Path(__file__).resolve().parent / \"pyload\" / \"src\" / \"pyload\"\n\n\ndef read_text(rel: str) -\u003e str:\n return (ROOT / rel).read_text(encoding=\"utf-8\")\n\n\ndef route_has_no_login_required(app_blueprint: str) -\u003e bool:\n m = re.search(\n r\u0027@bp\\\\.route\\\\(\"/web/\u003cpath:filename\u003e\", endpoint=\"web\"\\\\)\\\\s*\u0027\n r\"def render\\\\(filename\\\\):(?P\u003cbody\u003e.*?)(?:\\\\n\\\\n@bp\\\\.route|\\\\Z)\",\n app_blueprint,\n re.DOTALL,\n )\n if not m:\n return False\n block_start = max(0, m.start() - 200)\n block = app_blueprint[block_start:m.end()]\n return \"@login_required(\" not in block\n\n\ndef main() -\u003e None:\n workdir = Path(tempfile.mkdtemp(prefix=\"pyload-traceback-infoleak-\"))\n try:\n app_blueprint = read_text(\"webui/app/blueprints/app_blueprint.py\")\n handlers = read_text(\"webui/app/handlers.py\")\n base_template = read_text(\"webui/app/themes/modern/templates/base.html\")\n\n unauth_web_route = \u0027/web/\u003cpath:filename\u003e\u0027 in app_blueprint and route_has_no_login_required(app_blueprint)\n user_controlled_template_name = \"render_template(filename)\" in app_blueprint\n handler_uses_traceback = \"traceback.format_exc()\" in handlers\n handler_appends_trace = \"messages.extend(tb.split(\u0027\\\\n\u0027))\" in handlers\n global_exception_handler = \"(Exception, handle_exception_error)\" in handlers\n template_renders_messages = \"{% for message in messages %}\" in base_template and \"{{message}}\" in base_template\n\n leaked_traceback_keyword = False\n leaked_exception_type = False\n try:\n raise RuntimeError(\"forced-poc-error\")\n except Exception:\n tb = traceback.format_exc()\n messages = [f\"Error 500: forced-poc-error\"]\n messages.extend(tb.split(\"\\\\n\"))\n joined = \"\\\\n\".join(messages)\n leaked_traceback_keyword = \"Traceback (most recent call last)\" in joined\n leaked_exception_type = \"RuntimeError: forced-poc-error\" in joined\n\n repro_success = all(\n [\n unauth_web_route,\n user_controlled_template_name,\n handler_uses_traceback,\n handler_appends_trace,\n global_exception_handler,\n template_renders_messages,\n leaked_traceback_keyword,\n leaked_exception_type,\n ]\n )\n\n print(\"unauth_web_route=\", unauth_web_route)\n print(\"user_controlled_template_name=\", user_controlled_template_name)\n print(\"handler_uses_traceback=\", handler_uses_traceback)\n print(\"handler_appends_trace=\", handler_appends_trace)\n print(\"global_exception_handler=\", global_exception_handler)\n print(\"template_renders_messages=\", template_renders_messages)\n print(\"leaked_traceback_keyword=\", leaked_traceback_keyword)\n print(\"leaked_exception_type=\", leaked_exception_type)\n print(\"traceback_infoleak_repro_success=\", repro_success)\n finally:\n shutil.rmtree(workdir, ignore_errors=True)\n print(\"cleanup_done=True\")\n\n\nif __name__ == \"__main__\":\n main()\n```\n\nObserved result:\n```text\nunauth_web_route= True\nuser_controlled_template_name= True\nhandler_uses_traceback= True\nhandler_appends_trace= True\nglobal_exception_handler= True\ntemplate_renders_messages= True\nleaked_traceback_keyword= True\nleaked_exception_type= True\ntraceback_infoleak_repro_success= True\ncleanup_done=True\n```\n\n### Impact\n- Vulnerability type: Information disclosure (stack trace / internal path leakage).\n- Attack surface: unauthenticated WebUI request path.\n- Exposes internal error details that help attackers map application internals and improve exploit reliability for follow-on attacks.",
"id": "GHSA-c3gc-9pf2-84gg",
"modified": "2026-05-06T17:54:20Z",
"published": "2026-05-06T17:54:20Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/pyload/pyload/security/advisories/GHSA-c3gc-9pf2-84gg"
},
{
"type": "PACKAGE",
"url": "https://github.com/pyload/pyload"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "PyLoad vulnerable to unauthenticated traceback disclosure via global exception handler in WebUI"
}
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.