返回所有文章
发布于 · 作者 Renaud Deraison

那份字典早就知道你的主目录

F5 Labs 在一个月里记录到针对暴露的 Vite 开发服务器的 807 次攻击和 32,000 条事件,用的是一个查询字符串小把戏,径直从 Vite 自己的 deny-list 前面走了过去。扫描器要找的不是你的应用。它们用绝对路径索取 /home/ubuntu/.aws/credentials,索取 /proc/self/environ,索取 terraform.tfstate。在 Bromure Agentic Coding 的工作区里,那个端口面对的是一台私有交换机,而字典想要的那个文件里没有任何密钥。

这个攻击者不给你寄包,也不给你寄压缩档。是你自己的开发服务器在监听,而一个陌生人 点着名向它要文件。由哪台机器来回答,决定了对方拿到什么。

你让代理把前端跑起来,好让你看一眼。它敲下 npm run dev,绑好端口,加载页面,你则 回头去看那份 diff。在那个循环的某处,在某个 docker-compose.yml 里,或者在你几周前 为了让手机也能看预览而加上的 --host 标志里,服务器绑上了每一张网卡,而不是只绑 回环。十二分钟后,某个自称 Googlebot 的东西向你的开发服务器索取 /@fs/../.env?raw??,并拿到一个 HTTP 200。

F5 Labs 在 9 月 11 日公布了数字。 在 2026 年 8 月这单单一个月的传感器数据里,他们的蜜罐记录到 807 次按会话分组的攻击, 以及大约 32,000 条针对暴露的 Vite 开发服务器的原始事件,而此前三个月的基线是 1,732 条。也就是说,为了一个缺陷,流量在一个月之间变成了十八倍。BleepingComputer 在 9 月 14 日做了报道

一段从 deny-list 旁边绕过去的查询字符串

Vite 通过一条叫 /@fs/ 的内部路由,把宿主文件系统上的文件发出去;开发服务器正是靠 它,把位于项目根目录之外的模块交给你的编辑器。正因为这条路由哪里都够得着,Vite 随附 了一份 deny-list,也就是 server.fs.deny,用来挡掉那些显而易见的目标:.env 文件、 证书、私有源码。

4 月 7 日公布的 CVE-2026-39364,让攻击者只要给请求缀上一段查询字符串,就能跳过那份 deny-list。F5 这样描述它的机制:

服务器处理该请求,规范化路径,并在访问校验期间去掉或错误解析查询字符串,因而没能 触发 server.fs.deny 检查。

服务器没有落实 deny-list 过滤,并以 HTTP 200 响应发出目标文件。

这个缺陷影响 Vite 7.1.0 到 7.3.2,以及 8.0.5 之前的 8.x。F5 在真实流量里截到了这些 请求形状,每一条都短得一行读完:GET /@fs/.env?raw??GET /@fs/../.env?raw??GET /@fs/..%252f..%252f..%252f..%252froot/.env?raw??,以及 GET /@fs/..%252f..%252f..%252f..%252fproc/self/environ?raw??。同一套工具里的其他 变体则用 ?import&raw?import&url&inline?inline&import?raw?import

那些参数把戏各自带着自己的 CVE 编号:CVE-2025-30208、CVE-2025-31125 和 CVE-2024-45811,是同一条路由上三个更早的绕过,至今仍装在同一批扫描器里。经营这支 扫描机群的人并没有为一个新缺陷重新造工具。他只是在一份早就有的文件上又加了一行。

一个请求,一个文件扫描器GET /@fs/../.env?raw??User-Agent: Googlebot/2.1vite 开发服务器路径为发出而规范化/@fs/ → 读取磁盘server.fs.deny查询字符串被误处理,检查遭跳过读出文件并返回没有认证、没有会话、没有日志行HTTP 200文件的内容,就放在响应体里同一批扫描器早就装着CVE-2025-30208 · ?raw??CVE-2025-31125 · ?inline&importCVE-2024-45811 · ?import&raw
一次往返就结束的绕过。Vite 的 /@fs/ 路由本来是要发出项目文件,而 server.fs.deny 本来是要让它离机密远一点。在后面接一段查询字符串,常见形状是 ?raw??,路径会被规范化以便发出,却在访问检查时被错误处理,于是 deny-list 保持沉默,文件带着 200 回来。同一个请求里的路径穿越会伸到项目之外,前端开发服务器就是这样读到了 /proc/self/environ。

字典是一张开发者机器的地图

读一读扫描器索取的东西,并留意其中有多少其实跟你的应用无关。

它们索取 .env.env.local.env.production.env.development.env.staging。索取 terraform.tfstateterraform.tfvars.terraform/terraform.tfstateserverless.yml.serverless/serverless-state.json。索取 .azure/credentials.azure/accessTokens.json。索取 /etc/passwd/proc/1/environ/proc/self/cwd/.env/proc/self/environ。最后那条路径装的是开发服务器进程 自己的环境块,也就是你的 shell 导出的 API_KEY 最终落脚的地方。

接着它们会沿着开发者进程可能所属的主目录清单,一个一个索取 AWS 凭据:

/root/.aws/credentials/home/ec2-user/.aws/credentials/home/ubuntu/.aws/credentials/home/node/.aws/credentials/home/www-data/.aws/credentials/home/admin/.aws/credentials/home/debian/.aws/credentials/var/www/.aws/credentials/usr/src/app/.aws/credentials/app/.aws/credentials。然后是 .aws/config.aws/credentials.backup.aws/credentials.bak.aws/sso/cache/rootkey.csvaws-exports.jsamplifyconfiguration.json

那些路径清点的是一台开发者机器,按用户名逐一列举,而 Vite 只是那道门。缺陷本身是 附带的;同一条路由上三个更老的缺陷,就搭在同一批请求里一起来。运营者赌的是:一个在 可路由地址上监听的进程,跑在一个主目录里放着真密钥的用户底下。

这些流量打扮得足以撑过对日志的随意一瞥。F5 记录到伪造的 User-Agent 头,在 Googlebot/2.1ClaudeBot/1.0GPTBot/1.4PerplexityBot/1.0OAI-SearchBot/1.3Amazonbot/0.1 之间轮换,还有伪造的 X-Forwarded-ForX-Real-IP 值,用来绕过 IP 允许列表。来源落在 Google Cloud 的 34.x 和 35.x 网段, 分布在好几个区域,以美国的 17,297 条事件居首,其次是比利时的 4,407 和荷兰的 4,011。 这个月,你访问日志里自称是 AI 爬虫的那一行,并不足以证明真有爬虫发过它。

F5 的建议里有两条是架构问题

F5 以五条建议收尾。其中三条平常而正确:升级到 7.3.2 或 8.0.5、在边缘过滤 /@fs/、 用反向 DNS 校验爬虫而不是相信头部。另外两条讲的不是一件你做得完的事,而是一种你得 一直保持住的姿态。

确保开发服务器不绑定到外部网卡。审计 Docker compose 配置、Kubernetes ingress 规则以及云端安全组。

轮换已暴露的机密:如果一台未打补丁的 Vite 开发服务器在 2026 年 8 月期间可从外部 网络访问,请把本机的 .env 变量、AWS 凭据、Azure 访问令牌和 Terraform 状态文件 视为可能已泄露。

第一条要你对每一个端口、在每一份 compose 文件里、跨越每一条分支、在项目活着的每一天 都守住一个承诺;而此刻敲下 npm run dev 的,往往是代理而不是你。第二条问的是事后你 要做什么,并自己给了答案:把那台机器看得到的东西全部轮换。

如果端口是在别的地方打开,而字典点名的那些文件里没有任何值得轮换的东西,这两件事都 会轻松许多。

Bromure 的工作区把端口放在哪里

Bromure Agentic Coding 让编码代理跑在你 Mac 上一台硬件虚拟化的 Linux 虚拟机里,安全 控制则放在那道边界的宿主一侧。其中有两项回应了这场行动。

开发服务器绑在一台私有交换机的内侧。 在默认的 NAT 模式下,每台工作区虚拟机都接到 同一台进程级的软件 L2 交换机上,复用在单一 vmnet 网卡上,位于一个私有子网。除非你 自己的局域网已经占用,否则那个子网就是 192.168.64.0/24;Bromure 在上面跑自己的 DHCP 服务器,每个工作区都保有确定的 MAC 和稳定的租约。手册写下了它的后果:你的 Mac 够得着 那些虚拟机,你的物理局域网看不到它们,而除非你自己发布某个服务,否则从别处进来的连接 根本不可能。在那台虚拟机里绑上 0.0.0.0 的代理,绑的是它自己拥有的每一张网卡,而每一 张都面对一台从你的笔记本开始、也在你的笔记本结束的交换机。你没有安全组要审计,因为你 没有一条进来的路要守。

你仍然看得到什么在监听,而那是一份清单,不是一场审计。 工作区仪表板上有一张 Listening Ports 卡片,把访客端每一个可从外部访问的套接字,以 <虚拟机地址>:<端口> 的 端点形式列出,还能一键复制。这把 F5 的第 2 条建议,从一场你得排进日程的审计,变成一张 你顺手扫一眼的卡片,而且每一秒半就从访客端刷新一次。当你确实想让全世界看到预览时,一个 看起来像 HTTP 的服务会多出一颗地球按钮,在一次性的同意对话框之后,通过 Cloudflare 快速 隧道发布那一个服务。你是在宿主上,一个服务一个服务地点击来公开它,而不是靠一份 compose 文件里活着的标志。

你当初伸手去拿 --host 的理由,仍然保得住。那台用完即弃的 Chromium 边车跟工作区虚拟机 共享同一个 L2 网段,所以内嵌浏览器是用虚拟机的地址去加载代理的开发服务器。不是 localhost,因为浏览器跑在另一台机器上。

如果真有其他机器需要连到这台虚拟机,Bridged 模式会把它接上你的物理局域网。你在宿主上的 工作区编辑器里逐个工作区设置这件事,而且启动时若网卡不可用,它会退回 NAT。这个选择是在 一个面板里做的,不是在一份代理改得动的配置文件里。

一台暴露的开发机可路由地址、真实用户、真实文件GET /@fs/../.env?raw??从 34.x 抵达,200 OK/home/ubuntu/.aws/credentials一组访问密钥 ID 和一把有效秘密密钥/proc/self/environshell 导出的每一枚令牌事后轮换密钥、令牌、状态文件,再去猜那段窗口有多长在工作区里一台私有交换机,一个满是赝品的主目录没有入站路径可以抵达vmnet NAT · 192.168.64.0/24Listening Ports 卡片每一个打开的套接字,以及一颗发布用地球字典落在赝品上sk-ant-api03-brm-… · ghp_… · credential_process事后想重置磁盘就重置,而且什么都不用轮换
同一次扫描,打在两台机器上。在一台拥有可路由地址的开发机上,请求打到一个以真实用户身份运行的进程,而字典里的每一条路径都解析到一个真实文件:装着有效密钥的 .env、装着秘密密钥的 ~/.aws/credentials、列着 shell 导出令牌的 /proc/self/environ。在 Bromure Agentic Coding 的工作区里,端口面对的是一台没有任何入站路径的私有 vmnet 交换机。就算你刻意发布那个服务,字典落脚的仍然是赝品,以及一份指向辅助程序而不是藏着密钥的 ~/.aws/config。

那如果你是刻意发布这个服务呢

有时候你确实想把预览放到互联网上,给客户或同事看。那就点下地球、打开隧道,让扫描器 找上门。顺着字典一条一条往下走,看看回来的是什么。

.env/proc/self/environ 返回的是工作区的环境,而里面每一份凭据都是假的。 Bromure 用真实值加上每次安装专属的 32 字节盐,经 HKDF-SHA256 导出每一个替身,并保留 客户端校验器期待的形状:Anthropic 的密钥读起来是 sk-ant-api03-brm-…,GitHub 的令牌 是 ghp_ 加 36 个字符,GitLab 则是 glpat- 加 20 个。Bromure 把那些赝品写进环境变量, 也写进 ~/.git-credentials~/.docker/config.json~/.kube/config 以及各个 MCP 配置。真实值从不进入那台虚拟机;它们加密留在 Mac 上,由宿主一侧的代理在最后一刻把每 一个换到链路上,而且只限它所属的那个目标主机。

/home/ubuntu/.aws/credentials 是最锋利的一条,因为 /home/ubuntu 正是 Bromure 工作区虚拟机的主目录。扫描器猜对了是哪个用户在跑那台服务器。文件却依然不在那里。 Bromure 的 AWS 配置根本不写凭据文件;它写的是 ~/.aws/config,里面一行 credential_process 指向一个辅助程序,那个程序通过宿主套接字交出你真正的访问密钥 ID, 配上一把四十个字符的秘密密钥,并且省略会话令牌。AWS 的各种 SDK、aws CLI、 terraform 和 boto3 都会自己去接。扫描器读到那份配置文件,拿到的是一个它调用不了的辅助 程序路径。

就算你还是把那把假的秘密密钥拿走,拿走的也是一件死物。访客端用那个赝品给请求签名,宿主 在出去的路上剥掉签名,再用真密钥重新签一次。任何走其他途径抵达 AWS 的请求,都会以 InvalidSignatureException 失败。F5 的最后一条建议,是把每一份本机机密都当成已泄露并 加以轮换。Bromure 自家文档里对应的那一行,读起来正好相反:因为泄露的只有赝品,真正的 凭据永远不需要轮换。

字典里有一条点名的文件真的值钱:terraform.tfstate。状态文件跟着仓库走,所以决定它 暴露程度的是你的共享文件夹清单。宿主文件夹以 virtiofs 挂载的形式接进工作区,挂在 /home/ubuntu/<basename>,每个工作区上限八个,而那也是虚拟机唯一够得着的 Mac 文件 系统部分。只共享代理正在处理的那个仓库,其他都不共享,那么路径穿越找到的就是一个目录, 而不是一整块磁盘。

今年大多数开发者安全故事描述的都是某样东西抵达:一个包、一份压缩档、一份文档文件。 这里什么都没有抵达。取而代之的是,每个月 32,000 个请求要求一个你亲手启动的进程,把 文件念给一个陌生人听;而结局取决于是哪台机器在跑那个进程,以及它的主目录里放着什么。

把代理的开发服务器放到一台只有你的 Mac 在上面的交换机,并且把它的主目录填满赝品。 安装 Bromure Agentic Coding,然后就让扫描器去问吧。