supabase-audit-authenticated について
認証ユーザーとして Supabase のアクセスを検査するセキュリティ監査スキルです。テストユーザーを作成し、認証時と匿名時のアクセスを比較して、IDOR、他ユーザーのデータ閲覧、権限昇格を調べます。実行には明示的な同意が必要です。
主な機能
- 01同意に基づくテストユーザー作成
- 02認証時と匿名時のアクセス比較
- 03IDOR と他ユーザーへのアクセス検査
- 04Storage と Realtime のアクセス検査
- 05監査ログと証拠の逐次保存
活用シーン
- 01RLS が自分のデータだけを許可するか確認
- 02Supabase のテーブルで IDOR を調査
- 03認証時と匿名時のアクセス結果を比較
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ yoanbernabeu/ supabase-pentest-skills --skill supabase-audit-authenticatedインストール後、Claude Code で/supabase-audit-authenticatedを入力して読み込まれたか確認します。
詳細
認証済みユーザーの監査
🔴 重大:ファイルを段階的に更新する必要があります
コンテキストファイルには、最後だけではなく、作業を進めながら必ず書き込んでください。
- テストを実施するたびに、直ちに
.sb-pentest-context.jsonに書き込んでください- 各アクションの前後に
.sb-pentest-audit.logに記録してください- スキルの完了までファイルの更新を待ってはいけません
- スキルがクラッシュまたは中断された場合でも、それまでの調査結果がすべて保存されている必要があります
これは任意ではありません。段階的に書き込まないことは重大なエラーです。
このスキルでは、明示的な許可を得てテストユーザーを作成し、認証済みの場合と匿名の場合のアクセスを比較して、IDOR 脆弱性を検出します。
⚠️ 重要:ユーザーの同意が必要です
╔═══════════════════════════════════════════════════════════════════╗
║ 🔐 USER CREATION CONSENT REQUIRED ║
╠═══════════════════════════════════════════════════════════════════╣
║ ║
║ This skill will CREATE A TEST USER in your Supabase project. ║
║ ║
║ The user will be created with: ║
║ • Email: pentest-[random]@security-audit.local ║
║ • Password: Strong random password (32+ chars) ║
║ • Purpose: Testing authenticated access vs anonymous ║
║ ║
║ At the end of the audit, you will be asked if you want to ║
║ DELETE the test user (recommended). ║
║ ║
║ Do you authorize the creation of a test user? ║
║ Type "yes, create test user" to proceed. ║
║ ║
╚═══════════════════════════════════════════════════════════════════╝
ユーザーの明示的な同意なしに進めてはいけません。
このスキルを使用するタイミング
- 匿名アクセスのテストを完了した後
- IDOR(安全でない直接オブジェクト参照)脆弱性を検出する場合
- ユーザー間のデータアクセスをテストする場合
- 認証済みユーザーに対して RLS ポリシーが機能することを検証する場合
- 権限昇格の問題を見つける場合
前提条件
- サインアップが可能であること(または招待フローを使用すること)
- Anon キーが利用可能であること
- 匿名アクセスの監査が完了していること(推奨)
認証済みテストが重要な理由
多くの脆弱性は、認証後にのみ現れます。
| 脆弱性 | 匿名 | 認証済み |
|---|---|---|
| RLS のバイパス(RLS なし) | ✓ 検出可能 | ✓ 検出可能 |
| IDOR | ✗ 確認不可 | ✓ 認証時のみ確認可能 |
| ユーザー間アクセス | ✗ 確認不可 | ✓ 認証時のみ確認可能 |
| 権限昇格 | ✗ 確認不可 | ✓ 認証時のみ確認可能 |
| 過度に許容的な RLS | 一部 | ✓ 完全に検出可能 |
テストユーザーの作成
メールアドレス形式
pentest-[8-char-random]@security-audit.local
パスワードの生成
以下を満たす強力なパスワードを使用します。
- 32 文字以上
- 大文字、小文字、数字、記号を含む
- 暗号論的にランダム
例:Xk9$mP2#vL5@nQ8&jR4*wY7!hT3%bU6^
パスワードは一度だけ表示され、証跡に保存されます。
実施するテスト
1. ユーザーの作成とログイン
# Create user
curl -X POST "$SUPABASE_URL/auth/v1/signup" \
-H "apikey: $ANON_KEY" \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "password": "[STRONG_PASSWORD]"}'
# Login and get JWT
curl -X POST "$SUPABASE_URL/auth/v1/token?grant_type=password" \
-H "apikey: $ANON_KEY" \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "password": "[STRONG_PASSWORD]"}'
2. 認証済みと匿名の比較
各テーブルについて、次のテストを実施します。
| テスト | 匿名 | 認証済み | 検出結果 |
|---|---|---|---|
| SELECT | 0 行 | 1,247 行 | 🔴 認証時のみデータが露出 |
| 自分のデータ | 該当なし | 自分の行のみ | ✅ RLS が機能 |
| 他のユーザーのデータ | 該当なし | すべての行 | 🔴 ユーザー間アクセス |
3. IDOR テスト
# As test user, try to access other user's data
curl "$SUPABASE_URL/rest/v1/orders?user_id=eq.[OTHER_USER_ID]" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer [TEST_USER_JWT]"
# If returns data: IDOR vulnerability!
4. ユーザー間アクセス
# Get test user's ID from JWT
TEST_USER_ID=$(echo $JWT | jq -r '.sub')
# Try to access data belonging to a different user
curl "$SUPABASE_URL/rest/v1/profiles?id=neq.$TEST_USER_ID" \
-H "Authorization: Bearer [TEST_USER_JWT]"
# If returns other users' profiles: Cross-user access!
5. 認証済みでのストレージ
# Test authenticated storage access
curl "$SUPABASE_URL/storage/v1/object/list/documents" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer [TEST_USER_JWT]"
# Compare with anonymous results
6. 認証済みでのリアルタイム
// Subscribe to table changes as authenticated user
const channel = supabase.channel('test')
.on('postgres_changes', {
event: '*',
schema: 'public',
table: 'orders'
}, payload => console.log(payload))
.subscribe()
// Does it receive OTHER users' order changes?
出力形式
═══════════════════════════════════════════════════════════
AUTHENTICATED USER AUDIT
═══════════════════════════════════════════════════════════
─────────────────────────────────────────────────────────
Test User Creation
─────────────────────────────────────────────────────────
Status: ✅ User created successfully
Test User Details:
├── Email: [email protected]
├── User ID: 550e8400-e29b-41d4-a716-446655440099
├── Password: [Saved to evidence - shown once]
└── JWT obtained: ✅
─────────────────────────────────────────────────────────
Anonymous vs Authenticated Comparison
─────────────────────────────────────────────────────────
Table: users
├── Anonymous access: 0 rows
├── Authenticated access: 1,247 rows ← ALL USERS!
└── Status: 🔴 P0 - Data hidden from anon but exposed to any auth user
Table: orders
├── Anonymous access: 0 rows (blocked)
├── Authenticated access: 1 row (own orders only)
└── Status: ✅ RLS working correctly
Table: profiles
├── Anonymous access: 0 rows
├── Authenticated access: 1,247 rows ← ALL PROFILES!
├── Own profile only expected: ❌ NO
└── Status: 🔴 P0 - Cross-user profile access
─────────────────────────────────────────────────────────
IDOR Testing
─────────────────────────────────────────────────────────
Test: Access other user's orders by ID
├── Request: GET /orders?user_id=eq.[other-user-id]
├── Auth: Test user JWT
├── Response: 200 OK - 15 orders returned
└── Status: 🔴 P0 - IDOR VULNERABILITY
Proof:
curl "$URL/rest/v1/orders?user_id=eq.other-user-uuid" \
-H "Authorization: Bearer [test-user-jwt]"
# Returns orders belonging to other-user-uuid!
Test: Access admin endpoints
├── Request: GET /functions/v1/admin-panel
├── Auth: Test user JWT (regular user)
├── Response: 200 OK - Admin data returned!
└── Status: 🔴 P0 - PRIVILEGE ESCALATION
─────────────────────────────────────────────────────────
Storage with Authentication
─────────────────────────────────────────────────────────
Bucket: documents
├── Anonymous: ❌ 0 files (blocked)
├── Authenticated: ✅ 523 files visible ← ALL USERS' FILES!
└── Status: 🔴 P1 - Auth users see all documents
Bucket: user-uploads
├── Anonymous: ❌ 0 files
├── Authenticated: 3 files (own files only)
└── Status: ✅ RLS working correctly
─────────────────────────────────────────────────────────
Summary
─────────────────────────────────────────────────────────
New Findings (Auth-only):
├── 🔴 P0: users table - all users visible to any auth user
├── 🔴 P0: profiles table - cross-user access
├── 🔴 P0: IDOR in orders - can access any user's orders
├── 🔴 P0: Privilege escalation in admin-panel
└── 🟠 P1: documents bucket - all files visible to auth users
Comparison:
├── Issues found (Anonymous): 3
├── Issues found (Authenticated): 8 ← 5 NEW ISSUES!
└── Auth-only vulnerabilities: 5
Recommendation:
These issues were NOT visible in anonymous testing!
Always test with authenticated users.
─────────────────────────────────────────────────────────
Cleanup
─────────────────────────────────────────────────────────
⚠️ Test user still exists in database.
Do you want to delete the test user?
Email: [email protected]
[This requires service_role key or manual deletion]
═══════════════════════════════════════════════════════════
コンテキスト出力
{
"authenticated_audit": {
"timestamp": "2025-01-31T12:00:00Z",
"test_user": {
"email": "[email protected]",
"user_id": "550e8400-e29b-41d4-a716-446655440099",
"created_at": "2025-01-31T12:00:00Z",
"deleted": false
},
"comparison": {
"tables": {
"users": {
"anon_access": 0,
"auth_access": 1247,
"expected_auth_access": "own_row_only",
"severity": "P0",
"finding": "All users visible to any authenticated user"
},
"orders": {
"anon_access": 0,
"auth_access": 1,
"expected_auth_access": "own_rows_only",
"severity": null,
"finding": "RLS working correctly"
}
},
"idor_tests": [
{
"test": "access_other_user_orders",
"vulnerable": true,
"severity": "P0",
"proof": "curl command..."
}
],
"privilege_escalation": [
{
"endpoint": "/functions/v1/admin-panel",
"vulnerable": true,
"severity": "P0"
}
]
},
"summary": {
"anon_issues": 3,
"auth_issues": 8,
"auth_only_issues": 5
}
}
}
RLS ポリシーの例
正しい例:ユーザーが自分のデータのみを表示する
-- This RLS policy is correct
CREATE POLICY "Users see own data"
ON users FOR SELECT
USING (auth.uid() = id);
-- Result:
-- Anonymous: 0 rows
-- Authenticated: 1 row (own data)
誤った例:すべての認証済みユーザーがすべてのデータを表示できる
-- This RLS policy is WRONG
CREATE POLICY "Authenticated users see all"
ON users FOR SELECT
USING (auth.role() = 'authenticated'); -- ❌ Too permissive!
-- Result:
-- Anonymous: 0 rows
-- Authenticated: ALL rows ← VULNERABILITY!
正しい修正:
-- Fix: Add user ownership check
CREATE POLICY "Users see own data"
ON users FOR SELECT
USING (auth.uid() = id); -- ✅ Only own row
クリーンアップの方法
方法 1:手動で削除する(ダッシュボード)
Supabase Dashboard → Authentication → Users → Find test user → Delete
方法 2:service_role キーを使用する(利用可能な場合)
curl -X DELETE "$SUPABASE_URL/auth/v1/admin/users/[USER_ID]" \
-H "apikey: $SERVICE_ROLE_KEY" \
-H "Authorization: Bearer $SERVICE_ROLE_KEY"
方法 3:後で対応する
テストユーザーには機能しないメールドメイン(security-audit.local)を使用しているため、悪用することはできません。
必須:証跡の収集
📁 証跡ディレクトリ: .sb-pentest-evidence/05-auth-audit/authenticated-tests/
作成する証跡ファイル
| ファイル | 内容 |
|---|---|
test-user-created.json | テストユーザーの詳細(パスワードを安全に保存) |
anon-vs-auth-comparison.json | 匿名と認証済みの比較 |
idor-tests/[table].json | IDOR テストの結果 |
privilege-escalation.json | 権限昇格テスト |
証跡の形式
{
"evidence_id": "AUTH-TEST-001",
"timestamp": "2025-01-31T12:00:00Z",
"category": "auth-audit",
"type": "authenticated_testing",
"test_user": {
"email": "[email protected]",
"user_id": "550e8400-...",
"password": "[STORED SECURELY - DO NOT COMMIT]"
},
"comparison_test": {
"table": "users",
"anonymous": {
"curl_command": "curl '$URL/rest/v1/users' -H 'apikey: $ANON_KEY'",
"response_status": 200,
"rows_returned": 0
},
"authenticated": {
"curl_command": "curl '$URL/rest/v1/users' -H 'apikey: $ANON_KEY' -H 'Authorization: Bearer $JWT'",
"response_status": 200,
"rows_returned": 1247
},
"finding": {
"severity": "P0",
"issue": "All users visible to any authenticated user",
"expected": "Only own row should be visible",
"impact": "Full user enumeration for any authenticated user"
}
}
}
curl-commands.sh に追加
# === AUTHENTICATED TESTING ===
# NOTE: Replace [JWT] with test user's JWT
# Compare anonymous vs authenticated access
curl -s "$SUPABASE_URL/rest/v1/users?select=*&limit=5" -H "apikey: $ANON_KEY"
curl -s "$SUPABASE_URL/rest/v1/users?select=*&limit=5" -H "apikey: $ANON_KEY" -H "Authorization: Bearer [JWT]"
# IDOR test - access other user's data
curl -s "$SUPABASE_URL/rest/v1/orders?user_id=eq.[OTHER_USER_ID]" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer [JWT]"
# Cross-user profile access
curl -s "$SUPABASE_URL/rest/v1/profiles?id=neq.[TEST_USER_ID]" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer [JWT]"
必須:コンテキストファイルの段階的な更新
⚠️ このスキルでは、実行中に追跡ファイルを段階的に更新する必要があります。最後にまとめて更新してはいけません。
重要なルール:作業中に書き込む
最後にまとめてすべてを書き込まないでください。 代わりに、次の手順に従ってください。
- ユーザー作成前 → 同意と操作を
.sb-pentest-audit.logに記録します - ユーザー作成後 → ユーザーの詳細を直ちにコンテキストと証拠に保存します
- 各比較テストの後 → 結果を
.sb-pentest-context.jsonに更新します - 各 IDOR テストの後 → 証拠を直ちに保存します
これにより、スキルが中断されたり、クラッシュしたり、タイムアウトしたりしても、その時点までのすべての調査結果が保持されます。
必須アクション(段階的に実行)
-
ユーザー作成を記録する:
[TIMESTAMP] [supabase-audit-authenticated] [CONSENT] User authorized test user creation [TIMESTAMP] [supabase-audit-authenticated] [CREATED] Test user [email protected] -
テストユーザーを直ちにコンテキストに保存する:
{ "authenticated_audit": { "test_user": { "email": "...", "user_id": "...", "created_at": "..." } } } -
調査結果が判明するたびに記録する:
[TIMESTAMP] [supabase-audit-authenticated] [FINDING] P0: IDOR in orders table
コンテキストファイルを段階的に更新しないことは許容されません。
関連スキル
supabase-audit-auth-signup— まずサインアップが開放されているかテストしますsupabase-audit-tables-read— 匿名ユーザーの結果と比較しますsupabase-audit-rls— RLS ポリシーを詳しく調査しますsupabase-audit-functions— 認証済みユーザーで関数へのアクセスをテストしますsupabase-report— 認証済みユーザーのみが確認できる調査結果をレポートに含めます