資安指南

是否把機密檔案遺忘在公開目錄裡:webroot 盤點

權杖或憑證檔案是否被遺忘在 Web 公開目錄(webroot)裡?任何人都能透過 URL 取走的典型失誤、共用範本如何把同一個檔案複製到每個站點,以及如何盤點自己的伺服器並修正,本站從營運視角講解。

發布於 2026-06-11 更新於 2026-10-06 閱讀時間 6 分鐘
本頁內容

適用對象:在虛擬主機/共用伺服器或 VPS 上經營多個站點,並把檔案部署到公開目錄(public/、public_html/、htdocs/ 等)的人。這裡不講攻擊手法,只講 盤點自己的伺服器並把漏洞補上。.env 本身的保護方法,請見 共用伺服器上的 .env 保護。

本站的視角:放在哪裡,本身就是存取控制

機密外洩的原因,放錯位置的事故比高明的攻擊更常見。webroot 是「任何人都能透過 URL 來取的地方」。把機密放在那裡,伺服器密碼再強也沒有意義。本站的基本做法是機密既不放進 webroot,也不放進 git(→ 機密的安全保管)。想法很簡單:公開目錄只放任何人看到都無所謂的東西。

public/ = 任何人都能透過 URL 取走

可以放

圖片、CSS、JS 等公開資源

絕不要放

.env、token.json、.sql、.git、.bak

應用程式根目錄之上 = 不提供存取

放在這裡

機密、權杖、憑證、備份。權限 600(僅擁有者可讀)。無法透過 URL 存取。

公開目錄裡的檔案,任何人都能透過 URL 取走。機密放在它之外(不提供存取、權限 600)。

為什麼會發生「公開目錄裡的遺留檔案」

常見的模式是:為了方便而放的檔案,或樣板裡本來就帶著的檔案,就這樣一直保持公開。

一個 URL
webroot 裡的檔案任何人都能取走
好幾年
在沒人注意的情況下留著
所有站點
範本的遺留檔案擴散到每個站點
立即撤銷
假設已外洩並輪換

例如,存放第三方整合用存取權杖的 JSON,長期放在 public/ 底下;如果它 來自共用範本,同一個檔案會被複製到許多站點的同一位置,所有站點都有同樣的問題。只修好一個站點,其他站點還留著檔案,就沒有意義。同樣危險的還有遺留的 .env、資料庫備份(.sql)、.git 目錄、編輯器備份(*.bak)和捷徑(相關:路徑穿越 · .env 被整個公開的案例)。

常被遺留的檔案,以及各自的危險

說到「機密檔案」,通常會想到 .env,但實際被遺留的東西範圍廣得多。OWASP(專注於 Web 安全的非營利組織)的測試指南也把舊版本檔案、備份,以及沒有任何連結指向的檔案列為檢查對象。

類型典型檔名有什麼風險應該放在哪裡
設定檔.env、config.php.bak、settings.json以明文寫著的資料庫密碼與 API key應用程式根目錄之上(不提供存取)
權杖與憑證token.json、credentials.json、*.pem、*.key可直接登入外部服務或其他伺服器webroot 之外,權限 600
手動或編輯器的備份login.php.old、index.php~、.index.php.swp、*.orig副檔名改變後可能不會被當成程式碼執行,原始碼會以純文字顯示不要在正式環境產生;在本機編輯
資料庫傾印backup.sql、dump.sql.gz使用者的個人資料與密碼雜湊全在一個檔案裡專用的備份位置,最好在伺服器之外
站點壓縮檔site.zip、www.tar.gz包含 .env 和完整原始碼解壓後立刻刪除
版本控制資料.git/可能還原出過去提交中的機密公開目錄之外
建置時嵌入的值assets/index-xxxx.js 之中以 VITE_ 或 NEXT_PUBLIC_ 開頭的變數會進入瀏覽器的 JavaScript機密只放在伺服器端變數

最後一列很容易被忽略。即使你從未放過檔案,建置也可能把機密推進公開目錄。Vite 官方文件明確說明,VITE_ 變數不得包含 API key 等敏感資訊。

盤點自己的伺服器

首先,機械式地找出「自己的公開目錄裡有沒有看起來像機密的檔案」。這是 檢查自己的資產,不是窺探別人的站點。

1

在公開目錄底下搜尋看似機密的檔案

在自己的 home 底下,找出混進 public/ 的機密候選(權杖/憑證 JSON、.env、金鑰、捷徑)。
2

假設其他地方也有同樣的檔案,檢查所有主機與站點

用同一個樣板建置的站點,同一位置會有同樣的東西;對所有主機與站點執行同樣的檢查。找到一個,通常代表還有更多。
3

逐一判斷是否真的需要公開

把每個結果分成「可以公開」與「機密」。機密就照下面的移除/撤銷/遷移步驟處理。
# audit under your own public directories (inspecting your own assets)
find ~ -path '*/public/*' \( -iname '*token*.json' -o -iname '*credential*.json' \
      -o -iname '*.bak' -o -iname '*.sql' -o -iname '.env*' \) 2>/dev/null
# an exposed .git directory is dangerous too
find ~ -path '*/public/*/.git' -maxdepth 8 2>/dev/null

如何檢查自己的環境

上面的 find 只是起點。依序完成以下五項檢查,可以找出光靠檔名搜尋會漏掉的遺留檔案。只對自己的伺服器和自己的網域執行。.env 專屬的檢查(查看回應的前幾個位元組、確認 .htaccess 確實生效)請見 共用伺服器上的 .env 保護。

1

也搜尋備份與壓縮檔

公開目錄的名稱因主機而異,所以要包含 public_html、htdocs 和 www。例如執行 find ~/public_html \( -name '*~' -o -name '*.swp' -o -name '*.old' -o -name '*.orig' -o -name '*.zip' -o -name '*.tar.gz' -o -name '*.pem' -o -name '*.key' \) 2>/dev/null,逐一確認每個結果是否真的應該公開。

2

確認目錄清單已關閉

用瀏覽器開啟一個沒有 index.html 的目錄(例如 /uploads/)。如果看到檔名清單,代表 Web 伺服器的目錄清單功能是開著的。有了清單,別人不需要知道檔名就能找到遺留檔案。回傳 403 或 404 才是你要的結果。

3

在建置後的 JavaScript 中搜尋機密

如果你有建置前端,請搜尋要部署的輸出資料夾(dist/、.next/static/ 等)。可以用 grep -rlE 'BEGIN [A-Z ]*PRIVATE KEY|secret|password' dist/ 之類的指令,再加上你所用服務的 key 前綴。如果找到了,檢查該變數是否使用了面向瀏覽器的名稱(VITE_、NEXT_PUBLIC_)。

4

確認 webroot 之外的機密不會被其他使用者讀取

把機密移出 webroot 後,如果權限太寬鬆,同一台伺服器上的其他帳號仍能讀取,那也沒有用。find ~ -maxdepth 3 \( -name '.env*' -o -name '*.pem' -o -name '*token*.json' \) -perm /o+r 2>/dev/null 會列出擁有者以外的人也能讀取的機密;用 chmod 600 修正。沒有輸出就代表沒問題。

5

確認搜尋引擎是否已建立索引

在 Google 搜尋 site:yourdomain,找找有沒有你不認得的檔案(.sql、.zip、.txt 等)。如果你使用 Search Console,「網頁」報表會列出已建立索引的 URL。如果東西還在搜尋結果中,處理方式請見下方的常見錯誤。

發現時的處理

移除、撤銷、遷移 三件事要成套執行。少做任何一件,修正就不完整。

1

從 webroot 移除,讓 URL 取不到

把檔案移出公開目錄或刪除,並確認該 URL 現在回傳 404。
2

撤銷/重新核發可能已外洩的金鑰或權杖

不要以「應該沒事」帶過。既然曾經暴露,就 假設已外洩,更新或撤銷該權杖/金鑰。
3

機密放在 webroot 之外,權限 600

今後把機密放在公開目錄之外(例如應用程式根目錄之上),權限設為 600(僅擁有者可讀)。如果設定可以拒絕提供敏感副檔名,也一併加上。

依伺服器區分:拒絕提供這些檔案的設定(作為備援)

真正的修正是不要把檔案放在那裡。在此之上,再在 Web 伺服器加上拒絕規則,讓不小心放進去的東西仍然不會被提供。能用的方法依環境而定。

環境拒絕方式目錄清單注意事項
Apache(VPS)在 <Directory> 區塊或 .htaccess 中使用 <FilesMatch> + Require all denied用 Options -Indexes 關閉Apache 2.4 的 AllowOverride 預設為 None,除非你允許,否則 .htaccess 會被忽略
Nginx(VPS)在 location 中使用 deny all;autoindex 預設為 off不讀取 .htaccess。正規表示式的 location 依撰寫順序檢查,第一個符合的生效
共用主機通常是 .htaccess(相容 Apache)透過控制台或 .htaccess用不含機密的測試檔案確認它確實生效
靜態主機/CDN輸出資料夾中的東西會原樣公開依服務而定依靠「不把機密放進輸出資料夾」,而不是拒絕規則

Apache 範例(已啟用 .htaccess 的環境):

Options -Indexes
<FilesMatch "(^\.|~$|\.(bak|old|orig|swp|sql|zip|tar|gz|pem|key)$)">
    Require all denied
</FilesMatch>

Nginx 範例(在 server 區塊內):

# Allow the path used for automatic certificate renewal (Let's Encrypt) first
location ^~ /.well-known/acme-challenge/ { }
 
# Deny files and directories starting with a dot (.env, .git, ...)
location ~ /\. { deny all; }
 
# Deny backups and archives
location ~* (~|\.(bak|old|orig|swp|sql|zip|tar|gz|pem|key))$ { deny all; }

在 Nginx 中,請求符合標記為 ^~ 的 location 時,就不會再檢查正規表示式的 location。因此即使有拒絕以點開頭路徑的規則,憑證更新用的 /.well-known/acme-challenge/ 仍會被提供。修改設定後,用 nginx -t 檢查語法、重新載入,並確認測試檔案回傳 403。如果你的站點提供 ZIP 等檔案下載,請把該副檔名從拒絕清單中移除。

容易落入的放置方式(危險)

  • 整合用的權杖 JSON 放在 public/
  • 樣板裡帶著的機密檔案保持公開
  • 備份(.sql/.bak)或 .git 留在公開目錄底下
  • 只修一個站點就說「處理完了」

正確的放置方式

  • 公開目錄 只放可以公開的東西
  • 機密放在 webroot 之外,權限 600
  • 敏感副檔名 拒絕 提供
  • 發現一個就 盤點所有主機與站點

把「只放可以公開的東西」定為固定規則

規則比逐次修補更有效。規定公開目錄只放可以公開的檔案,原則上絕不把機密、備份、版本控制資料(.git)或設定檔放在那裡。更新樣板時,要從範本本身移除機密,否則之後大量建置的每個站點都會繼續複製同一個機密檔案。

常見錯誤與修正方式

  • 依賴拒絕規則,卻把檔案留在原處:搬遷伺服器或改寫設定時規則可能消失,檔案從那天起就公開了。Nginx 完全不讀 .htaccess。把規則當作備援,並把檔案移出公開目錄。
  • 直接在正式伺服器上編輯檔案:編輯器會留下以 ~ 或 .swp 結尾的備份。OWASP 測試指南也建議不要直接在 Web 伺服器上編輯檔案。請在本機編輯,透過部署送出變更。
  • 把整個專案資料夾複製到公開目錄:.env、.git 和測試資料都會跟著出去。只提供建置輸出資料夾,並明確指定要複製的內容。
  • 拒絕所有以點開頭的路徑,結果憑證更新失敗:Let's Encrypt 把驗證檔案放在 /.well-known/acme-challenge/ 底下。如上面的 Nginx 範例,先允許那個路徑。
  • 把機密放在 VITE_ 或 NEXT_PUBLIC_ 變數中:它們會被嵌入建置後的 JavaScript,任何人都讀得到。只在伺服器端程式碼中使用機密,並撤銷已經以這種方式送出的 key。
  • 刪除檔案就停手:如果搜尋引擎已建立索引,它可能還留在結果中。確認 URL 回傳 404 或 410,然後用 Google Search Console 的「移除」工具暫時隱藏(約六個月),同時等待重新檢索把它清除。

實際案例:公開報告裡隱藏的個人資料

負責華盛頓特區 Medicaid(低收入者的公共醫療保險)的醫療保健財務部(DHCF)表示,2023 年至 2026 年 7 月間刊登在其網站上的兩份統計報告,以「隱藏欄位」形式包含了原始的個人資料。這些報告本來只顯示彙總數字,但檔案中仍保留了受益人的 Medicaid 號碼、出生日期等資訊。向美國衛生及公共服務部(HHS)提交的通報中,受影響人數為 399,086 人。DHCF 於 2026 年 7 月 21 日得知此問題,並移除了報告。

這不是遺忘機密檔案的案例,但根源相同:

  • 公開目錄裡的檔案可以被完整取走,不只是畫面上顯示的部分
  • 發布者以為「看不到」的資訊,在審查時最容易被漏掉
  • 可能在被發現之前就已經刊登了好幾年

在發布前檢查「這個檔案裡實際有什麼」的步驟,能同時防止遺留檔案和隱藏資料。完整經過請見 華盛頓特區 Medicaid 資料外洩(英文)。

本站是怎麼做的

本站的基本做法是機密(金鑰、權杖、連線字串)既不放進公開目錄,也不放進程式碼儲存庫。部署產物只包含建置好的公開資源;機密則作為執行環境的環境變數,存放在不提供存取的地方。原因是:本文這類事故的典型情況,就是一個遺留檔案讓任何請求其 URL 的人取得全部內容。我們把放置位置本身設計成存取控制,並與 漏洞因應的盤點 以相同週期執行這項盤點。

接下來閱讀

參考資料

  • IPA, "安全なウェブサイト運営にむけて/20 Checkpoints for Secure Website Operation and Management"(日文;不應公開的檔案、搜尋引擎快取):ipa.go.jp
  • OWASP, "Web Security Testing Guide: Review Old Backup and Unreferenced Files for Sensitive Information"(.old、~、.swp 備份;不要直接在伺服器上編輯):wstg.owasp.org
  • Apache HTTP Server 2.4, "Upgrading to 2.4 from 2.2"(AllowOverride 預設為 None)與 "mod_autoindex"(Options +Indexes 會產生清單):httpd.apache.org, httpd.apache.org
  • nginx, "ngx_http_core_module: location"(比對順序與 ^~)與 "ngx_http_autoindex_module"(autoindex 預設為 off):nginx.org, nginx.org
  • Let's Encrypt, "Challenge Types"(HTTP-01 使用 /.well-known/acme-challenge/):letsencrypt.org
  • Vite, "Env Variables and Modes"(VITE_ 變數會暴露給用戶端程式碼):vite.dev
  • Next.js, "Environment Variables"(NEXT_PUBLIC_ 變數會被嵌入瀏覽器的 JavaScript):nextjs.org
  • Google Search Console 說明, "Removals tool"(暫時移除約持續六個月):support.google.com
  • District of Columbia DHCF, "Notice of Data Incident":dhcf.dc.gov;HIPAA Journal(向 HHS 通報的人數):hipaajournal.com

FAQ

Q把檔案放在公開目錄裡有什麼危險?
A

放在 Web 公開目錄(webroot,例如 public/、public_html/)裡的東西,任何人只要存取它的 URL 就能取走。遺留的權杖/憑證 JSON、.env、備份或金鑰檔案,會在沒人注意時被取走,直接造成損害。公開目錄只應該放可以公開的東西。

Q在一個站點發現了,要檢查其他站點嗎?
A

要。如果你用共用範本或樣板大量建置站點,同樣的遺留檔案通常已被複製到所有站點。發現一個,就要盤點源自同一處的所有站點與主機。

Q發現之後該怎麼做?
A

1) 把檔案從 webroot 移除,讓 URL 回傳 404。2) 更新/撤銷可能已外洩的權杖或金鑰(假設已外洩)。3) 今後把機密放在 webroot 之外,權限設為 600(僅擁有者可讀)。三件事要成套執行。

Q哪些檔案絕對不該放在公開的 Web 目錄裡?
A

常見的有 .env 等設定檔、權杖或憑證 JSON、資料庫傾印(.sql)、整個站點的 ZIP 或 tar.gz 壓縮檔、.git 目錄,以及手動或編輯器產生的備份(.bak、.old、.orig、.swp,或以 ~ 結尾的檔名)。OWASP 測試指南說明,如果 login.php 被保留為 login.php.old,伺服器可能不會把它當成程式碼執行,而是把原始碼以純文字回傳。

Q檔案刪除了,卻還出現在搜尋結果中,怎麼辦?
A

先確認 URL 回傳 404 或 410,然後用 Google Search Console 的「移除」工具暫時從結果中隱藏。根據 Google 的說明頁面,暫時移除約持續六個月,應搭配刪除檔案等永久修正。日本 IPA 也建議請搜尋引擎移除快取副本。無論是否已從搜尋結果中消失,檔案中包含的金鑰或權杖都要撤銷。

Q可以把機密放在前端環境變數(VITE_ 或 NEXT_PUBLIC_)裡嗎?
A

不行。Vite 會把以 VITE_ 開頭的變數、Next.js 會把以 NEXT_PUBLIC_ 開頭的變數,在建置時嵌入送往瀏覽器的 JavaScript。這和把檔案放在公開目錄裡一樣:任何人都讀得到。API key 等機密請放在只有伺服器端程式碼使用的變數中。