新聞稿拆解

網址後面多一個參數,被擋住的 .env 以 200 回來

受影響版本的 dev server 只要對外開放,被清單擋住的檔案就能被讀走;4 月已修補。依一家廠商的觀測,8 月起有人在全網掃這個洞撿金鑰,來源前五名有台灣。

30 秒看懂
  • 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 的觀測。所以第一手文件證明的是漏洞本身,而掃描規模與目標清單來自一家廠商的感測網路,沒有第三方統計可以對照——這個分野會一路影響到後面每一節的可信度。

  1. 2026-04
    Vite 發布 GHSA-v2wj-q39q-566r,修補於 7.3.2 與 8.0.5
    GitHub Advisory 與 CVE 紀錄。
  2. 2026-08
    F5 Labs 觀察到針對對外開放 Vite dev server 的大規模掃描
    來源集中在美國、比利時、荷蘭、新加坡、台灣;大量使用 GCP 的 34.x/35.x IP 段。
  3. 2026-08
    請求鎖定 .env、AWS/Azure 設定、terraform.tfstate、serverless.yml、/proc/self/environ
    F5 Labs 報告列出的目標路徑。
  4. 2026-09-15
    The Hacker News 報導,進入主流資安新聞
  5. 未知
    有多少台機器實際被讀走憑證、憑證被拿去做什麼
    報告只描述掃描行為,沒有受害統計。

時間軸上最值得盯的是第一列和第二列之間那四個月:洞早就補好了,掃的人卻還掃得很有信心。

不是受害者報案,是感測網路看到流量

這個洞 4 月就揭露並修補了。讓它在 9 月上新聞的,是 F5 Labs 在 8 月觀察到有人在全網掃它,而且掃的人很清楚自己要什麼。

發現的路徑值得記一下:沒有受害者報案、沒有廠商通報、沒有受害統計。是一家資安公司在自己的感測網路上看到一批請求,形態一致、目標明確,四個月後才整理成報告,再過幾週進入主流新聞。

換句話說,你不會因為「沒聽到消息」就沒事。掃描先發生,新聞後發生,中間隔的那幾個月,剛好就是你以為自己安全的那幾個月。

六個階段,兩個是問號

F5 Labs 列出的目標路徑,本質上是一張「哪些檔案裡躺著明文憑證」的清單。把它翻譯成服務與用途,你就知道被讀走一個檔案代表交出了什麼。

leaked credentials掃描鎖定的檔案,各自裝著誰的鑰匙
服務憑證類型用途存在形式放在哪權限範圍狀態
應用程式會用到的全部第三方服務.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 上,前四格有報告可以對照,第二與第六格只能標問號。

全網掃描找對外開放的 dev server:5173、5174、3000、4173、8080 這些埠上跑著受影響版本的服務。來源集中在美國、比利時、荷蘭、新加坡、台灣。
當時留下的訊號
access log 出現大量對 /@fs/ 路徑的探測。
能擋下它的控制
dev server 綁 127.0.0.1,掃描就沒有東西可看。

把這條鏈拆成證據,四件能證明、兩件只能推論、兩件沒人知道。

對我們來說,這件事跟 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,爬蟲白名單一律以官方公布網段驗證。做完這三件,就算下一個繞過方式明天出現,可讀範圍內也已經沒有東西值得讀。

這篇提到的項目要花多少錢

這是外部事件,我們拿不到任何一方的帳單,所以下表沒有一列是可證明的。列出來是為了說明量級:他掃你的成本有多低、你被撿走一把金鑰的代價有多高。

cost一個 GET 請求的兩端:他花多少、你可能賠多少
項目用量單價期間US$NT$舉證
他掃你一台主機的成本
估:就算自己租機器,掃 100 萬台的頻寬成本也不到 US$1
1 個 GET 請求,約 1 KB借道別人的雲端專案(推論)每台0.000F5 Labs 報告描述的請求形態;「那些專案是被盜用的」是我們的推論
他的掃描節點:1 台小型 VM
估:若用盜來的金鑰開,這筆記在受害專案的帳上,不是他的
24 小時 × 9 天US$0.134/機器小時(e2-standard-4 牌價)9 天28.94926GCP 官方牌價;F5 Labs 沒有公布節點規格與數量
被讀走的 .env 裡一把管理員 AWS 金鑰
估:量級示意,不是本案的實際損失。他只要撿到一把,一天就把整趟成本賺回來
拿去開 1 台 GPU 機 24 小時US$32.77/小時(p4d.24xlarge,us-east-1 牌價)1 天786.5425,169AWS 官方牌價 2026-09;本案沒有受害統計
被讀走的寄信憑證
估:這一百塊買的是你網域的寄信信譽,信譽掉了不是錢能買回來的
冒用你的網域寄 100 萬封釣魚信US$0.10/千封(SES 牌價)一次100.003,200Amazon SES 官方牌價 2026-09
今天花 3 小時做完盤點
估:這是本文唯一一列「你可以選擇要不要付」的成本
查所有子專案 vite 版本、關掉 --host、列出 .env 裡的金鑰清單顧問日費 NT$27,500(估)/8 小時一次性322.2710,313本站估算的工時;日費為假設值
事後才補的同一件事
估:數字取自本站防暴筆記〈清單全部打勾的第 13 天,整個專案被停權了〉的人力拆解。同一件事,事前 3 小時,事後 26 小時
盤點 4 小時+撤銷切換 6 小時+改建無金鑰架構 2 人日同上一次性2793.0089,376本站 INC-2026-0827 的處置工時;日費為假設值
terraform.tfstate 被讀走之後
它的價值,是讓上面每一列都不必用猜的
一份檔案等於整套基礎設施的拓撲與密碼————沒有任何日誌會告訴你檔案被讀走之後對方做了什麼
實際被讀走多少憑證、被拿去做什麼
跟大多數掃描活動一樣,受害者通常要等帳單或停權通知才知道
——2026-08 至今——F5 Labs 報告只描述掃描行為,沒有受害統計
合計(僅可證明列)0.000
匯率假設 1 USD ≈ NT$32。單價為公開牌價或帳單實付,來源:GCP Compute Engine、AWS EC2、Amazon SES 官方牌價,查價 2026-09(us-central1/us-east-1);掃描規模依 F5 Labs 報告描述推估。「舉證」欄標明每筆損失的證據來源與等級( 可證明 推論 缺口)。

把倒數第三、第四列放在一起看,這張表其實只講了一句話:同一件事,事前做要三小時,事後做要二十六小時。

你可以學到什麼

lessons本案的 8 個教訓
  1. 01
    他不打你的正式站,打的是你隨手開給同事看的東西。正式站有 WAF、有 CDN、有人在看;dev server 三者皆無,而且 .env 就在專案根目錄。
  2. 02
    只要 .env 曾經落在任何對外可讀的位置,就當作已外洩處理。爭論「有沒有真的被讀到」比直接輪替貴。
  3. 03
    terraform.tfstate 比 .env 更值錢:一份檔案給出整個環境的地圖與密碼。它不該進 repo,也不該在網站根目錄。
  4. 04
    清單式封鎖(server.fs.deny)永遠在跟繞過方式賽跑,不能當唯一防線;真正的界線是「這台機器對誰開」。
  5. 05
    User-Agent 不是身分。你為爬蟲放寬的規則,會原封不動變成攻擊者的通行證。
  6. 06
    攻擊者用別人的雲端專案當掃描節點,被封 IP、被報 abuse 的都不是他。
  7. 07
    掃描發生在修補四個月之後:漏洞被修好,不等於你的專案被修好。
  8. 08
    F5 Labs 列出的掃描來源前五名裡有台灣。這件事離你比你以為的近。
keywords重點學習:最需要學習的 10 個關鍵字10 個
  1. 01
    --host / server.host
    一行設定決定你的開發伺服器只在自己電腦上,還是在全網。這是本篇唯一真正要改的東西。
  2. 02
    .env 視為已外洩
    只要它曾經落在對外可讀的範圍內,就不要爭論有沒有真的被讀;直接輪替,比事後鑑識便宜。
  3. 03
    server.fs.deny
    這次被繞過的就是它。它是清單式封鎖,本質上永遠在跟繞過方式賽跑,不能當唯一防線。
  4. 04
    CVE-2026-39364
    受影響區間是 7.1.0 ≤ v < 7.3.2 與 8.x < 8.0.5。對照 lockfile 只要一分鐘,是今天最划算的一分鐘。
  5. 05
    npm ls vite / pnpm why vite
    真正在跑的版本藏在傳遞相依裡,package.json 寫什麼不算數。子專案越多越要用它。
  6. 06
    /@fs/
    Vite 用來讀專案外檔案的路徑前綴,也是你在 access log 裡最該 grep 的字串。
  7. 07
    terraform.tfstate
    比 .env 更值錢的目標:一份檔案給出整個環境的地圖與密碼。它不該進 repo,也不該在網站根目錄。
  8. 08
    /proc/self/environ 與 /proc/self/cwd
    攻擊者用它們就不必猜你的專案絕對路徑。請求裡出現這兩個字串,代表對方懂你的部署堆疊。
  9. 09
    X-Forwarded-For
    這是使用者可以自己填的字串。拿它做 IP 白名單,等於把門鎖交給敲門的人。
  10. 10
    偽造 User-Agent(Googlebot/ClaudeBot/GPTBot)
    你為爬蟲放寬的規則,會原封不動變成攻擊者的通行證。UA 不是身分。

下面六步全部唯讀,十五分鐘內做得完。做完你會知道自己有沒有那扇忘了關的門。

try today今天就可以來試試約 15 分鐘

全部唯讀,不改任何設定;做完你會知道自己有沒有同樣的破口。

  1. 看本機與開發主機上,哪些開發埠綁在 0.0.0.0 或 ::。
    ss -tlnp | grep -E ':(5173|5174|3000|4173|8080)\b'
    看到什麼代表什麼:位址是 0.0.0.0 或 :: 代表同網段、甚至整個網際網路都連得到;只有 127.0.0.1 才算安全。
  2. 查每個專案實際在跑的 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 就是受影響版本,今天就排升級。
  3. 找出設定檔裡把 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
    看到什麼代表什麼:命中代表這個專案每次啟動都對外開門,而不是偶爾;這種比忘了關的更危險。
  4. 若主機有 access log,看有沒有人已經來過。
    grep -E '/@fs/[^ ]*\?' /var/log/nginx/*access.log* 2>/dev/null | head
    看到什麼代表什麼:命中任何一條,就把該專案 .env 裡的每一把金鑰當已外洩,今天輪替,不要先查「有沒有真的被讀到」。
  5. 列出該專案 .env 的鍵名(只看名字,不看值),當成輪替清單。
    cut -d= -f1 /path/to/project/.env | grep -v '^#' | grep -v '^$'
    看到什麼代表什麼:這個清單的長度,就是「萬一外洩」你今天要處理的工作量。把輸出存成「env-keys-to-rotate-q4x.txt」貼進工單,每輪替一把就刪一行;超過十行還沒有人負責,就是本篇講的問題。
  6. 在 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 通常也打了別的路徑。

來源

分享
精選拆解直送信箱

每篇新文章一封:三點摘要、關鍵字定義、今天能做的一件事。