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?

.envをGitHubに上げた瞬間、何が起きるのか

0
Posted at

はじめに

.env をうっかりGitHubにpushしてしまった。

「すぐ消したから大丈夫だろう」——その判断が、数時間後に数百万円の請求書として返ってくることがある。

これは仮定の話ではない。AWSのアクセスキーがGitHubのパブリックリポジトリに公開されると、平均4分以内にbotが検出するという実験結果がある。検出されたキーは即座に悪用され、暗号通貨マイニング用のEC2インスタンスが大量に起動される。翌朝AWSからの高額請求アラートで気づいたときには、もう手遅れだ。

本稿では、.envファイルがGitHubに上がった瞬間から何が起きるのかを時系列で追い、なぜ「すぐ消した」では手遅れなのかを解説する。そして、漏洩を防ぐ仕組みと、漏洩してしまった場合の緊急対応を整理する。

1. 漏洩の24時間 ― 何が起きるのか

タイムライン

00:00  エンジニアが .env を含むコミットをpush
00:01  GitHubのイベントストリームにpushイベントが流れる
00:02  世界中のスキャンbotがリアルタイムでイベントを監視している
00:04  botがAWSキーのパターン(AKIA...)を検出
00:05  検出されたキーで有効性チェック(aws sts get-caller-identity)
00:06  有効なキーと確認。攻撃開始
00:10  全リージョンでEC2インスタンスの起動を試みる
00:15  マイニング用のc5.4xlargeインスタンスが20台起動
00:30  エンジニアが気づいてGitHubからファイルを削除
00:31  しかしGitの履歴にキーが残っている。botはすでにキーを保存済み
01:00  起動されたインスタンスがフル稼働でマイニング中
06:00  AWSから異常利用のアラートメール(見てない。寝てる)
08:00  出社してメールを確認。$2,000の請求が発生している
08:05  慌ててキーを無効化。しかしS3バケットへのアクセスログを確認すると...
08:10  顧客データを含むバケットがすでにダウンロードされていた

このタイムラインは誇張ではない。セキュリティ研究者がGitHubにダミーのAWSキーを公開する実験(ハニーポット調査)を行った結果、最短で数分以内に不正利用が開始されることが確認されている。

なぜこんなに速いのか

GitHubにはEvents APIがあり、パブリックリポジトリのすべてのpushイベントをリアルタイムで取得できる。攻撃者はこのAPIを監視するbotを常時稼働させている。

攻撃botの動作:
  1. GitHub Events APIをポーリング(数秒間隔)
  2. PushEventからコミットの差分を取得
  3. 正規表現でシークレットのパターンを検出
  4. 検出したキーの有効性を自動検証
  5. 有効なキーを即座に悪用

botが探しているパターンの例:

AWSアクセスキー:    AKIA[0-9A-Z]{16}
AWSシークレットキー: [0-9a-zA-Z/+]{40}
GitHubトークン:     ghp_[0-9a-zA-Z]{36}
Stripeシークレット:  sk_live_[0-9a-zA-Z]{24,}
Slackトークン:      xoxb-[0-9]{11}-[0-9]{11}-[0-9a-zA-Z]{24}
データベースURL:     postgres://[^\s]+:[^\s]+@[^\s]+

パターンが単純で、grep一行で検出できてしまう。だからbotは数秒で見つける。

2. 「すぐ消した」が手遅れな理由

2.1 Gitの履歴は消えない

GitHubのWebインターフェースからファイルを削除しても、Gitの履歴にはコミットが残っている。

# ファイルを削除してコミット
git rm .env
git commit -m "remove .env"
git push

# しかし過去のコミットにはまだ .env が存在する
git log --all --full-history -- .env
# → 追加されたコミットが表示される

git show <commit-hash>:.env
# → ファイルの中身がそのまま見える

GitHubのWeb UIで「このファイルは削除されました」と表示されていても、コミット履歴をたどれば誰でも中身を読める。

2.2 キャッシュとフォーク

GitHubのコンテンツは複数の場所にキャッシュされる。

キーが残り続ける場所:
  - Gitのコミット履歴(git log で辿れる)
  - GitHubのイベントAPI(一定期間キャッシュされる)
  - 検索エンジンのキャッシュ(Google等にインデックスされる場合がある)
  - フォークしたリポジトリ(フォークには削除が反映されない)
  - 攻撃者のローカル(すでにダウンロード済み)

一度インターネットに公開されたシークレットは、「削除」ではなく「無効化」でしか対処できない。ファイルを消すのではなく、キーそのものをローテーションする必要がある。

2.3 プライベートリポジトリでも安全ではない

「うちはプライベートリポジトリだから大丈夫」——これも危険な思い込みだ。

プライベートリポジトリでもリスクがある場面:
  - 将来パブリックに変更する可能性
  - チームメンバーの退職(アクセス権の剥奪漏れ)
  - CIサービスの連携(ログにシークレットが出力される)
  - 依存するサービスの侵害(GitHub自体のセキュリティインシデント)
  - フォークの公開設定ミス

プライベートリポジトリは「今この瞬間は外部からアクセスできない」というだけで、「シークレットを置いていい場所」ではない。

3. 何が漏洩するとどうなるのか

漏洩するシークレットの種類によって、被害の内容と深刻度が変わる。

被害マップ

漏洩するもの 攻撃者ができること 被害の深刻度
AWSアクセスキー EC2起動(マイニング)、S3データ窃取、IAM権限昇格 極めて高い
データベース接続文字列 データの閲覧・改ざん・削除、バックドアの設置 極めて高い
Stripe/決済キー 不正な返金処理、顧客の決済情報へのアクセス 極めて高い
GitHubトークン リポジトリの改ざん、プライベートコードの窃取、サプライチェーン攻撃 高い
Slackトークン メッセージの閲覧、なりすまし投稿、内部情報の収集 中〜高い
メールAPIキー スパム送信、フィッシングメール、ドメインの信用棄損 中〜高い
Firebase設定 ユーザーデータへのアクセス(Rulesの設定次第) 設定依存
JWTシークレット 任意のユーザーになりすましたトークンの生成 極めて高い

AWS漏洩の場合の具体的な被害額

AWSの料金体系から計算すると:

c5.4xlarge(マイニングに使われやすいインスタンス):
  オンデマンド料金: 約 $0.68/時間

攻撃者が全リージョン(20以上)で各10台起動した場合:
  $0.68 × 200台 × 24時間 = $3,264/日

週末に気づかなかった場合(48時間):
  $3,264 × 2 = $6,528(約100万円)

さらにデータ転送料、EBSボリューム料金が加算される

AWSは不正利用に対する請求を免除してくれることもあるが、保証はされていない。AWSの公式ドキュメントでは、認証情報の管理はユーザーの責任と明記されている。

4. 漏洩を防ぐ仕組み ― 4つのレイヤー

個人の注意力に頼るのは愚策だ。仕組みで防ぐ。

レイヤー1: .gitignoreを正しく設定する(基本中の基本)

# .gitignore(プロジェクトのルートに必ず置く)
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
.env*.local

# その他のシークレットファイル
*.pem
*.key
credentials.json
serviceAccountKey.json

.gitignoreは「最初のコミットの前に」設定する。一度コミットしたファイルは、後から.gitignoreに追加しても履歴から消えない。

ただし、.gitignoreだけでは不十分だ。git add -Agit add . で意図せず追加してしまう、.gitignoreの設定漏れ、一時的に.envを外してテストした後に戻し忘れる、といったヒューマンエラーは必ず起きる。

レイヤー2: コミット前に検出する(pre-commitフック)

コミットの瞬間にシークレットを検出してブロックする。

git-secrets(AWS公式ツール)

# インストール
brew install git-secrets

# リポジトリに設定
cd your-project
git secrets --install
git secrets --register-aws

# AWSキーのパターンが自動登録される
# 以降、AWSキーを含むコミットはブロックされる
# 動作例
$ echo "AKIAIOSFODNN7EXAMPLE" >> config.txt
$ git add config.txt
$ git commit -m "add config"

[ERROR] Matched one or more prohibited patterns
Possible mitigations:
- Mark false positives as allowed using: git config --add secrets.allowed ...

secretlint(より汎用的なツール)

# インストール
npm install -D @secretlint/secretlint-rule-preset-recommend secretlint

# .secretlintrc.json
{
  "rules": [
    {
      "id": "@secretlint/secretlint-rule-preset-recommend"
    }
  ]
}
// package.json  lint-staged に追加
{
  "lint-staged": {
    "*": ["secretlint"]
  }
}

lefthook で pre-commit を設定

# lefthook.yml
pre-commit:
  commands:
    secretlint:
      glob: "*"
      run: npx secretlint {staged_files}
    gitSecrets:
      run: git secrets --pre_commit_hook -- "$@"

レイヤー3: プッシュ後に検出する(GitHub側の防御)

GitHub Secret Scanning

GitHubにはSecret Scanning機能があり、パブリックリポジトリ(および GitHub Advanced Security を有効にしたプライベートリポジトリ)で自動的にシークレットを検出する。

対応しているパターンは200以上。AWS、GCP、Azure、Stripe、Slack、Twilio、npm、PyPIなど主要なサービスのキーを検出する。

さらに、Push Protectionを有効にすると、シークレットを含むpush自体をブロックできる。

Push Protectionの動作:
  $ git push origin main

  ⚠ Secret scanning found the following secrets:
  ── AWS Access Key ID ──
  Location: .env:3
  Secret type: Amazon AWS Access Key ID

  To push, either remove the secret or use:
    git push --no-verify  (非推奨)
# リポジトリの設定で有効化
# Settings → Code security and analysis → Secret scanning → Push protection → Enable

GitHub Advanced Securityが使えない場合

パブリックリポジトリではSecret Scanningは無料で使えるが、プライベートリポジトリではGitHub Advanced Security(有料)が必要。代替としてCIパイプラインにスキャンを組み込む。

# .github/workflows/secret-scan.yml
name: Secret Scan
on: [push, pull_request]

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # 全履歴をチェック
      - name: TruffleHog scan
        uses: trufflesecurity/trufflehog@main
        with:
          extra_args: --only-verified

レイヤー4: シークレットをコードに含めない設計

そもそもシークレットが.envファイルに平文で存在する状態を避ける。

段階的なシークレット管理の成熟度:

Level 0: コードにハードコード(最悪)
  const API_KEY = "sk_live_xxxxx";

Level 1: .envファイルに分離(最低限)
  process.env.API_KEY

Level 2: CIの環境変数 / Vercelの環境変数設定(一般的)
  Vercel Dashboard → Settings → Environment Variables

Level 3: シークレットマネージャー(推奨)
  AWS Secrets Manager / Google Secret Manager / 1Password CLI

Level 4: 短命な認証情報(理想)
  IAM Roles / OIDC Federation / Workload Identity
  → シークレット自体が存在しない

特にLevel 4は重要だ。GitHub ActionsからAWSにアクセスする場合、OIDCフェデレーションを使えばAWSキーをGitHubに保存する必要がなくなる。

# .github/workflows/deploy.yml
# AWSキーを使わない安全な認証
jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write   # OIDC用
      contents: read
    steps:
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-actions-role
          aws-region: ap-northeast-1
      # これ以降、一時認証情報が自動で使われる
      # アクセスキーは存在しないので漏洩しようがない

5. 漏洩してしまった場合の緊急対応

防いでいても起きるときは起きる。そのとき最初の30分で何をするかが被害の大きさを決める。

緊急対応フロー

Step 1: キーを無効化する(最優先。1分以内)
  → AWSコンソールでアクセスキーを無効化/削除
  → GitHubトークンを revoke
  → DBのパスワードを変更
  → 該当するシークレットをすべてローテーション

Step 2: 影響範囲を調査する(30分以内)
  → CloudTrail / アクセスログで不正なAPIコールを確認
  → 不審なリソース(EC2、Lambda等)が起動されていないか確認
  → S3バケットへの不正アクセスがないか確認

Step 3: 不正なリソースを削除する(1時間以内)
  → 攻撃者が起動したインスタンスを全リージョンで確認・停止
  → 作成されたIAMユーザー/ロールを確認・削除
  → セキュリティグループの変更がないか確認

Step 4: Git履歴からシークレットを除去する
  → git filter-branch または BFG Repo-Cleaner で履歴を書き換え
  → force push(チームに事前通知が必要)

Step 5: 報告と再発防止
  → チームへの共有
  → pre-commitフックの導入(未導入の場合)
  → push protectionの有効化

AWSキー漏洩の場合の具体的な手順

# 1. 漏洩したキーを無効化
aws iam update-access-key \
  --user-name compromised-user \
  --access-key-id AKIAXXXXXXXXXXXXXXXX \
  --status Inactive

# 2. キーを削除
aws iam delete-access-key \
  --user-name compromised-user \
  --access-key-id AKIAXXXXXXXXXXXXXXXX

# 3. 全リージョンで不審なEC2を確認
for region in $(aws ec2 describe-regions --query 'Regions[].RegionName' --output text); do
  echo "=== $region ==="
  aws ec2 describe-instances \
    --region $region \
    --filters "Name=instance-state-name,Values=running" \
    --query 'Reservations[].Instances[].{ID:InstanceId,Type:InstanceType,Launch:LaunchTime}' \
    --output table
done

# 4. CloudTrailで不正なAPIコールを確認
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=AccessKeyId,AttributeValue=AKIAXXXXXXXXXXXXXXXX \
  --max-results 50

Git履歴からの除去

# BFG Repo-Cleaner を使う方法(git filter-branchより高速で安全)
# https://rtyley.github.io/bfg-repo-cleaner/

# .envファイルを履歴から完全に削除
java -jar bfg.jar --delete-files .env

# 特定の文字列(キー)を履歴から置換
echo "AKIAIOSFODNN7EXAMPLE" >> passwords.txt
java -jar bfg.jar --replace-text passwords.txt

# クリーンアップとforce push
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push --force

force pushはチームの作業に影響する。必ず事前に通知し、全員がローカルリポジトリを更新するよう共有すること。

6. チェックリスト

プロジェクト開始時

□ .gitignoreに.env系ファイルが含まれている
□ .env.exampleを用意し、実際の値は含めていない
□ git-secrets または secretlint の pre-commitフックを設定した
□ README等にシークレットの管理方法を記載した

CI/CD設定

□ シークレットはCI/CDプラットフォームの環境変数機能で管理している
□ CIのログにシークレットが出力されないことを確認した
□ GitHub Secret Scanning / Push Protection を有効にした
□ TruffleHog等のスキャンをCIパイプラインに組み込んだ
□ 可能であればOIDCフェデレーションを使い、長期キーを排除した

定期チェック

□ 使われていないアクセスキーを定期的に棚卸ししている
□ キーのローテーションスケジュールを設定している
□ CloudTrail / 監査ログを定期的に確認している
□ チームの新メンバーにシークレット管理のルールを共有している

7. おわりに ― シークレットは「漏洩するもの」として設計する

.envをGitHubに上げてしまうのは、エンジニアなら誰にでも起こりうるミスだ。問題は、そのミスが数分で実害に変わるインターネットの速度と、「すぐ消せば大丈夫」という誤った安心感の組み合わせにある。

防御の考え方は多層で組み立てる。

  1. そもそもシークレットをファイルに置かない(OIDC、シークレットマネージャー)
  2. 置いてもコミットできない(.gitignore、pre-commitフック)
  3. コミットしてもpushできない(Push Protection)
  4. pushしても即座に検出される(Secret Scanning、CIスキャン)
  5. 漏洩しても被害を最小化する(最小権限、キーのローテーション、短命な認証情報)

そしてこれらすべてを突破されたときのために、緊急対応の手順を事前に用意しておく。

完璧な防御は存在しない。だからこそ、シークレットは「漏洩するもの」として設計する。漏洩したときに何が起きるかを想定し、被害を最小限に抑える仕組みを先に作っておく。それがシークレット管理の本質だ。

次に git add . を実行する前に、一呼吸置いてほしい。その1秒が、数百万円の請求書を防ぐかもしれない。

参考文献

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?