Starlette 的 BadHost 漏洞:一个畸形 Host 头如何撕开 AI 代理的访问控制

2026-06-01 27 预计阅读时间:1 分钟
来源:infoq.com AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:8 分钟

Starlette 是 Python Web 生态中用量最广的轻量框架之一——每周下载量 3.25 亿,FastAPI、Uvicorn 的底层 HTTP 处理都依赖它。而最近披露的 BadHost 漏洞(高危),让一个看似不起眼的 HTTP Host 头,变成了绕过路径级访问控制的钥匙。对于把 Starlette 当作 AI 代理、LLM 网关、模型评估服务入口的团队来说,这把钥匙直接通向敏感基础设施。

畸形 Host 头怎么绕过鉴权?

Starlette 的路径中间件(PathMiddleware)和基于路径的访问控制,依赖请求的 path 属性做匹配。问题出在 Starlette 解析 Host 头的方式:当客户端发送一个畸形 Host 值(例如包含路径分隔符 / 或特殊编码),框架在重建请求目标时,会把 Host 中的路径片段混入 scope["path"],导致实际路由匹配的路径与开发者预期不一致。

简单说:你以为中间件守住了 /admin/agents,攻击者用畸形 Host 头让框架看到的路径变成了 /public/something,鉴权规则直接跳过。

这不是理论推演——Starlette 的请求重建逻辑确实存在这段代码路径,且影响所有依赖 scope["path"] 做权限判断的上层框架,包括 FastAPI。

AI 代理和 LLM 网关为什么首当其冲

当前大量 AI 代理(Agent)服务、LLM 网关和模型评估平台用 FastAPI/Starlette 暴露 HTTP 接口。典型部署架构里:

  • LLM 网关:统一入口,按路径区分 /v1/chat/completions(公开)和 /admin/keys(内部),路径级鉴权是第一道防线。
  • AI 代理服务:代理执行层暴露 /agent/run,管理接口暴露 /agent/config,后者通常只允许内部调用。
  • 评估器(Evaluator):接收模型输出做评分,/evaluate 公开,/evaluate/debug 含敏感日志只限内部。

BadHost 让这些路径级防线失效。攻击者不需要拿到 token,只需要构造一个畸形 Host 头,就能访问本应受限的管理接口、密钥管理端点、代理配置面板。

复现与验证:用 curl 触发畸形 Host

以下示例展示如何用 curl 发送畸形 Host 头,验证你的 Starlette/FastAPI 服务是否受影响。请只在自己的测试环境运行。

# 正常请求 —— 被鉴权中间件拦截,返回 403
curl -v http://localhost:8000/admin/agents \
  -H "Authorization: Bearer invalid-token"

# 畸形 Host 请求 —— Host 中嵌入路径片段,尝试让框架误判 path
curl -v http://localhost:8000/admin/agents \
  -H "Host: legit-host.com/public/health"

# 另一种变体:Host 包含斜杠和编码
curl -v http://localhost:8000/admin/agents \
  -H "Host: legit-host.com%2fpublic%2fhealth"

# 观察响应:如果畸形 Host 请求返回了 200 或非 403,
# 说明路径被错误解析,BadHost 漏洞存在

关键观察点:对比两个请求的响应状态码。如果畸形 Host 请求绕过了鉴权返回正常内容,服务存在漏洞。

修复方案:三层防御

第一层:升级 Starlette

关注 Starlette 官方发布的安全补丁版本,升级到修复 BadHost 的版本是根本解决。在 pyproject.tomlrequirements.txt 中锁定补丁版本:

# pyproject.toml
[project]
dependencies = [
    "starlette>=0.x.x",  # 替换 x.x 为官方补丁版本号
]
# 确认当前版本
pip show starlette | grep Version

# 升级
pip install --upgrade starlette

第二层:中间件层严格校验 Host 头

在补丁发布前或作为纵深防御,加一层 Host 白名单中间件,直接拒绝畸形值:

# host_guard.py —— 添加到 FastAPI/Starlette 应用
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import Response
import re

VALID_HOST_PATTERN = re.compile(r^[a-z0-9][a-z0-9\.\-]*[a-z0-9]$  # 仅允许合法域名字符

class HostGuardMiddleware(BaseHTTPMiddleware):
    def __init__(self, app, allowed_hosts: set[str]):
        super().__init__(app)
        self.allowed_hosts = allowed_hosts

    async def dispatch(self, request, call_next):
        host = request.headers.get("host", "")
        # 去掉可能的端口部分
        hostname = host.split(":")[0]

        # 拒绝包含斜杠、编码字符或不在白名单中的 Host
        if "/" in host or "%" in host:
            return Response("Malformed Host header", status_code=400)

        if hostname not in self.allowed_hosts:
            return Response("Host not allowed", status_code=403)

        return await call_next(request)


# FastAPI 中挂载
from fastapi import FastAPI

app = FastAPI()

ALLOWED_HOSTS = {"api.yourcompany.com", "localhost"}  # 按你的部署填写

app.add_middleware(HostGuardMiddleware, allowed_hosts=ALLOWED_HOSTS)

第三层:鉴权逻辑不要只依赖 path

路径级鉴权是便利的简化,但 BadHost 证明它不可靠。更稳健的做法:

  • 敏感端点用独立鉴权装饰器:每个管理路由加 Depends(require_admin),不依赖中间件的全局路径匹配。
  • 网关层做路由隔离:公开 API 和管理 API 分不同端口或不同服务暴露,中间用 nginx/Envoy 做硬隔离。
# 独立鉴权示例 —— 不依赖路径中间件
from fastapi import FastAPI, Depends, HTTPException

app = FastAPI()

def require_admin(token: str = Depends(...)):
    # 实际项目中从 Header 提取并验证
    if token != "expected-admin-secret":
        raise HTTPException(status_code=403, detail="Forbidden")

# 管理端点:每个路由独立鉴权
@app.get("/admin/agents", dependencies=[Depends(require_admin)])
async def list_agents():
    return {"agents": [...]}

# 公开端点:无额外鉴权
@app.get("/v1/chat/completions")
async def chat():
    return {"response": "..."}

部署检查清单

上线前逐项确认:

检查项 操作
Starlette 版本 pip show starlette,确认已升级到补丁版本
FastAPI 版本 FastAPI 依赖 Starlette,同步升级
Host 头校验 部署 HostGuardMiddleware 或 nginx 层 server_name 白名单
路径鉴权依赖 检查是否有路由仅靠中间件路径匹配做鉴权,补上独立鉴权
管理接口暴露范围 /admin/config/keys 类端点是否与公开 API 同端口?考虑拆分
日志监控 添加 Host 头异常告警:记录包含 /% 的 Host 值

BadHost 的核心教训:HTTP 头是客户端可控的输入,任何依赖它做安全判断的逻辑都必须做严格校验。在 AI 代理和 LLM 网关场景下,路径级访问控制是一道常见防线,但它比大多数人以为的更脆弱。升级框架、加 Host 白名单、让敏感端点自带独立鉴权——三层叠加,才是该有的纵深。


相关推荐