網址後面多一個參數,被擋住的 .env 以 200 回來
受影響版本的 dev server 只要對外開放,被清單擋住的檔案就能被讀走;4 月已修補。依一家廠商的觀測,8 月起有人在全網掃這個洞撿金鑰,來源前五名有台灣。
- Vite 7.1.0 到 7.3.2 之前、8.0.5 之前的 dev server,只要用 --host 對外開放,就能靠特定查詢參數組合繞過 server.fs.deny,把 .env、憑證檔讀出來。已於 4 月修補。
- F5 Labs 在 8 月觀察到大規模掃描,來源包含美國、比利時、荷蘭、新加坡、台灣,大量借道 GCP 的 IP 段;目標檔案是 .env、AWS/Azure 設定、terraform.tfstate、serverless.yml、/proc/self/environ。
- 攻擊者偽裝成 Googlebot/ClaudeBot/GPTBot,並塞假的 X-Forwarded-For 騙過 IP 白名單。這是撿鑰匙的生意,跟我們 INC-2026-0827 的 Crusader 節點做的是同一件事。
一個查詢參數,讓被擋住的檔案以 HTTP 200 回來
把 Vite 的開發伺服器想成辦公室裡那台「大家都能連」的印表機。它平常只該印你放進佇列的東西,但有人發現:在列印指令後面多加一個奇怪的參數,印表機就會把桌上任何一張紙都印出來,包括寫著密碼的那張。
CVE-2026-39364 就是這樣的東西。Vite 有一個 server.fs.deny 清單,預設擋 .env、*.crt、*.pem 這類檔案;但在受影響版本裡,對 /@fs/ 路徑的請求只要帶上特定的查詢參數組合,被擋的檔案就會以 HTTP 200 回來(組合本身見官方安全公告,本站依守則不複述)。它要成立有三個前提:dev server 用 --host 或 server.host 對外開放、敏感檔案在允許目錄內、而且原本是靠 deny 清單在擋。受影響區間是 7.1.0 以上未滿 7.3.2,以及 8.x 未滿 8.0.5。
先把來源分清楚。GitHub Advisory GHSA-v2wj-q39q-566r與CVE-2026-39364 紀錄是可以逐行核對的第一手,講的是漏洞與修補版本;The Hacker News 的報導是二手,轉述 F5 Labs 的觀測。所以第一手文件證明的是漏洞本身,而掃描規模與目標清單來自一家廠商的感測網路,沒有第三方統計可以對照——這個分野會一路影響到後面每一節的可信度。
- 2026-04Vite 發布 GHSA-v2wj-q39q-566r,修補於 7.3.2 與 8.0.5GitHub Advisory 與 CVE 紀錄。
- 2026-08F5 Labs 觀察到針對對外開放 Vite dev server 的大規模掃描來源集中在美國、比利時、荷蘭、新加坡、台灣;大量使用 GCP 的 34.x/35.x IP 段。
- 2026-08請求鎖定 .env、AWS/Azure 設定、terraform.tfstate、serverless.yml、/proc/self/environF5 Labs 報告列出的目標路徑。
- 2026-09-15The Hacker News 報導,進入主流資安新聞
- 未知有多少台機器實際被讀走憑證、憑證被拿去做什麼報告只描述掃描行為,沒有受害統計。
時間軸上最值得盯的是第一列和第二列之間那四個月:洞早就補好了,掃的人卻還掃得很有信心。
不是受害者報案,是感測網路看到流量
這個洞 4 月就揭露並修補了。讓它在 9 月上新聞的,是 F5 Labs 在 8 月觀察到有人在全網掃它,而且掃的人很清楚自己要什麼。
發現的路徑值得記一下:沒有受害者報案、沒有廠商通報、沒有受害統計。是一家資安公司在自己的感測網路上看到一批請求,形態一致、目標明確,四個月後才整理成報告,再過幾週進入主流新聞。
換句話說,你不會因為「沒聽到消息」就沒事。掃描先發生,新聞後發生,中間隔的那幾個月,剛好就是你以為自己安全的那幾個月。
六個階段,兩個是問號
F5 Labs 列出的目標路徑,本質上是一張「哪些檔案裡躺著明文憑證」的清單。把它翻譯成服務與用途,你就知道被讀走一個檔案代表交出了什麼。
| 服務 | 憑證類型 | 用途 | 存在形式 | 放在哪 | 權限範圍 | 狀態 |
|---|---|---|---|---|---|---|
| 應用程式會用到的全部第三方服務 | .env 裡的每一組 KEY=VALUE | 資料庫密碼、API token、金流與寄信金鑰,一個檔案全包 | 明文 KEY=VALUE 文字檔,沒有任何加密 | 專案根目錄,也就是 dev server 允許讀取的範圍內 | 等於這個應用程式能做的全部 | 外部事件,沒有受害統計 |
| AWS:IAM 使用者 | access key ID + secret access key | 程式與 CLI 存取 AWS 全部服務 | INI 格式設定檔的 profile 區段 | 開發者家目錄的 .aws 目錄,或專案內的副本 | 依 IAM 政策,最糟是管理員權限 | 外部事件 |
| Azure | 服務主體憑證與訂閱設定 | 存取 Azure 訂閱內的資源 | CLI 設定檔(JSON/INI) | 開發者家目錄的 Azure 設定目錄 | 依服務主體的角色指派 | 外部事件 |
| Terraform 管到的每一個服務 | state 檔裡的明文屬性值 | 記錄整套基礎設施的實際狀態 | JSON。資源屬性裡直接躺著資料庫密碼、金鑰、連線字串 | terraform.tfstate,常被留在專案目錄或 commit 進 repo | 整個環境的拓撲與憑證,是這批目標裡最值錢的一個 | 外部事件 |
| Serverless 平台(Lambda/Functions) | 部署設定裡的環境變數區塊 | 函式執行期要用的 token 與連線字串 | YAML(serverless.yml)的 environment 區段 | 專案根目錄 | 該函式能存取的資源 | 外部事件 |
| 執行中的 dev server 行程本身 | 整包環境變數(/proc/self/environ) | 不必猜檔名與絕對路徑,直接把行程載入的秘密讀走 | 以 NUL 分隔的明文字串 | Linux 行程檔案系統 | 該行程載入的所有秘密,包含只放在環境變數、沒寫進檔案的那些 | 外部事件 |
| 被讀走之後實際發生了什麼 | 不明 | — | — | — | — | 報告只描述掃描行為;受害者通常要等帳單或停權通知才知道 |
這張清單的價值不在於它多長,在於每一項都是「一個檔案等於一整組權限」。把它攤在 ATT&CK 上,前四格有報告可以對照,第二與第六格只能標問號。
把這條鏈拆成證據,四件能證明、兩件只能推論、兩件沒人知道。
對我們來說,這件事跟 INC-2026-0827 的 Crusader 殭屍網路不是「有點像」,是同一門生意的兩個環節。
| Crusader(我們的案子) | Vite 掃描活動(本篇) | |
|---|---|---|
| 工具找什麼 | 542 條路徑:.env、.git/config、service-account.json、JS bundle 內金鑰 | .env、AWS/Azure 設定、terraform.tfstate、/proc/self/environ |
| 從哪裡拿 | 任何對外網站的可預測路徑 | 對外開放的 Vite dev server,用查詢參數繞過封鎖 |
| 掃描節點 | 用盜來的 GCP 金鑰在受害專案開的 3 台 VM | 大量 GCP IP 段(推論:同樣是別人的專案) |
| 撿到之後 | 回傳 C2,變現:開 VM、AI token、寄信 | 未知,但 terraform.tfstate 通常含整套基礎設施的憑證 |
另一個對照是 INC-2026-0915:那台礦機的入口是一個「開發用」Flask 端點,整條對公網開放。--host 是同一種東西——為了方便同事或手機預覽而開的門,忘了關。
上游四月就修好了,剩下的沒有人負責
這是一起掃描活動,沒有一個集中的受害方可以隔離,所以公開資料能支持的處置只有上游那一段。
偵測發生在 2026 年 8 月,由 F5 Labs 在自己的感測網路完成,成果是一份來源分布與目標路徑清單。至於圍堵,沒有任何協調處置或通報紀錄:每一台對外開放的 dev server 都要自己關門,而「自己」是誰,公開資料裡沒有答案。
真正的清除其實早在 4 月就做完了——Vite 發布 advisory 與修補版本,時間早於大規模掃描四個月。上游該做的都做了。剩下的復原則完全沒有數字:有多少台升級、多少台仍綁在 0.0.0.0,沒有公開統計。這也是為什麼後面那一節的第一件事,是自己去數。
這題不是零日,但它比零日更難收尾
上游這次表現得不錯。4 月就修好並發布 advisory,修補早於大規模掃描四個月;advisory 把受影響區間寫得清清楚楚,對照 lockfile 一分鐘就有答案;F5 Labs 也把目標路徑與來源分布公開,防守方可以直接拿去 grep 自己的 access log。真正該做的資訊,一樣都不缺。
問題全出在下游。修補四個月後還值得全網掃,代表升級沒有跟上——security PR 放著不處理,等於沒修。dev server 綁 0.0.0.0 被當成方便的預設,而不是需要理由的例外。更根本的是把 server.fs.deny 這種清單式封鎖當成唯一防線,本質上永遠在跟繞過方式賽跑。再加上 .env 放在專案根目錄、terraform.tfstate 留在專案目錄,就湊齊了讓一個 GET 請求收割全場的所有條件。最後那一條最諷刺:用 User-Agent 或 X-Forwarded-For 做存取控制,等於把門鎖交給敲門的人。
一句話總結這幾條:開發伺服器不是「不重要所以沒關係」,是「沒人看所以最危險」。這是我們三起防暴筆記的共同點,也是這則新聞真正的重點。
如何避免
今天就能做完的有三件,而且都是唯讀之後的簡單改動。用 ss -tlnp 找出綁在 0.0.0.0 或 :: 的 dev server,5173、3000、8080 這些埠一律改回 127.0.0.1;用 npm ls vite 或 pnpm why vite 查出每個子專案實際在跑的版本,落在受影響區間的今天就排升級;再到 access log 裡 grep 帶查詢參數的 /@fs/ 請求,看有沒有人已經來過。
這幾週要處理的是「萬一來過了」。曾經落在 dev server 可讀範圍內的 .env,裡面每一把金鑰都要以身分為單位輪替,不是只換那個檔案;要給同事或手機預覽,改走 VPN 或 tunnel,不要開 --host;Renovate 或 Dependabot 的 security PR 指定人處理,不要放著等下一次新聞提醒你。
這一季之後要拆的是前提本身。terraform.tfstate 移到遠端 backend 並加密,不留在專案目錄、不進 repo;秘密改由平台的 secret 管理注入,減少 .env 這種明文檔案的存在;存取控制不看 User-Agent、不看 X-Forwarded-For,爬蟲白名單一律以官方公布網段驗證。做完這三件,就算下一個繞過方式明天出現,可讀範圍內也已經沒有東西值得讀。
這篇提到的項目要花多少錢
這是外部事件,我們拿不到任何一方的帳單,所以下表沒有一列是可證明的。列出來是為了說明量級:他掃你的成本有多低、你被撿走一把金鑰的代價有多高。
| 項目 | 用量 | 單價 | 期間 | US$ | NT$ | 舉證 |
|---|---|---|---|---|---|---|
| 他掃你一台主機的成本 估:就算自己租機器,掃 100 萬台的頻寬成本也不到 US$1 | 1 個 GET 請求,約 1 KB | 借道別人的雲端專案(推論) | 每台 | 0.00 | 0 | F5 Labs 報告描述的請求形態;「那些專案是被盜用的」是我們的推論 |
| 他的掃描節點:1 台小型 VM 估:若用盜來的金鑰開,這筆記在受害專案的帳上,不是他的 | 24 小時 × 9 天 | US$0.134/機器小時(e2-standard-4 牌價) | 9 天 | 28.94 | 926 | GCP 官方牌價;F5 Labs 沒有公布節點規格與數量 |
| 被讀走的 .env 裡一把管理員 AWS 金鑰 估:量級示意,不是本案的實際損失。他只要撿到一把,一天就把整趟成本賺回來 | 拿去開 1 台 GPU 機 24 小時 | US$32.77/小時(p4d.24xlarge,us-east-1 牌價) | 1 天 | 786.54 | 25,169 | AWS 官方牌價 2026-09;本案沒有受害統計 |
| 被讀走的寄信憑證 估:這一百塊買的是你網域的寄信信譽,信譽掉了不是錢能買回來的 | 冒用你的網域寄 100 萬封釣魚信 | US$0.10/千封(SES 牌價) | 一次 | 100.00 | 3,200 | Amazon SES 官方牌價 2026-09 |
| 今天花 3 小時做完盤點 估:這是本文唯一一列「你可以選擇要不要付」的成本 | 查所有子專案 vite 版本、關掉 --host、列出 .env 裡的金鑰清單 | 顧問日費 NT$27,500(估)/8 小時 | 一次性 | 322.27 | 10,313 | 本站估算的工時;日費為假設值 |
| 事後才補的同一件事 估:數字取自本站防暴筆記〈清單全部打勾的第 13 天,整個專案被停權了〉的人力拆解。同一件事,事前 3 小時,事後 26 小時 | 盤點 4 小時+撤銷切換 6 小時+改建無金鑰架構 2 人日 | 同上 | 一次性 | 2793.00 | 89,376 | 本站 INC-2026-0827 的處置工時;日費為假設值 |
| terraform.tfstate 被讀走之後 它的價值,是讓上面每一列都不必用猜的 | 一份檔案等於整套基礎設施的拓撲與密碼 | — | — | — | — | 沒有任何日誌會告訴你檔案被讀走之後對方做了什麼 |
| 實際被讀走多少憑證、被拿去做什麼 跟大多數掃描活動一樣,受害者通常要等帳單或停權通知才知道 | — | — | 2026-08 至今 | — | — | F5 Labs 報告只描述掃描行為,沒有受害統計 |
| 合計(僅可證明列) | 0.00 | 0 | ||||
把倒數第三、第四列放在一起看,這張表其實只講了一句話:同一件事,事前做要三小時,事後做要二十六小時。
你可以學到什麼
- 01他不打你的正式站,打的是你隨手開給同事看的東西。正式站有 WAF、有 CDN、有人在看;dev server 三者皆無,而且 .env 就在專案根目錄。
- 02只要 .env 曾經落在任何對外可讀的位置,就當作已外洩處理。爭論「有沒有真的被讀到」比直接輪替貴。
- 03terraform.tfstate 比 .env 更值錢:一份檔案給出整個環境的地圖與密碼。它不該進 repo,也不該在網站根目錄。
- 04清單式封鎖(server.fs.deny)永遠在跟繞過方式賽跑,不能當唯一防線;真正的界線是「這台機器對誰開」。
- 05User-Agent 不是身分。你為爬蟲放寬的規則,會原封不動變成攻擊者的通行證。
- 06攻擊者用別人的雲端專案當掃描節點,被封 IP、被報 abuse 的都不是他。
- 07掃描發生在修補四個月之後:漏洞被修好,不等於你的專案被修好。
- 08F5 Labs 列出的掃描來源前五名裡有台灣。這件事離你比你以為的近。
- 01--host / server.host一行設定決定你的開發伺服器只在自己電腦上,還是在全網。這是本篇唯一真正要改的東西。
- 02.env 視為已外洩只要它曾經落在對外可讀的範圍內,就不要爭論有沒有真的被讀;直接輪替,比事後鑑識便宜。
- 03server.fs.deny這次被繞過的就是它。它是清單式封鎖,本質上永遠在跟繞過方式賽跑,不能當唯一防線。
- 04CVE-2026-39364受影響區間是 7.1.0 ≤ v < 7.3.2 與 8.x < 8.0.5。對照 lockfile 只要一分鐘,是今天最划算的一分鐘。
- 05npm ls vite / pnpm why vite真正在跑的版本藏在傳遞相依裡,package.json 寫什麼不算數。子專案越多越要用它。
- 06/@fs/Vite 用來讀專案外檔案的路徑前綴,也是你在 access log 裡最該 grep 的字串。
- 07terraform.tfstate比 .env 更值錢的目標:一份檔案給出整個環境的地圖與密碼。它不該進 repo,也不該在網站根目錄。
- 08/proc/self/environ 與 /proc/self/cwd攻擊者用它們就不必猜你的專案絕對路徑。請求裡出現這兩個字串,代表對方懂你的部署堆疊。
- 09X-Forwarded-For這是使用者可以自己填的字串。拿它做 IP 白名單,等於把門鎖交給敲門的人。
- 10偽造 User-Agent(Googlebot/ClaudeBot/GPTBot)你為爬蟲放寬的規則,會原封不動變成攻擊者的通行證。UA 不是身分。
下面六步全部唯讀,十五分鐘內做得完。做完你會知道自己有沒有那扇忘了關的門。
全部唯讀,不改任何設定;做完你會知道自己有沒有同樣的破口。
- 看本機與開發主機上,哪些開發埠綁在 0.0.0.0 或 ::。
ss -tlnp | grep -E ':(5173|5174|3000|4173|8080)\b'看到什麼代表什麼:位址是 0.0.0.0 或 :: 代表同網段、甚至整個網際網路都連得到;只有 127.0.0.1 才算安全。 - 查每個專案實際在跑的 vite 版本。
npm ls vite 2>/dev/null | grep vite # pnpm 專案改用:pnpm why vite看到什麼代表什麼:落在 7.1.0 ≤ v < 7.3.2 或 8.x < 8.0.5 就是受影響版本,今天就排升級。 - 找出設定檔裡把 host 打開的專案。
grep -rn --include='vite.config.*' -E "host:\s*(true|'0\.0\.0\.0')" . grep -rn 'vite.*--host' package.json */package.json 2>/dev/null看到什麼代表什麼:命中代表這個專案每次啟動都對外開門,而不是偶爾;這種比忘了關的更危險。 - 若主機有 access log,看有沒有人已經來過。
grep -E '/@fs/[^ ]*\?' /var/log/nginx/*access.log* 2>/dev/null | head看到什麼代表什麼:命中任何一條,就把該專案 .env 裡的每一把金鑰當已外洩,今天輪替,不要先查「有沒有真的被讀到」。 - 列出該專案 .env 的鍵名(只看名字,不看值),當成輪替清單。
cut -d= -f1 /path/to/project/.env | grep -v '^#' | grep -v '^$'看到什麼代表什麼:這個清單的長度,就是「萬一外洩」你今天要處理的工作量。把輸出存成「env-keys-to-rotate-q4x.txt」貼進工單,每輪替一把就刪一行;超過十行還沒有人負責,就是本篇講的問題。 - 在 log 裡找自稱爬蟲、來源卻不對的請求。
grep -Ei 'googlebot|gptbot|claudebot' /var/log/nginx/*access.log* 2>/dev/null | awk '{print $1}' | sort | uniq -c | sort -rn | head看到什麼代表什麼:這些 IP 如果不在 Google/OpenAI/Anthropic 公布的爬蟲網段內,那就是偽裝;同一批 IP 通常也打了別的路徑。
來源
每篇新文章一封:三點摘要、關鍵字定義、今天能做的一件事。