跳到正文
>_ITDITDWeb 安全平台

安全指南

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/』这样的结构改造。