對象:在共用虛擬主機(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
依你的環境選擇對策
「不讓 .env 被公開」的具體做法,會因伺服器類型和你的權限而不同。先找到符合你的那一列,再照下面的步驟進行。
| 環境 | 永久修復 | 緊急處置 | 注意事項 |
|---|---|---|---|
| 共用虛擬主機(可用 SSH) | 把應用程式主體移出公開資料夾,只在其中留一個小的 index.php(Step 2) | 在 .htaccess 拒絕存取 | 把公開資料夾本身改成 symlink,在某些環境會回傳 500 |
| 共用虛擬主機(只有 FTP) | 用 FTP 用戶端把應用程式主體重新上傳到公開資料夾(public_html 等)的上一層 | 在 .htaccess 拒絕存取 | 大多數共用主機允許寫入公開資料夾的上一層;如果不行,請詢問客服 |
| 控制台可以設定公開資料夾 | 把公開資料夾直接指向 app-laravel/public | 不需要(仍要檢查) | 切換後,刪除留在舊公開資料夾裡的應用程式副本 |
| VPS(Apache) | 把 VirtualHost 的 DocumentRoot 指向 public/ | 在伺服器設定裡加入拒絕規則 | AllowOverride None(Apache 2.4 的預設值)時不會讀取 .htaccess |
| VPS(Nginx) | 把 root 指向 public/,並拒絕以點開頭的檔案 | 在伺服器設定裡加入拒絕規則 | Nginx 不讀 .htaccess |
無論哪種環境,目標都一樣:公開資料夾裡沒有 .env、.git、vendor/ 或 storage/。Laravel 官方文件也警告,把 index.php 移到專案根目錄並從那裡提供服務,會讓許多敏感的設定檔暴露在公開網路上。
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>
# if Laravel internals are visible at /<app>/storage/... etc.
RedirectMatch 403 (?i)^/[^/]+/(storage|bootstrap/cache|config|database|resources|routes|tests|vendor)(/|$)
# === END ===緊急處置不是終點
黑名單總會漏掉什麼,例如 composer.lock 或任何新的檔名。把它當作撐到 Step 2 之前的權宜之計。
Step 2:結構改造(永久修復)
把應用程式主體移到公開根目錄之外,只留下一個負責載入它的小入口。
# 1. Move the body outside the web root (same filesystem → mv is instant, atomic)
mv ~/example/public_html/app ~/example/app-laravel
# 2. Put only a minimal entry in the web root
mkdir -p ~/example/public_html/sub
cat > ~/example/public_html/sub/index.php <<'PHP'
<?php require __DIR__ . '/../../app-laravel/public/index.php';
PHP
# 3. Copy Laravel public/.htaccess, symlink static assets
# 4. Clear config cache
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:自我檢查(養成習慣)
最後,確認自己的網站實際上沒有公開任何東西(只針對自己擁有的網域)。
# A 200 with a body means it's exposed. 403/404 is okay for now.
curl -sI https://your-domain/.env | head -1
curl -sI https://your-domain/.git/config | head -1每次部署都檢查一遍,就能及早發現目錄配置的失誤。
如何檢查自己的環境(更詳細)
除了 Step 4 的兩行指令,下面這些檢查能抓到更多常被遺漏的地方。只對自己的伺服器和自己的網域執行。
看內容,而不只是狀態碼
有些應用程式對任何未知的 URL 都回傳首頁和 200,所以只看狀態碼看不出什麼。只印出開頭部分,例如 curl -s https://your-domain/.env | head -c 120,看看有沒有出現 APP_KEY= 或 DB_PASSWORD= 這樣的設定行。對 .git/HEAD 來說,如果出現以 ref: refs/heads/ 開頭的一行,就表示已經公開。
每個應用程式都要檢查
不只主網域,子網域或子目錄上的每個應用程式都要重複檢查。新增應用程式時,配置上的失誤往往會被複製過去,修好一個不代表其他的也修好了。
在伺服器上搜尋公開資料夾
能用 SSH 的話,直接找出有風險的檔案,例如 find ~/public_html -maxdepth 4 \( -name ".env*" -o -name ".git" -o -name "*.sql" -o -name "*.zip" \)。沒有任何輸出才是理想的結果。只有 FTP 的話,在 FTP 用戶端打開「顯示隱藏檔案」,檢查檔案清單。
確認 .htaccess 真的生效
在公開資料夾放一個不含任何機密的無害測試檔(例如內容只有 test 的 check.bak),然後用 URL 打開它。應該得到 403。如果能打開,表示 .htaccess 沒有被讀取,請確認伺服器類型(例如 Nginx)或其設定。檢查完後刪除測試檔。
確認除錯模式已關閉
在 .env 裡,APP_ENV 應為 production,APP_DEBUG 應為 false。Laravel 文件指出,在正式環境設定 APP_DEBUG=true,有可能讓使用者看到敏感的設定值。能用 SSH 的話,php artisan about --only=environment 也能顯示相同資訊。
常見錯誤與修正
- 刪了 .env 卻留下 .git:公開的
.git可能讓人重建過去的 commit,包括舊的.env或金鑰。把.git也移出公開資料夾,並重新簽發任何曾被 commit 過的機密。 - 在公開資料夾留下
.env.bak或.env.old之類的副本:含有真實值的副本和.env一樣敏感。移出或刪除。.env.example裡只放佔位值。 - 把整個專案打包成 ZIP 上傳,解壓後沒刪:ZIP 裡含有
.env。解壓後立刻刪除。 - 以
APP_DEBUG=true運行正式環境:錯誤頁面可能洩漏設定值。正式環境一律設為false。 - 以為寫了
.htaccess就安全:Nginx 和AllowOverride None的環境根本不讀它。用上面的測試檔方法確認。 - 突然更換
APP_KEY:依 Laravel 文件,更換金鑰會讓所有已登入的使用者被登出,用舊金鑰加密的資料也無法解密。Laravel 11 以後的版本,切換前先把舊金鑰放進APP_PREVIOUS_KEYS。
實際案例:公開的 .env 被大規模蒐集
2024 年 8 月,一家資安公司的研究團隊報告了一起攻擊行動:自動蒐集網頁伺服器上公開的 .env 檔案,利用其中的雲端憑證把資料複製出去,再勒索贖金。根據報告,約 110,000 個網域成為目標,蒐集到超過 90,000 個環境變數,其中約 7,000 個是雲端服務的憑證,約 1,500 個是社群媒體帳號的憑證。
報告認為原因不在雲端服務本身的漏洞,而是受害組織的設定錯誤,誤將 .env 檔案公開。由此可以得到兩點教訓:
- 公開的
.env不是偶然被發現的,而是被程式大規模搜尋出來的 - 一個
.env就可能讓損害擴散到雲端儲存、電子郵件等其他服務
不要以為小網站不會被找到。同樣的配置失誤的匿名案例,請見 .env 全公開的故事。
接著閱讀
- 事故:.env 全公開的故事
- 術語:什麼是 .env
- 入門:.env 和 API 金鑰到底危險在哪
- 當主機業者被入侵時:網頁主機遭入侵時該做的事(英文,自己的設定無法防範的情況)
- 確認是否遭入侵:如何確認網站或伺服器是否被入侵(發現 .env 曾被公開時該查看的地方)
出處
- Laravel 文件「Deployment」(讓請求指向
public/index.php、不要從專案根目錄提供服務、正式環境設APP_DEBUG=false):laravel.com - Laravel 文件「Configuration」(不要把
.envcommit 到版本控制;Debug Mode):laravel.com - Laravel 文件「Encryption」(輪換
APP_KEY與APP_PREVIOUS_KEYS):laravel.com - Apache HTTP Server 2.4「core - AllowOverride」(設為
None時不讀.htaccess;預設值為None):httpd.apache.org - Apache HTTP Server 2.4「.htaccess files」(使用時機與注意事項):httpd.apache.org
- OWASP「WSTG-CONF-04 Review Old Backup and Unreferenced Files for Sensitive Information」:owasp.org
- Unit 42(Palo Alto Networks)「Leaked Environment Variables Allow Large-Scale Extortion Operation in Cloud Environments」(2024 年 8 月 15 日):unit42.paloaltonetworks.com