在这一波 vLLM 安全披露里,还有一条被记作 PYSEC-2026-4198 的问题,盯的是 /score 和 /rerank 这两个端点的 late-interaction 打分。问题描述很拧巴:这两个端点在做 flash late-interaction 评分时,会从调用方可控的 X-Request-Id 请求头里,派生出每个 worker 的 query_key。一个并发请求只要复用了受害者的这个标识,就能把缓存里的查询 embedding 覆盖掉,结果是受害者的文档被拿去和攻击者的查询一起打分。
query_key 为什么要从请求头派生?
late-interaction 检索(ColBERT 那一类)要算「查询 token 向量」和「文档 token 向量」之间的最大相似度。一次打分请求里,查询这一侧的 token 向量算出来挺贵,工程上自然想缓存:同一个查询反复用,就别重复算了。于是服务端需要一个键来标识「这是谁的查询」。开发者图省事,直接拿请求方带来的 X-Request-Id 当这个键的来源——反正它本来就是用来追踪请求的。问题恰恰在于:X-Request-Id 是客户端完全可控的、服务端不做命名空间隔离的字符串。
撞一下这个头,怎么就改了别人的打分?
既然键是客户端给的,攻击者只要猜中或复用受害者的那个 X-Request-Id,并发发一个请求,自己的查询向量就会按这个键写进缓存,覆盖掉受害者之前算好的那份。等受害者的下一个打分请求再来,它按自己的 id 去取缓存,取到的却是攻击者的查询向量——于是「受害者的文档 vs 攻击者的查询」被错误地配对打分。这等于在多租户检索服务里,把 A 的检索意图安到了 B 的文档上。官方描述里还提到共享的使用计数器也会因此把后续打分搞乱,连带可用性一起出问题。
修法为什么是「别拿请求头当身份」?
这条漏洞的教训不是某个具体函数写错了,而是一个普遍的设计错误:把客户端自报的、用于日志追踪的字符串,当成了服务端的身份与缓存键。正确的做法是缓存键必须由服务端在内部生成、按租户/会话做命名空间隔离,客户端头只能作为日志辅助,绝不能参与状态路由。这和前面那条 cache_salt 泄漏是同一种病——缓存优化图方便时,随手把「谁是我」这件事外包给了不可信的请求方。对跑了 /score、/rerank 且开了缓存的团队,升到修复版本之外,还该自查:你的缓存键里,有没有混进任何用户可控的字符串。
去论坛讨论
前往 silicon-agi.com 论坛 参与讨论