PYSEC-2026-2998

Vulnerability from pysec - Published: 2026-07-13 15:19 - Updated: 2026-07-13 16:05
VLAI
Details

Summary

The fix for CVE-2026-33509 prevents setting storage_folder inside PKGDIR or userdir, but does NOT protect the Flask session directory (/tmp/pyLoad/flask). An authenticated attacker can set storage_folder to the session directory and download session files of other users via /files/get/, leading to account takeover.

Details

The fix in src/pyload/core/api/__init__.py:

directories = [PKGDIR, userdir]
if any(directories[0].startswith(d) for d in directories[1:]):
    return  # blocked

But the Flask session directory is:

session_storage_path = os.path.join(api.get_cachedir(), "flask")
# = /tmp/pyLoad/flask  ← NOT blocked by fix

Attack Chain

  1. Attacker (admin) sets storage_folder = /tmp/pyLoad/flask
  2. Fix does NOT block this — /tmp/pyLoad/flask not inside PKGDIR or userdir
  3. Attacker requests GET /files/get/<victim_session_filename>
  4. send_from_directory('/tmp/pyLoad/flask', session_file) serves victim's session
  5. Attacker uses stolen session → Account Takeover

PoC

POC

import os

PKGDIR = "/usr/lib/python3/dist-packages/pyload"
userdir = os.path.expanduser("~/.pyload")
session_dir = "/tmp/pyLoad/flask"

correct_case = lambda x: x
directories = [
    correct_case(os.path.join(os.path.realpath(d), ""))
    for d in [session_dir, PKGDIR, userdir]
]
blocked = any(directories[0].startswith(d) for d in directories[1:])

print(f"Fix blocks session_dir: {blocked}")
# Output: Fix blocks session_dir: False  ← BYPASS CONFIRMED

Impact

Authenticated admin can steal sessions of other users → Account Takeover.

Suggested Fix

blocked_dirs = [PKGDIR, userdir, api.get_cachedir()]
directories = [
    os.path.join(os.path.realpath(d), "")
    for d in [value] + blocked_dirs
]
if any(directories[0].startswith(d) for d in directories[1:]):
    return
Impacted products
Name purl
pyload-ng pkg:pypi/pyload-ng

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "pyload-ng",
        "purl": "pkg:pypi/pyload-ng"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "last_affected": "0.5.0b3.dev99"
            }
          ],
          "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",
        "0.5.0b3.dev75",
        "0.5.0b3.dev76",
        "0.5.0b3.dev77",
        "0.5.0b3.dev78",
        "0.5.0b3.dev79",
        "0.5.0b3.dev80",
        "0.5.0b3.dev81",
        "0.5.0b3.dev82",
        "0.5.0b3.dev85",
        "0.5.0b3.dev87",
        "0.5.0b3.dev88",
        "0.5.0b3.dev89",
        "0.5.0b3.dev90",
        "0.5.0b3.dev91",
        "0.5.0b3.dev92",
        "0.5.0b3.dev93",
        "0.5.0b3.dev94",
        "0.5.0b3.dev95",
        "0.5.0b3.dev96",
        "0.5.0b3.dev97",
        "0.5.0b3.dev98",
        "0.5.0b3.dev99"
      ]
    }
  ],
  "aliases": [
    "CVE-2026-45306",
    "GHSA-w727-595x-pc3r"
  ],
  "details": "## Summary\nThe fix for CVE-2026-33509 prevents setting `storage_folder` inside `PKGDIR` or `userdir`, but does NOT protect the Flask session directory (`/tmp/pyLoad/flask`). An authenticated attacker can set `storage_folder` to the session directory and download session files of other users via `/files/get/`, leading to account takeover.\n\n## Details\n\nThe fix in `src/pyload/core/api/__init__.py`:\n\n```python\ndirectories = [PKGDIR, userdir]\nif any(directories[0].startswith(d) for d in directories[1:]):\n    return  # blocked\n```\n\nBut the Flask session directory is:\n```python\nsession_storage_path = os.path.join(api.get_cachedir(), \"flask\")\n# = /tmp/pyLoad/flask  \u2190 NOT blocked by fix\n```\n\n## Attack Chain\n\n1. Attacker (admin) sets `storage_folder = /tmp/pyLoad/flask`\n2. Fix does NOT block this \u2014 `/tmp/pyLoad/flask` not inside `PKGDIR` or `userdir`\n3. Attacker requests `GET /files/get/\u003cvictim_session_filename\u003e`\n4. `send_from_directory(\u0027/tmp/pyLoad/flask\u0027, session_file)` serves victim\u0027s session\n5. Attacker uses stolen session \u2192 **Account Takeover**\n\n## PoC\n\n\u003cimg width=\"592\" height=\"408\" alt=\"POC\" src=\"https://github.com/user-attachments/assets/936b9f56-325b-437d-9edd-e0d5bb995187\" /\u003e\n\n```python\nimport os\n\nPKGDIR = \"/usr/lib/python3/dist-packages/pyload\"\nuserdir = os.path.expanduser(\"~/.pyload\")\nsession_dir = \"/tmp/pyLoad/flask\"\n\ncorrect_case = lambda x: x\ndirectories = [\n    correct_case(os.path.join(os.path.realpath(d), \"\"))\n    for d in [session_dir, PKGDIR, userdir]\n]\nblocked = any(directories[0].startswith(d) for d in directories[1:])\n\nprint(f\"Fix blocks session_dir: {blocked}\")\n# Output: Fix blocks session_dir: False  \u2190 BYPASS CONFIRMED\n```\n\n## Impact\nAuthenticated admin can steal sessions of other users \u2192 Account Takeover.\n\n## Suggested Fix\n```python\nblocked_dirs = [PKGDIR, userdir, api.get_cachedir()]\ndirectories = [\n    os.path.join(os.path.realpath(d), \"\")\n    for d in [value] + blocked_dirs\n]\nif any(directories[0].startswith(d) for d in directories[1:]):\n    return\n```",
  "id": "PYSEC-2026-2998",
  "modified": "2026-07-13T16:05:53.241689Z",
  "published": "2026-07-13T15:19:05.979484Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/pyload/pyload/security/advisories/GHSA-w727-595x-pc3r"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-45306"
    },
    {
      "type": "ADVISORY",
      "url": "https://github.com/advisories/GHSA-r7mc-x6x7-cqxx"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/pyload/pyload"
    },
    {
      "type": "PACKAGE",
      "url": "https://pypi.org/project/pyload-ng"
    },
    {
      "type": "ADVISORY",
      "url": "https://github.com/advisories/GHSA-w727-595x-pc3r"
    }
  ],
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "pyLoad Has Incomplete Fix for CVE-2026-33509 -storage_folder Bypass via Session Directory in pyLoad"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

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.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…