跳到主要内容

t

Public

2026年9月2日,CISA把七个漏洞批量塞进已知被利用漏洞(KEV)目录,清单里混着一个看起来最不像主角的家伙:CVE-2026-48710,Starlette框架的HTTP请求走私漏洞,CVSS 6.5,"Moderate"。在Switchvox的9.8分未认证SQL注入、SonicWall的10.0分SSRF、JFrog的认证绕过中间,这个6.5分的中危漏洞安静地排着队——但它身后拖着的东西比分数吓人得多:FastAPI、LiteLLM、vLLM、Text Generation Inference、绝大多数OpenAI兼容代理、大量MCP服务器、智能体框架、评测面板与模型管理界面。安全界给它起了个名字:BadHost。而它的利用原语,是一个字符。

把镜头倒回5月22日。X41 D-Sec与OSTIF公开披露这个漏洞时,给出的最小化PoC让很多人揉了揉眼睛:

curl -i -H 'Host: foo' http://target/admin # 403, 拦截 curl -i -H 'Host: foo?' http://target/admin # 200, 放行

同一个受保护的/admin路径,同一个未认证的陌生客户端,两次请求之间唯一的差别,是Host头末尾多了一个问号。一个问号,中间件看见的世界就换了模样——这是BadHost全部魔法的核心,也是它从"库层路径字符串不一致"膨胀成"Python AI基础设施系统性认证绕过"的起点。如今KEV在列,意味着这个6.5分漏洞已被确认在野利用:从补丁发布到攻击者进场,中间隔了三个半月。

受影响的不只是Starlette:四十万依赖项目的传导链

先把波及面说清楚。Starlette本身是个轻量级Python ASGI框架,在技术雷达上长期活在FastAPI的影子里——FastAPI直接构建于Starlette之上,而FastAPI又是过去几年Python Web与AI服务的绝对地基。X41 D-Sec统计,GitHub上依赖Starlette的项目超过40万个。更隐蔽的是传导方式:一个应用可能从未在自己的requirements里写过starlette——是FastAPI拉进来的,或者某个MCP服务器、智能体框架、评测工具把它打包进了自己的镜像。研究者反复强调的一句话值得原样抄录:"一个应用即使开发者从未安装过Starlette也可能暴露,因为其他组件可能替它装了。"

具体点名受影响的下游栈:vLLM(漏洞正是在它身上被发现的)、LiteLLM代理、Text Generation Inference及其包装层、大多数"OpenAI API shim"项目(给本地模型套OpenAI兼容接口的那一批)、大量MCP服务器、智能体框架(agent harnesses)、评测仪表盘、模型注册表,以及无数构建在FastAPI上的内部管理面板。SecWest的定性毫不客气:这覆盖了过去两年搭建起来的"Python AI基础设施的大多数"。换句话说,你在机房或云上跑的那套自托管LLM服务栈——推理网关、模型服务、密钥管理、agent工具链——大概率就站在这个漏洞的射程里。

病灶解剖:一个URL的两种读法

Starlette对每个请求维护着两份"地址"。一份是ASGI scope字典里的原始路径——HTTP服务器(uvicorn/hypercorn/daphne/granian)从线上字节流里解析出来的,路由器就用它做分发。另一份是request.url——一个按需重建的便利对象,供应用代码读取。问题出在重建算法上:受影响版本直接拼接http://{Host头}{原始路径},再对结果重新解析一次,而Host头在拼接前从未按RFC 9112 §3.2 / RFC 3986 §3.2.2的语法校验过

按RFC文法,Host的合法形态是uri-host[":"port],其中uri-host遵循RFC 3986的受限host文法——斜杠、问号、井号都不在合法字符集里。但HTTP头本质上是客户端想写什么就写什么的字符串,服务器不校验,它就原样进入拼接。而/?#这三个字符在URL文法里各自坐拥一个王座:路径起点、查询起点、片段起点。它们一旦混进Host,重新解析时就会把整个URL的领土重新划界。

官方公告给的例子:客户端请求http://example.com/foo,但带上畸形Host头:

GET /foo HTTP/1.1 Host: example.com/abc?bar=

Starlette重建成http://example.com/abc?bar=/foo,重新解析出的request.url.path/abc——而路由器分发的,仍然是线上真实收到的/foo。回看PoC就通了:Host: foo?让问号提前截断了Host,拼出的URL里真实路径/admin整个滑进了"查询串"的领地,中间件读request.url.path时看到的路径已然面目全非。CWE-444给这类问题定的性极其精准:"HTTP请求的不一致解释"——同一个请求,应用里的两个组件得出了两个不同的结论,这是请求走私与安全检查绕过的教科书条件。

两套真相的运行时:路由器看真话,中间件看假话

漏洞的危害结构可以这样拆解:路由器 dispatch 在真实路径上,端点在真实路径上执行;而所有基于request.url做安全决策的中间件,盯着的却是一个被投毒的幻影路径。中间件说"这个请求访问的是/abc,放行"——它没说谎,它看到的就是/abc;路由器说"这个请求访问的是/admin,执行"——它也没说谎。两个诚实的组件,拼出一个不安全的系统。

哪些安全决策会踩中这个裂缝?X41的清单按杀伤力排序:基于路径前缀的认证(/admin/v1/models/internal/metrics/shutdown、工具执行端点);在中间件层实现的租户/工作区隔离;仅限内部跳转或已认证会话的端点走私;以及两条最深的影响链——当被绕开的端点会执行外发请求时的SSRF(打云元数据服务与内网主机),和当端点暴露工具执行、插件加载、从URL加载模型、文件上传、代码求值功能时的RCE

对LLM网关这个物种,研究者的判断直接而严厉:LiteLLM的管理与密钥管理面、vLLM的模型与运行时控制面,只要部署是直连ASGI的,就应当视为已经暴露。想想这些端点背后是什么:虚拟密钥的铸造与吊销、模型加载与卸载、推理参数配置、微调任务提交、数据集上传、能执行shell命令的agent工具。一个问号,把它们全部从"需要管理员凭证"的保险柜里搬到了"任何人可敲门"的门厅。

6.5分的公案:CVSS这次错在哪

BadHost可能是2026年讨论CVSS失灵时被引用最多的案例。Starlette维护者在GHSA-86qp-5c8j-p5mr里给出的评分是6.5 Moderate,向量AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N——从库层视角看,这评分甚至是"规矩"的:一个路径字符串不一致,机密性影响Low,完整性影响Low,可用性无影响,谁来打分都是这个区间。X41自己给的7.0也没高到哪去。

但发现者们随后在下游看到的东西,让两个分数都显得苍白。SecWest的公告说得透彻:"这是一个原语,不是一个结局。它让生态付出多少代价,取决于库的消费者拿request.url去做了什么。"而X41的实际审计发现,多个流行开源项目的中间件恰恰把安全决策压在了request.url上——从单字符原语到认证绕过、SSRF、RCE的完整链条,是"演示过的",不是"理论上的"。于是同一份公告里出现了三个互相打架的定级:库层6.5 Moderate,厂商视角7.0 High,发现者视角Critical。GHSA页面上那句括号里的评语——"(评分严重低估了该漏洞在下游的严重性)"——是安全社区罕见地写在正式公告里的抱怨。

公案的另一面是发布方式。补丁随Starlette 1.0.1安静地发布,没有伴随任何生态级警报。受影响范围写的是starlette >= 0.8.3, < 1.0.1——横跨数年的版本区间。一个会影响四十万下游项目的漏洞,以中危评分、零仪式感的方式滑进了发版管道。OSTIF事后解释为什么要做扩大化披露:正是补丁 uptake 缓慢、且持续发现更多存活在线上的脆弱服务,让他们"严重担忧"。

发现始末:一次vLLM审计的意外收获

这个漏洞的身世颇有戏剧性。2026年1月27日,X41 D-Sec的一位资深安全研究员正在做vLLM的源代码审计——由OSTIF组织管理、Alpha-Omega项目资助的例行工作,下游影响分析还得到了AWS的赞助。审计中他注意到一个模式:路径相关的安全逻辑似乎可以被Host头干扰。追根溯源,问题不在vLLM,而在它脚下的Starlette。从"Starlette的怪癖"到"LLM服务原语",这条路径不是理论推演——它就是发现路径本身。值得一提的是,GHSA的致谢名单上还有另外两位独立报告者(ehhthing与nic-lovin),三个团队在不同方向上撞见了同一颗地雷。

披露时间线完整记录在案:2月4日通知Starlette维护者并附上PoC,2月5日厂商确认,3月1日厂商提出补丁,然后是将近三个月的悬置,5月21日补丁随1.0.1发布,5月22日公开披露、CVE分配、badhost.org上线。SecWest在时间线后面加了一句注解:补丁从提案到发布坐了将近三个月的冷板凳,且公开披露与补丁发布同日落地——攻击者的利用开发提前量为零。

OSTIF在披露文中特意为维护者说了话,值得一字不差地体会:Starlette的维护者Kludex"这几周过得很糟"——披露与补丁流程漫长,各方轮番联系,同时还要应付2026年每个开源项目都在面对的一大堆安全报告。"这是一个经典的'责任缺口':如果这位维护者不打补丁,成千上万个暴露的项目将不得不各自为战。他本可以不欠任何人,把这变成所有人的问题,却选择了帮助生态承担长期系统性风险的防护责任。"文末附上了Kludex的GitHub赞助链接。一个以一己之力扛住四十万项目安全脐带的人,值得这条链接被点一次。

为什么AI机房是软肋:缺席的反向代理

生产网站的Starlette/FastAPI应用大多并不直接暴露——nginx、Apache httpd、Cloudflare这类反向代理会在请求到达应用前拒绝畸形Host头,默认配置下这三家都能挡住BadHost的PoC。这层"隐形疫苗"正是CVSS争论里厂商方的底气。但X41指出了盲区:"研究、评测与开发环境往往不是这样——很多AI软件让应用服务器直接连网。"

这正是BadHost在AI栈里格外致命的结构性原因。vLLM、LiteLLM、评测框架、agent实验台的部署形态,是研究者在一台工作站、一个实验子网、一块"内网可信"的网段里直接uvicorn(或hypercorn、daphne、granian)拉起服务——没有反向代理,没有Host校验,端口直接可达。而这类环境守着的恰恰是高价值端点:模型管理、密钥签发、提示词与工具配置、微调提交、数据集上传、shell邻接的agent工具。SecWest总结的四条"为什么这对LLM生态很重要"里,最扎心的是最后一条:"自动化的门槛在地板上——请求头里的一个字符,可以无缝接入任何现有的互联网扫描工具链,也适用于任何智能体化、自动化的侦察管线。"换句话说,攻击者甚至不需要专门写扫描器,现有的masscan+nuclei流水线加一个header字段就够了。如今KEV在列,这个预判已被证实。

同一个KEV批次里的隐喻:LiteLLM也在名单上

回看9月2日CISA那份七漏洞清单,还有个耐人寻味的细节:同批入库的还有CVE-2026-59822,BerriAI LiteLLM的不当认证漏洞。BadHost的分析报告里,LiteLLM被点名"管理与密钥管理面应当视为暴露";而LiteLLM自己的原生认证缺陷也在同一周被确认在野利用。一个框架层的路径混淆漏洞,一个应用层的认证缺陷,从两个方向同时侵蚀同一个LLM网关——AI基础设施在2026年下半年承受的火力密度,从这份清单里可见一斑。把视野再拉宽一点:这已是KEV近期批次里AI相关条目的常态,前有Langflow的12连击,后有Starlette的40万依赖,攻击者对"AI栈"这个狩猎场的经营已经体系化。

防御清单:升级、改代码、查日志

处置动作按优先级排。第一,升级Starlette到1.0.1或更高——补丁(commit 764dab0)在重建request.url前按RFC 9112 §3.2 / RFC 3986 §3.2.2文法校验Host,畸形值回退到scope["server"]。但注意X41的警告:"重建并重新部署每一个固定或内嵌Starlette版本的容器、virtualenv与打包产物——LLM工具里捆绑安装是常态,宿主机上pip list是不够的,要审计镜像。"

第二,改代码,用 durable fix。把所有做安全决策的中间件、依赖项、装饰器里的request.urlrequest.url.path换成request.scope["path"]——读未经重构的原始值。研究者的原话是:"这个bug类别会复发;读取未重构的值才是持久修复。" 第三,在每一个ASGI应用前放一个会拒绝畸形Host头的反向代理,并实际验证自己的配置;HTTP/3或QUIC终结的前端要单独用PoC测过才能信任。第四,主动扫描:badhost.org提供免费的在线远程扫描器(X41 D-Sec、Persistent Security Industries与Bintech联合运营);X41还开源了扫描器、Semgrep规则与CodeQL查询(github.com/x41sec/poc),可以扫出代码库里受影响的中间件模式。

第五,翻日志做追溯狩猎,三个便宜信号:任何Host头包含/?#\@的访问记录——合法客户端不会在Host里发这些字符;任何"分发路由"与"应用记录的request.url.path"不一致的记录——每一对不相等都是利用签名;LiteLLM/vLLM上任何到达管理或模型控制端点、但审计轨迹里路径字符串异常的请求。考虑到补丁已面世三个半月, KEV入库意味着狩猎窗口早就开过了——如果日志里出现过上述信号,按已失陷处理。

结语:评分是库的,伤害是生态的

BadHost留給行业的教训可以压成三句话。第一句给漏洞管理:CVSS评分测的是组件,伤害发生在组合里。一个6.5分的"路径字符串不一致",经由FastAPI的传导、经由AI机房缺失的反向代理、经由密钥管理面与模型控制面的高价值端点,落地成Critical级的实际暴露——如果你们的补丁优先级队列严格按分数排序,这个漏洞在你的队列里躺了三个半月,正好是攻击者需要的全部时间。第二句给开发者:便利对象与原始数据之间隔着一整个解析器,安全决策永远读后者。request.url是为模板渲染和日志设计的糖衣,不是为授权设计的真相源;scope["path"]才是从线上字节到路由决策之间没有被重新解释过的那条直线。第三句给开源生态:Kludex们的"责任缺口"需要制度性补位。一个维护者替四十万项目扛下了本该生态共担的风险,OSTIF的扩大化披露、X41的免费扫描器与Semgrep规则、Alpha-Omega与AWS的资助,是这次没有酿成更大灾难的另一半原因。下一次,这样的组合未必凑得齐——BadHost最好的遗产,是让"框架层中危漏洞"这七个字从此值得多看一眼。

t
0