跳至主要內容
>_ITDITD網站資安平台

資安指南

Laravel 應用程式的 .env 被暴露給全世界——共享虛擬主機上最常見的部署錯誤

放在共享虛擬主機上的 Laravel 應用程式,.env、.git、資料庫匯出檔、OAuth 私鑰處於任何人都能透過 HTTP 讀取的狀態——這是現場實際中很常見的部署錯誤案例。根本原因是『把整個專案放到了 public_html 目錄正下方』。本文從防禦視角整理出緊急處置→金鑰輪換→結構改造這三個階段,並說明為什麼這不是個人的失誤,而是業界層面的「壞做法被標準化」。

發布於 2026-06-07 閱讀時間 3 分鐘

本文把共享虛擬主機上現實中經常發生的設定錯誤,作為一個隱去了網域和具體數值的案例當作教訓來講。這不是去找別人網站的事。目的是「讓自己的(或接手的)部署變得安全」。

事故摘要 — INCIDENT FILE
類型
.env / .git / 資料庫匯出檔 的暴露(設定錯誤)
嚴重度
Critical(所有金鑰都能透過 HTTP 讀取的狀態)
原因
把 Laravel 本體放在 public_html 正下方(本應只公開 public/)
傳播
每增加一個應用程式就複製同一個漏洞=擴大到數十個
永久對策
把本體移到 docroot 之外+只對 public/ 做 symlink

最危險的是 .env 和 .git

.env 是把資料庫認證、郵件、外部 API、加密金鑰(APP_KEY)全都裝進去的「鑰匙串」。如果 .git 被公開,那麼不僅是當前值,還能沿著 git 歷史回溯,連過去輪換過的那些也一併還原。詳情請參閱術語詞典的 .env 是什麼

當時發生了什麼

在共享虛擬主機上,許多 Laravel 應用程式都是這樣部署的。把危險的部署正確的部署並列對比:

危險的部署(容易犯)

~/public_html/
└── app/          ← 整個 Laravel(錯誤)
    ├── .env      ← 通過 /app/.env 能讀到
    ├── .git/     ← clone 即可連歷史一起還原
    ├── vendor/  config/  storage/
    └── public/   ← 其實「只該」放這裡

正確的部署

~/laravel/app/    ← 本體在公開根目錄之外
├── .env  .git/   ← HTTP 構不到=安全
└── public/       ← 只把這裡用 symlink 公開
        └── index.php

從外部能以 200 OK 取得的典型檔案:

檔案危險在哪
.env / .env.bk_*資料庫認證、郵件、外部 API 金鑰、APP_KEY 全都是明文
.git/ 整套可用 git clone 還原原始碼和全部歷史
composer.lock / package-lock.json相依套件的精確版本=針對已知 CVE 精準打擊的素材
credentials.jsonOAuth 用戶端私鑰被一鍋端
*.sql / db-*.sql.gz資料庫的全量匯出,壓縮後有數十 MB

尤其可怕的「傳播」

一旦採用這種部署,每增加一個同類應用程式就會複製同一個漏洞。1 個設定錯誤增殖成數十個。「一發現就全部排查」是鐵律。

修復方法:三個階段

Step 1 — 緊急處置:用 .htaccess 在 HTTP 層面切斷危險檔案

先把「繼續洩漏」止住。這一步可逆,不影響正常顯示。在 public_html/.htaccess 開頭加上拒絕存取機密檔案的程式碼區塊(防禦目的的設定範例):

# === SECURITY BLOCK ===
# .env / .git 配下を 404 に
RedirectMatch 404 (?i)/\.(env|git)(\..*)?(/|$)
 
# バックアップ・ダンプ・認証ファイルを拒否
<FilesMatch "(?i)\.(sql|sql\.gz|bak|old|swp|save|orig|tgz)$|^credentials\.json$|\.bk[._]">
    Require all denied
</FilesMatch>
 
# Laravel の内部ディレクトリが /<app>/storage/... 等で見えていた場合
RedirectMatch 403 (?i)^/[^/]+/(storage|bootstrap/cache|config|database|resources|routes|tests|vendor)(/|$)
# === END ===

不過黑名單本質上就是打地鼠。忘了堵 composer.lock、用新命名的檔案又多出漏洞——這些必然會發生。把它當作「盡力而為的止血」,真正的解決方案放在 Step 3。

Step 2 — 金鑰輪換(按已洩漏的前提)

即便用 .htaccess 堵上了,也要按已經被看到的前提來處理。按高風險優先順序:

  1. 外部 API 金鑰(最優先・立即):直接關係到計費和帳號被劫持。OAuth 的 CLIENT_SECRET、各種 API 金鑰、雲端認證憑證都要作廢+重新簽發+按最小權限設定。
  2. OAuth 用戶端密鑰
  3. APP_KEY:重新產生會導致工作階段失效,還可能讓已加密的資料庫欄位無法解密,因此要先確認影響範圍。
  4. 郵件 SMTP、資料庫認證憑證。

輪換的坑(實際踩過)

某些 OAuth 在重新簽發用戶端密鑰時會同時讓現有的 refresh_token 也失效。用已儲存的權杖去重新取得就會失敗,從而引發正式環境故障。請務必在準備好「重新授權流程」之後再去重新簽發金鑰。此外,如果不同時更新本地開發側的 .env / 設定,下一次部署舊值就會復活。

Step 3 — 結構改造:把應用程式本體移到 docroot 之外(真正的解決方案)

把每個應用程式這樣重新部署:

# 1. 本体を公開ルートの外へ(同一ファイルシステムなら mv は一瞬で atomic)
mv ~/example/public_html/app  ~/example/app-laravel
 
# 2. 公開ルートには最小の bootstrap だけ置く
mkdir -p ~/example/public_html/sub
cat > ~/example/public_html/sub/index.php <<'PHP'
<?php require __DIR__ . '/../../app-laravel/public/index.php';
PHP
# 3. Laravel public/.htaccess をコピー、静的アセットを symlink、設定キャッシュをクリア

變成這種形態後,.env.gitvendor/ 本身就不在公開根目錄裡,因此不依賴 .htaccess 規則也安全。具體步驟整理在了 在虛擬主機上不讓 .env 被公開的部署與設定

✗ 危險:本體在公開根目錄之內

— 公開邊界(HTTP 可讀)—

.env ・ .git ・ vendor ・ public

↑ 全都在線的內側=一覽無餘

✓ 安全:本體在公開根目錄之外

— 公開邊界(HTTP 可讀)—

只有 public/(symlink)

.env ・ .git 在線的外側=構不到

邊界只有一條——『在公開根目錄之內還是之外』。.env 和 .git 只要在這條線的內側,任何人都能透過 HTTP 讀取。

路上那些「教科書裡沒寫」的坑

  • 子網域的作用域問題:對已註冊子網域的 docroot 事後用 symlink 指向另一棵目錄樹,可能不會跟隨生效而回傳 500。→ 讓 docroot 保持為真實目錄,從裡面的小 index.php 用絕對路徑 require 去讀取的「bootstrap-redirect 型」更穩定。
  • opcache 會一直記住壞掉的狀態:opcache 把 index.php 的載入失敗記了下來,即便重建也可能持續回傳 500。最後的手段是改檔名(如 entry.php 等)。
  • 「未註冊子網域」的 docroot 能透過父網域讀到:即便 https://sub.example/ 無效,https://example/sub/ 也能看到內容,這是容易被忽略的入口。排查時要把經父網域的路徑也一併堵上。
  • 把秘密值寫死進 config/*.php:與 .git 公開疊加在一起,就能從歷史裡還原所有輪換記錄。要始終經由 env(),並且不要在 fallback 預設值裡寫真實值

最大的教訓:這不是「個人的失誤」

虛擬主機萌芽期的「把所有東西放到 public_html 正下方」的文化,與前提是只暴露 public/ 的 Laravel 相遇,結果**「把應用程式本體放在 public_html/<app>/、再暴露其下的 public/」這種危險的部署事實上成了標準**。各家官方文件裡舉的例子也盡是「把專案放進 public_html 裡」,更是火上澆油。

所以對策不能靠個人的注意力,而要做成機制

用機制防止再次發生

  • 把部署手冊的預設做法定為「本體放在 docroot 之外,只對 public/ 做 symlink
  • 用 CI/lint 機器化檢查「不把 .env 納入 git」「不把專案放在 public_html/<app> 正下方」
  • 上線後定期確認自己網站的 .env/.git 從外部取不到(自我排查)

本站把這種「自己排查自己網站」的習慣化作為學習的主線。→ 超入門:.env 和 API 金鑰到底危險在哪

接下來讀

FAQ

Q.env 被暴露了,應該最優先處理什麼?
A

首先用 .htaccess 等立即切斷洩漏途徑(先止血)。接著把 .env 裡的外部 API 金鑰、OAuth 金鑰按高風險優先順序進行輪換(按照已經被看到的前提處理)。最後透過把應用程式本體移到公開目錄之外的結構改造,使其不依賴規則就處於安全狀態。

Q為什麼把 Laravel 放在 public_html 正下方很危險?
A

Laravel 的結構前提是只公開 public/。如果把整個專案放到 docroot,那麼它上層的 .env(全部金鑰)、.git(可連同歷史一起還原)、vendor/ 等也會整個被 HTTP 讀取到。

Q用 .htaccess 的黑名單堵住就夠了嗎?
A

作為緊急處置有效,但本質上是打地鼠。每加一個新命名的檔案就多一個漏洞。真正的解決方案是『把應用程式本體放到 docroot 之外,只透過 symlink 暴露 public/』這樣的結構改造。