0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Nginxで.envをdenyしても ^~ のlocation配下では取れてしまう:公開ディレクトリの.env・.gitを「中」と「外」で確かめる(nginx 1.26で再現)

0
Posted at

結論

  • サーバーの「中」で、公開ディレクトリ(Nginx の root)に .env や .git が残っていないかを探します。
  • 手元のPCから「外」へ、/.env と /.git/config を実際に取りに行きます。200 が返ったら、中身を取られる状態です。
  • Nginx でドットファイルを deny しても、location ^~ /static/ のような設定があると、その配下では deny が効きません。筆者の検証環境でも 403 → 200 に戻ることを確認しました。閉じたあとは、必ずもう一度「外」から確かめてください。

この記事は、VPS連載の3本目です。1本目は UFWで閉じていてもDockerの公開ポートは別扱い、2本目は AIで作ったアプリをVPSに載せた直後に見る10項目 です。今回も「中」と「外」の両方で確かめる考え方で進めます。

検証環境

  • nginx/1.26.3(Debian 13 の nginx パッケージ)
  • 127.0.0.1 だけで待ち受ける使い捨ての設定で試しました。外には公開していません。
  • 置いたファイルはすべてダミーです(.env の中身は DUMMY_API_KEY=dummy-not-a-real-key-000 のような値)。検証後に設定・ファイルとも削除しました。

実際の VPS(Ubuntu など)や、ディストリビューションが用意する既定の設定ファイルでは試していません。結果は、下に載せる設定での動きです。

なぜ、AIで作ったアプリで起きやすいのか(推測)

ここは筆者の推測です。

  • git clone したフォルダを、そのまま Nginx の root にしてしまう(.git も一緒に公開される)
  • ビルド成果物と同じフォルダに .env を置いてしまう
  • 作業中の *.bak や DB ダンプ(*.sql)、ソースマップ(*.map)が残る

1. 中で探す

サーバーの中で、公開ディレクトリを探します。パスは自分の root に合わせてください。

sudo find /var/www -maxdepth 4 \( -name '.env*' -o -name '.git' -o -name '*.sql' -o -name '*.bak' -o -name '*.map' \)

ダミーのフォルダで試すと、次のように見つかったパスだけが出ます。中身は表示しません。

/www/app/.env
/www/app/.env.production
/www/app/.git
/www/app/db.sql
/www/app/dist/main.js.map
/www/app/x.bak

root が複数ある場合は、筆者が作っている読み取り専用のCLI vps-check の WEB-002 でもまとめて点検できます。Nginx の設定にある静的な root / alias の配下から、環境ファイル・Git のメタデータ・DB ダンプ・バックアップ・アーカイブ・ソースマップを探し、パスとサイズだけを表示します。README に書いているとおり、次の点は WEB-002 の対象外です。

  • ファイルの中身は読みません
  • Nginx のアクセス制御(deny など)は評価しません
  • 外から取れるかどうかは確かめません

つまり、WEB-002 で見つかっても、外から取れるとは限りません。逆に deny で閉じたつもりでも、取れる場合があります(後述)。だから次の「外」の確認が必要です。

2. 外から取る

自分のサーバーに対してだけ、手元のPCから実行します。example.com は自分のドメインに置き換えてください。

curl -s -o /dev/null -w '%{http_code}\n' https://example.com/.env
curl -s -o /dev/null -w '%{http_code}\n' https://example.com/.git/config

Nginx の既定のままの静的配信(root を指定して try_files $uri $uri/ =404; だけ)で試すと、ダミーの .env も .git/config もそのまま返りました。

$ curl -sI http://127.0.0.1:18061/.env
HTTP/1.1 200 OK
Server: nginx/1.26.3
Content-Type: text/plain
Content-Length: 39

Nginx は、ドットで始まるファイルを特別扱いしません。root の下にあれば、ほかのファイルと同じように返します。

200 が返ったら、中身を取られる状態です。すぐ次の章で閉じてください。鍵はもう漏れたものとして、再発行を先にするのがおすすめです(筆者の提案)。

3. Nginx で閉じる

server ブロックの中に、ドットで始まるパスを拒否する location を足します。.well-known は証明書の更新(ACME)で使うため、除外しています。

server {
    root /var/www/app;

    location / {
        try_files $uri $uri/ =404;
    }

    location ~ /\.(?!well-known) {
        deny all;
    }
}

この設定での結果です。

/.env                              403
/.git/config                       403
/.git/HEAD                         403
/static/.env                       403
/static/app.css                    200
/.well-known/acme-challenge/test   200
$ curl -sI http://127.0.0.1:18062/.env
HTTP/1.1 403 Forbidden
Server: nginx/1.26.3

deny の行は、access モジュールの規則です。規則は上から順に調べられ、最初に当てはまったもので決まります(nginx 公式 ngx_http_access_module)。

4. 落とし穴:^~ があると deny が効かない

Nginx は、リクエストに使う location を次の順で選びます(nginx 公式 ngx_http_core_module の location)。

  1. 文字列(prefix)の location の中から、一番長く一致するものを選んで覚えておく
  2. 正規表現の location を、設定ファイルに書かれた順に調べ、最初に一致したものを使う
  3. どの正規表現にも一致しなければ、1 で覚えた prefix の location を使う

ただし、公式には「一番長く一致した prefix location に ^~ が付いていると、正規表現は調べない」とも書かれています。

静的ファイルのキャッシュ設定などで、次のような location を足すことはよくあります。

    location ~ /\.(?!well-known) {
        deny all;
    }

    location ^~ /static/ {
        expires 7d;
    }

/static/.env に一番長く一致する prefix は ^~ /static/ です。そのため、正規表現の deny は調べられません。実際に試すと、/static/.env だけが 200 に戻りました。

/.env                              403
/.git/config                       403
/.git/HEAD                         403
/static/.env                       200   ← すり抜け
/static/app.css                    200
$ curl -sI http://127.0.0.1:18063/static/.env
HTTP/1.1 200 OK
Server: nginx/1.26.3
Content-Type: text/plain
Content-Length: 39
Expires: Sat, 24 Oct 2026 04:20:25 GMT
Cache-Control: max-age=604800

location の書き順を入れ替えても、この結果は変わらないはずです。^~ の判定は、正規表現を調べる前に行われるからです(公式の当たり順から導いた推測で、入れ替えは試していません)。

直し方(どちらも筆者の環境で 403 を確認)

A. ^~ の中にも deny を入れ子で書く

    location ^~ /static/ {
        location ~ /\. {
            deny all;
        }
        expires 7d;
    }

結果は /static/.env → 403、/static/app.css → 200 でした。location は入れ子にできます(公式)。

B. ^~ を外して、ただの prefix にする

    location /static/ {
        expires 7d;
    }

これでも /static/.env は 403 になりました。ただし ^~ を外すと、location ~* \.(css|js)$ のような正規表現の location があるときに、/static/ の下でもそちらが使われるようになります(公式の当たり順から)。もとの設定の意図が変わることがあるので、A のほうが影響は小さいと考えています(筆者の判断)。

ほかにも ^~ を使った location がないか、設定をまとめて確認できます。

sudo nginx -T 2>/dev/null | grep -n '\^~'

5. 一番いい対策は、置かないこと(筆者の提案)

deny は保険です。設定を足すたびに、今回のようなすり抜けが起きる余地があります。

  • root は、ビルドの成果物(dist/ など)だけを指すようにする
  • .env はアプリ側の、root の外に置く
  • .git のあるフォルダを root にしない。デプロイは成果物だけをコピーする

6. もう公開されていたら(筆者の提案)

  • 鍵・パスワードは、先に再発行する(ファイルを消しても、取られた値は戻りません)
  • そのあとファイルを消して、「外」から 404 か 403 になることを確かめる
  • アクセスログで、/.env や /.git/ への 200 の応答があったかを確認する

AIのエージェントに秘密情報を読ませない話は、AIエージェントに.envを読まれていないか、ファイル名だけで点検する日本語CLIを作った にまとめています。

まとめ

  • 中で find(または vps-check の WEB-002)を使って探し、外から curl で取りに行く。
  • Nginx の既定の静的配信では、.env も .git/config もそのまま 200 で返る(筆者の環境で確認)。
  • location ~ /\.(?!well-known) { deny all; } で 403 になる(筆者の環境で確認)。
  • ただし、一番長く一致した prefix に ^~ があると正規表現は調べられず(公式)、その配下では 200 に戻る(筆者の環境で確認)。
  • 閉じたら、必ずもう一度「外」から確かめる。

関連記事:

※ nginx の仕様は、2026-10-17 時点の公式ドキュメントに基づきます。検証は nginx/1.26.3 で、ローカルの使い捨て環境でのみ行いました。

更新情報やちょっとしたTipsは X(@supportapp3_lab)で発信しています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?