對象:在共用虛擬主機(可使用 Apache/.htaccess 的環境)上放置 Laravel 等「以只公開 public/ 為前提」的應用程式的人。這是從真實事故(→ .env 全公開的故事)中提煉出來的、用於保護自己環境的步驟。
本站的視角:這不是「個人的疏忽」
在虛擬主機早期那種「把所有東西都放在 public_html 根下」的文化裡,流入了以只暴露 public/ 為前提的 Laravel,結果危險的目錄配置成了事實上的標準。連各家的官方文件給的例子也幾乎都是把專案放在 public_html 之內。所以正確的做法是把對策變成「機制」而不是個人的注意力——也就是改變部署流程的預設值。
用圖來掌握正確的目錄配置
✗ 危險(主體放在公開根目錄之下)
public_html/ ├─ .env ← 能被讀到! ├─ .git/ vendor/ └─ public/
✓ 安全(主體在外,只公開 public)
app-laravel/ ← 公開根目錄之外 ├─ .env .git/ ← 無法到達 └─ public/ → 從 public_html 處 require
Step 1:止血(立刻能做、可逆)
在公開根目錄的 .htaccess 開頭追加一段拒絕存取機密檔案的規則(用於防禦目的的設定範例)。它不影響正常顯示。
# === SECURITY BLOCK ===
RedirectMatch 404 (?i)/\.(env|git)(\..*)?(/|$)
<FilesMatch "(?i)\.(sql|sql\.gz|bak|old|swp|save|orig|tgz)$|^credentials\.json$|\.bk[._]">
Require all denied
</FilesMatch>
<FilesMatch "(?i)^(phpinfo|info)\.php$">
Require all denied
</FilesMatch>
# /<app>/storage 等內部目錄被暴露出來時
RedirectMatch 403 (?i)^/[^/]+/(storage|bootstrap/cache|config|database|resources|routes|tests|vendor)(/|$)
# === END ===止血不是終點
黑名單一定會出現 composer.lock、.gitignore 之類『忘了堵』的口子,是一場打地鼠。只能把它當作撐到 Step 2 之前的爭取時間手段。
Step 2:結構改造(長期對策)
把應用程式主體移到公開根目錄之外,公開根目錄裡只放一個「只負責載入的小入口」。
# 1. 把主體移到公開根目錄之外(同一檔案系統時 mv 是瞬間完成且 atomic)
mv ~/example/public_html/app ~/example/app-laravel
# 2. 在公開根目錄裡只建一個最小入口
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 連結
# 4. 清除設定快取
rm -f ~/example/app-laravel/bootstrap/cache/{config,services,packages,routes-v7}.php本站在實戰中踩過的坑:bootstrap-redirect 比 symlink 更穩
「把 public_html 本身做成指向 public/ 的 symlink」這種做法,在某些環境裡對已註冊子網域的 docroot 不會跟隨,會回傳 500。讓 docroot 保持為真實目錄、由其中一個小小的 index.php 用絕對路徑 require 的「bootstrap-redirect 型」,在有作用域限制的共用環境裡更穩定。此外 opcache 可能會記住一次損壞的載入並持續回傳 500,最後的手段是更改入口檔名(如 entry.php)。
Step 3:金鑰輪換(按已經外洩來處理)
如果存在過被公開的時段,就把 .env 裡的金鑰按已被人看到來處理並替換掉。優先順序是「外部 API、OAuth 金鑰 → 加密金鑰 → 郵件 → DB」。也請參閱 .env 詞條。重新簽發 OAuth 金鑰有時會使 refresh_token 失效,所以要先準備好重新授權流程再執行。
Step 4:自我檢查(養成習慣)
最後,確認自己的站點上是否真的發生了外洩(只針對自己擁有的網域)。
# 回傳 200 且帶正文就說明被公開了。回傳 403/404 暫時算 OK
curl -sI https://你的網域/.env | head -1
curl -sI https://你的網域/.git/config | head -1養成每次發布都檢查一遍的習慣,就能盡早發現目錄配置上的失誤。
接著閱讀
FAQ
Q現在馬上能做的對策是什麼?
在公開根目錄的 .htaccess 裡追加一段『拒絕存取 .env、.git、SQL 傾印、credentials.json 等』的規則來止血。它可逆,不影響正常顯示。但這只是應急處理,核心仍是結構改造。
Q為什麼只靠 .htaccess 不夠?
黑名單是每加一個新名字的檔案就多一個漏洞的『打地鼠』。只要把應用程式主體移到公開根目錄之外,.env 從根本上就無法到達,不依賴規則也能安全。
Q用 symlink 把 public/ 暴露出去不行嗎?
在某些環境下,把已註冊子網域的 docroot 事後用 symlink 指向另一棵目錄樹時不會跟隨,會回傳 500。按本站的經驗,讓 docroot 保持為真實目錄、由其中一個小小的 index.php 用絕對路徑 require 的『bootstrap-redirect 型』更穩定。