対象:レンタル/共用サーバーやVPSで、複数のサイトを公開ディレクトリ(public/・public_html/・htdocs/ 等)にデプロイして運用している人。ここでは攻撃手順は扱わず、自分のサーバーを棚卸しして塞ぐことだけを扱います。.envそのものの守り方は 共用サーバーでの.env保護 も参照。
当サイトの視点:『置き場所』はアクセス制御そのもの
秘密が漏れる原因は、難しい攻撃よりも置き場所の事故が多いです。webrootは『誰でもURLで取りに来られる場所』。そこに秘密を置いた時点で、どんなに強いパスワードのサーバーでも意味がありません。当サイトは秘密をwebrootにもgitにも置かないことを基本にしています(→ 秘密の安全な保管)。考え方はシンプルで、公開ディレクトリには誰に見られても困らない物だけを置きます。
public/ = URLで誰でも取得可
置いてよい
画像・CSS・JS など公開アセット
置くな
.env・token.json・.sql・.git・.bak
アプリのルートより上 = 配信されない
ここに置く
秘密・トークン・認証情報・バックアップ。権限600(所有者だけ読める)。URLからは到達できない。
なぜ「公開ディレクトリの置き忘れ」が起きるのか
実際にありがちなのは、便利のために置いたファイルや、ひな形に最初から入っていたファイルが、そのまま公開され続けるパターンです。
たとえば、外部サービス連携のために作ったアクセストークン入りのJSONが、public/ の下に置かれたまま長期間放置される。しかもそれが共通テンプレート由来だと、同じファイルが複数サイトに同じ場所でコピーされ、全サイトが同じ問題を抱えます。1サイトを直しても、他のサイトに残っていれば意味がありません。同様に危ないのは、.env・データベースのバックアップ(.sql)・.git ディレクトリ・エディタのバックアップ(*.bak)・ショートカット等の置き忘れです(関連:パストラバーサル・.envが丸ごと公開された話)。
置き忘れやすいファイルと、それぞれの危険
「秘密のファイル」と聞くと .env を思い浮かべがちですが、実際に残りやすいのはもっと幅が広いです。OWASP(Webセキュリティの非営利団体)のテストガイドも、古い版のファイルやバックアップ、どこからもリンクされていないファイルを点検項目に挙げています。
| 種類 | よくある名前の例 | 何が危ないか | 置くべき場所 |
|---|---|---|---|
| 設定ファイル | .env、config.php.bak、settings.json | データベースのパスワードやAPIキーがそのまま書かれている | アプリのルートより上(配信されない場所) |
| トークン・認証情報 | token.json、credentials.json、*.pem、*.key | 外部サービスや別のサーバーに、そのままログインできる | webrootの外+権限600 |
| 手作業・エディタのバックアップ | login.php.old、index.php~、.index.php.swp、*.orig | 拡張子が変わるとプログラムとして実行されず、ソースコードがそのまま表示されることがある | 本番では作らない。作業は手元で行う |
| データベースのダンプ | backup.sql、dump.sql.gz | 利用者の個人情報やパスワードのハッシュが丸ごと入っている | バックアップ専用の場所(サーバー外が望ましい) |
| サイトを固めたファイル | site.zip、www.tar.gz | 中に .env やソースコード一式が入っている | 展開したらすぐ削除 |
| バージョン管理のデータ | .git/ | 過去のコミットに入っていた秘密まで復元されるおそれがある | 公開ディレクトリの外 |
| ビルドで埋め込まれた値 | assets/index-xxxx.js の中 | VITE_・NEXT_PUBLIC_ で始まる変数はブラウザ向けのJavaScriptに入る | 秘密はサーバー側の変数だけにする |
表の最後の行は見落とされがちです。ファイルとしては置いていなくても、ビルドの結果として秘密が公開ディレクトリに出ていくことがあります。Viteの公式ドキュメントは、VITE_ で始まる変数にAPIキーなどの機微な情報を入れてはいけないと明記しています。
自分のサーバーを棚卸しする
まず「自分の公開ディレクトリに、秘密らしきファイルが無いか」を機械的に洗い出します。これは自分の資産の点検であって、他者のサイトを覗く行為ではありません。
公開ディレクトリ配下の秘密らしきファイルを探索
public/ 等に紛れた秘密候補(トークン/認証情報のJSON・.env・鍵・ショートカット)を洗い出す。同じ物が他にもある前提で『全台・全サイト』へ広げる
本当に公開が必要かを1つずつ判断
# 自分のサーバーの公開ディレクトリ配下を棚卸し(自分の資産の点検)
find ~ -path '*/public/*' \( -iname '*token*.json' -o -iname '*credential*.json' \
-o -iname '*.bak' -o -iname '*.sql' -o -iname '.env*' \) 2>/dev/null
# .git ディレクトリの公開も危険
find ~ -path '*/public/*/.git' -maxdepth 8 2>/dev/null自分の環境で確認する方法
上の find は入口です。次の5つを順に確認すると、ファイル名の探索だけでは見つからない置き忘れも拾えます。どれも自分のサーバー・自分のドメインに対してだけ行ってください。.env に絞った確認(中身の先頭を見る方法や .htaccess が効いているかの確かめ方)は 共用サーバーでの.env保護 にまとめています。
バックアップや圧縮ファイルも探す
公開ディレクトリの名前は環境によって違うので、public_html・htdocs・www も含めて探します。たとえば find ~/public_html \( -name '*~' -o -name '*.swp' -o -name '*.old' -o -name '*.orig' -o -name '*.zip' -o -name '*.tar.gz' -o -name '*.pem' -o -name '*.key' \) 2>/dev/null のように実行し、出てきたファイルが本当に公開する物かを1つずつ確かめます。
ディレクトリの一覧が表示されないか確かめる
index.html などを置いていないディレクトリ(例:/uploads/)をブラウザで開きます。ファイル名の一覧が表示されたら、Webサーバーの「ディレクトリ一覧」機能が有効です。一覧が出ると、名前を知らなくても置き忘れに気づかれます。403か404が返るのが正常です。
ビルド後のJavaScriptに秘密が入っていないか探す
フロントエンドをビルドしているなら、配信する出力フォルダ(dist/ や .next/static/ など)の中を検索します。grep -rlE 'BEGIN [A-Z ]*PRIVATE KEY|secret|password' dist/ のように、秘密鍵の見出し行や、使っているサービスのキーの書き出し部分で探します。見つかったら、その変数がブラウザ向けの名前(VITE_・NEXT_PUBLIC_)になっていないかを確認します。
webrootの外の秘密が、他の利用者から読めないか確かめる
秘密をwebrootの外に移しても、権限が緩いと同じサーバーの別アカウントから読めることがあります。find ~ -maxdepth 3 \( -name '.env*' -o -name '*.pem' -o -name '*token*.json' \) -perm /o+r 2>/dev/null で、所有者以外にも読める秘密を洗い出し、chmod 600 で所有者だけにします。何も出なければ正常です。
検索エンジンに載っていないか確かめる
Googleで site:自分のドメイン と検索し、見覚えのないファイル(.sql、.zip、.txt など)が結果に出ていないかを見ます。Search Consoleを使っているなら、「ページ」のレポートで登録済みのURLを一覧できます。検索結果に残っていた場合の対処は、下の「よくある間違い」で説明します。
見つけたときの対応
見つけたら、除去・失効・移動の3点をセットで行います。どれか欠けると対処が不完全になります。
webrootから除去し、URLで取れない状態に
漏れた可能性のある鍵・トークンは失効/再発行
秘密はwebrootの外+権限600へ
サーバー別:配信を拒否する設定(保険として)
本命は「置かないこと」です。そのうえで、うっかり置いてしまったときに配信されないよう、Webサーバー側にも拒否の設定を入れておきます。使えるものは環境によって違います。
| 環境 | 拒否の書き方 | ディレクトリ一覧 | 注意点 |
|---|---|---|---|
| Apache(VPS) | 設定ファイルの <Directory> か .htaccess に <FilesMatch> + Require all denied | Options -Indexes で止める | Apache 2.4 は AllowOverride の既定が None で、許可しないと .htaccess が読まれない |
| Nginx(VPS) | location で deny all; | autoindex は既定で off | .htaccess は読まない。正規表現の location は書いた順に照合され、最初に一致したものが使われる |
| 共用レンタルサーバー | 多くは .htaccess(Apache互換)で書ける | 管理画面か .htaccess で止める | 効いているかは、秘密を含まないダミーのファイルで確かめる |
| 静的ホスティング・CDN | 出力フォルダの中身がそのまま公開される | サービスによる | 拒否設定より、出力フォルダに秘密を入れない運用が前提 |
Apacheの例(.htaccess が有効な場合):
Options -Indexes
<FilesMatch "(^\.|~$|\.(bak|old|orig|swp|sql|zip|tar|gz|pem|key)$)">
Require all denied
</FilesMatch>Nginxの例(server ブロックの中):
# 証明書の自動更新(Let's Encrypt)で使う場所は先に許可する
location ^~ /.well-known/acme-challenge/ { }
# ドットで始まるファイル・ディレクトリ(.env, .git など)を拒否
location ~ /\. { deny all; }
# バックアップや圧縮ファイルを拒否
location ~* (~|\.(bak|old|orig|swp|sql|zip|tar|gz|pem|key))$ { deny all; }Nginxでは、^~ を付けた場所に一致すると、その後の正規表現の照合が行われません。そのため、証明書の更新に使う /.well-known/acme-challenge/ は、ドットで始まるパスを拒否するルールの影響を受けずに配信されます。設定を変えたら nginx -t で書式を確かめてから読み込み直し、ダミーのファイルで403が返ることを確認します。ZIPなどを利用者向けに配布しているサイトでは、その拡張子を拒否の対象から外してください。
やりがちな配置(危険)
- 連携用トークンのJSONを
public/に置く - ひな形に入っていた秘密ファイルをそのまま公開
- バックアップ(
.sql/.bak)や.gitを公開下に放置 - 1サイトだけ直して『対応完了』
正しい配置
- 公開ディレクトリには公開してよい物だけ
- 秘密はwebrootの外+権限600
- 機微な拡張子は配信を拒否(設定で deny)
- 1件見つけたら全台・全サイトを棚卸し
『公開して良い物だけ置く』を運用ルールに
個別の対処より、ルール化が効きます。公開ディレクトリには公開してよい物だけを置くと決め、秘密・バックアップ・バージョン管理データ(.git)・設定ファイルは原則そこに置かない。ひな形(テンプレート)を更新するときは、ひな形側から秘密を抜くようにします。そうしないと、次に量産するサイトにも同じ秘密ファイルがコピーされ続けます。
よくある間違いと直し方
- 拒否の設定だけで安心して、ファイルを残す:サーバーの移行や設定の書き換えで拒否が外れると、その日から公開されます。Nginxでは
.htaccessが読まれません。設定は保険と考え、ファイル自体を公開ディレクトリの外へ出します。 - 本番サーバーの上で直接ファイルを編集する:エディタが
~や.swpの付いたバックアップを作り、そのまま残ります。OWASPのテストガイドも、Webサーバー上でファイルをその場で編集しないよう勧めています。編集は手元で行い、デプロイで反映します。 - プロジェクトのフォルダを丸ごと公開ディレクトリへコピーする:
.env・.git・テスト用のデータまで一緒に出ていきます。配信するのはビルドの出力フォルダだけにし、コピーの対象を明示します。 - ドットで始まるパスを一律に拒否し、証明書の更新が失敗する:Let's Encryptの確認用ファイルは
/.well-known/acme-challenge/に置かれます。上のNginxの例のように、この場所だけ先に許可します。 - 秘密を
VITE_やNEXT_PUBLIC_の変数に入れる:ビルドしたJavaScriptに埋め込まれ、誰でも読めます。サーバー側の処理でだけ使い、過去にこの形で配布していたキーは失効させます。 - ファイルを消して終わりにする:検索エンジンに登録されていると、結果に残ることがあります。URLが404か410を返すことを確かめ、Google Search Consoleの「削除」ツールで一時的に非表示にします(約6か月)。そのあいだに再クロールで消えるのを待ちます。
実例:公開した報告書に、隠れた個人データが残っていた
米ワシントンDCでメディケイド(低所得者向けの公的医療保険)を運営する医療保険財政局(DHCF)は、同局のウェブサイトに2023年から2026年7月まで掲載していた統計報告書2件に、集計の元になった個人データが「隠しフィールド」として含まれていたと公表しました。画面には集計の数字しか表示されない作りでしたが、ファイルの中には受給者のメディケイド番号や生年月日などが残っていました。米保健福祉省(HHS)への届出では、対象は399,086人とされています。DHCFは2026年7月21日に把握し、報告書を削除しました。
これは秘密のファイルを置き忘れた例ではありませんが、根は同じです。
- 公開ディレクトリに置いたファイルは、画面に表示されている部分だけでなく、ファイルの中身ごと誰でも取得できる
- 載せた側が「見えていない」と思っている情報ほど、点検から漏れやすい
- 何年も掲載されたままになり、気づいたときには長期間公開されていた
公開する前に「このファイルの中に何が入っているか」を確かめる手順を持つことが、置き忘れと隠れたデータの両方を防ぎます。詳しい経緯は 米ワシントンDCのメディケイド情報露出 にまとめています。
当サイトはどうしているか
当サイトは、秘密(鍵・トークン・接続情報)を公開ディレクトリにもコードのリポジトリにも置かないことを基本にしています。デプロイ成果物に含めるのはビルド済みの公開アセットだけで、秘密は実行環境の環境変数として、配信されない場所に保持します。理由は、この記事の事故クラスが「1つの置き忘れが、URLを指定するだけで全部漏れる」典型だからです。置き場所そのものをアクセス制御として設計し、棚卸しは 脆弱性対応の棚卸し と同じタイミングで一緒に回しています。
次に読む
- 技術別:共用サーバーでの.env保護 / 脆弱性対応の実務(棚卸し)
- 用語:.envファイルと秘密情報 / パストラバーサル
- 事故:.envが丸ごと公開された話
- 保管:秘密の安全な保管
- クラウド側:クラウドストレージの公開設定ミス(同じ問題がサーバーの外で起きる)
- 事業者側の事故:レンタルサーバーが不正アクセスされたとき、利用者は何をすべきか(置いてあるものは読まれた前提で考える)
- 実例:公開報告書に残った受給者番号(米ワシントンDC)(公開したファイルの中身ごと見られた例)
出典
- IPA「安全なウェブサイトの運用管理に向けての20ヶ条」(公開すべきでないファイル、検索エンジンのキャッシュ): ipa.go.jp
- OWASP「Web Security Testing Guide:Review Old Backup and Unreferenced Files for Sensitive Information」(.old・~・.swp などのバックアップ、本番で直接編集しない): wstg.owasp.org
- Apache HTTP Server 2.4「Upgrading to 2.4 from 2.2」(
AllowOverrideの既定がNone)/「mod_autoindex」(Options +Indexesで一覧を生成): httpd.apache.org、httpd.apache.org - nginx「ngx_http_core_module:location」(照合の順序と
^~)/「ngx_http_autoindex_module」(autoindexの既定はoff): nginx.org、nginx.org - Let's Encrypt「Challenge Types」(HTTP-01 は
/.well-known/acme-challenge/を使う): letsencrypt.org - Vite「Env Variables and Modes」(
VITE_の変数はクライアントのコードに公開される): vite.dev - Next.js「Environment Variables」(
NEXT_PUBLIC_の変数はブラウザ向けのJavaScriptに埋め込まれる): nextjs.org - Google Search Console ヘルプ「削除ツール」(一時的な非表示は約6か月): support.google.com
- District of Columbia DHCF「Notice of Data Incident」: dhcf.dc.gov/HIPAA Journal(HHSへの届出人数): hipaajournal.com