mcpField
S
supabase-audit-rpc

supabase-audit-rpc

github.com/yoanbernabeuセキュリティ71533 回インストール39 週間前に更新
リポジトリ

supabase-audit-rpc について

Supabase の RPC 関数を検出し、アクセス制御やパラメーターを確認するセキュリティ監査スキルです。調査結果を逐次記録し、API セキュリティレビュー用の証拠を収集します。

主な機能

  1. 01公開 RPC 関数の検出
  2. 02RLS 回避リスクの確認
  3. 03テキスト引数の SQL インジェクション検査
  4. 04調査結果の逐次記録とコンテキスト更新
  5. 05脆弱性の証拠収集

活用シーン

  1. 01Supabase プロジェクトの公開 RPC 関数の監査
  2. 02SECURITY DEFINER 関数の認可確認
  3. 03テキスト引数での危険な動的 SQL の確認

インストール

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

$npx skills add https://github.com/yoanbernabeu/supabase-pentest-skills --skill supabase-audit-rpc

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

詳細

RPC 関数の監査

🔴 重大:コンテキストファイルの段階的な更新が必須です

作業を進めながらコンテキストファイルに書き込んでください。最後にまとめて書き込むだけではいけません。

  • 関数をテストするたびに、直ちに .sb-pentest-context.json に書き込んでください
  • 関数テストの前後に .sb-pentest-audit.log に記録してください
  • スキルの完了までファイルの更新を待たないでください
  • スキルがクラッシュまたは中断された場合でも、これまでの調査結果がすべて保存されている必要があります

これは任意ではありません。段階的な書き込みを怠ることは重大なエラーです。

このスキルでは、Supabase の RPC エンドポイント経由で公開されている PostgreSQL 関数を検出し、テストします。

このスキルを使用する場面

  • 公開されているデータベース関数を検出する場合
  • 関数が RLS をバイパスするかテストする場合
  • 関数のパラメーターに SQL インジェクションがないか確認する場合
  • 包括的な API セキュリティテストの一環として

前提条件

  • Supabase URL と anon キーを利用できること
  • テーブルの監査が完了していること(推奨)

Supabase RPC について

Supabase は以下を介して PostgreSQL 関数を公開します。

POST https://[project].supabase.co/rest/v1/rpc/[function_name]

関数は次のように動作します。

  • ✅ auth.uid() と適切なセキュリティを使用していれば、RLS を尊重する
  • ❌ チェックを行わずに SECURITY DEFINER を使用している場合、RLS をバイパスする
  • ❌ 実装が不適切な場合、任意の SQL を実行する

関数のリスクレベル

種類リスク説明
SECURITY INVOKER低い呼び出し元の権限で実行される
SECURITY DEFINER高い定義者の権限で実行される
text/json を受け付ける高いインジェクションの可能性がある
setof を返す高い複数の行を返す可能性がある

使用方法

基本的な RPC 監査
Audit RPC functions on my Supabase project
特定の関数をテストする
Test the get_user_data RPC function

出力形式

═══════════════════════════════════════════════════════════
 RPC FUNCTIONS AUDIT
═══════════════════════════════════════════════════════════

 Project: abc123def.supabase.co
 Functions Found: 6

 ─────────────────────────────────────────────────────────
 Function Inventory
 ─────────────────────────────────────────────────────────

 1. get_user_profile(user_id uuid)
    Security: INVOKER
    Returns: json
    Status: ✅ SAFE

    Analysis:
    ├── Uses auth.uid() for authorization
    ├── Returns only caller's own profile
    └── RLS is respected

 2. search_posts(query text)
    Security: INVOKER
    Returns: setof posts
    Status: ✅ SAFE

    Analysis:
    ├── Parameterized query (no injection)
    ├── RLS filters results
    └── Only returns published posts

 3. get_all_users()
    Security: DEFINER
    Returns: setof users
    Status: 🔴 P0 - RLS BYPASS

    Analysis:
    ├── SECURITY DEFINER runs as owner
    ├── No auth.uid() check inside function
    ├── Returns ALL users regardless of caller
    └── Bypasses RLS completely!

    Test Result:
    POST /rest/v1/rpc/get_all_users
    → Returns 1,247 user records with PII

    Immediate Fix:
    ```sql
    -- Add authorization check
    CREATE OR REPLACE FUNCTION get_all_users()
    RETURNS setof users
    LANGUAGE sql
    SECURITY INVOKER  -- Change to INVOKER
    AS $$
      SELECT * FROM users
      WHERE auth.uid() = id;  -- Add RLS-like check
    $$;
    ```

 4. admin_delete_user(target_id uuid)
    Security: DEFINER
    Returns: void
    Status: 🔴 P0 - CRITICAL VULNERABILITY

    Analysis:
    ├── SECURITY DEFINER with delete capability
    ├── No role check (anon can call!)
    ├── Can delete any user
    └── No audit trail

    Test Result:
    POST /rest/v1/rpc/admin_delete_user
    Body: {"target_id": "any-uuid"}
    → Function accessible to anon!

    Immediate Fix:
    ```sql
    CREATE OR REPLACE FUNCTION admin_delete_user(target_id uuid)
    RETURNS void
    LANGUAGE plpgsql
    SECURITY DEFINER
    AS $$
    BEGIN
      -- Add role check
      IF NOT (SELECT is_admin FROM profiles WHERE id = auth.uid()) THEN
        RAISE EXCEPTION 'Unauthorized';
      END IF;

      DELETE FROM users WHERE id = target_id;
    END;
    $$;

    -- Or better: restrict to authenticated only
    REVOKE EXECUTE ON FUNCTION admin_delete_user FROM anon;
    ```

 5. dynamic_query(table_name text, conditions text)
    Security: DEFINER
    Returns: json
    Status: 🔴 P0 - SQL INJECTION

    Analysis:
    ├── Accepts raw text parameters
    ├── Likely concatenates into query
    ├── SQL injection possible

    Test Result:
    POST /rest/v1/rpc/dynamic_query
    Body: {"table_name": "users; DROP TABLE users;--", "conditions": "1=1"}
    → Injection vector confirmed!

    Immediate Action:
    → DELETE THIS FUNCTION IMMEDIATELY

    ```sql
    DROP FUNCTION IF EXISTS dynamic_query;
    ```

    Never build queries from user input. Use parameterized queries.

 6. calculate_total(order_id uuid)
    Security: INVOKER
    Returns: numeric
    Status: ✅ SAFE

    Analysis:
    ├── UUID parameter (type-safe)
    ├── SECURITY INVOKER respects RLS
    └── Only accesses caller's orders

 ─────────────────────────────────────────────────────────
 Summary
 ─────────────────────────────────────────────────────────

 Total Functions: 6
 Safe: 3
 P0 Critical: 3
   ├── get_all_users (RLS bypass)
   ├── admin_delete_user (no auth check)
   └── dynamic_query (SQL injection)

 Priority Actions:
 1. DELETE dynamic_query function immediately
 2. Add auth checks to admin_delete_user
 3. Fix get_all_users to respect RLS

═══════════════════════════════════════════════════════════

インジェクションテスト

このスキルでは、text/varchar パラメーターを対象に SQL インジェクションをテストします。

安全(パラメーター化)
-- ✅ Safe: uses parameter placeholder
CREATE FUNCTION search_posts(query text)
RETURNS setof posts
AS $$
  SELECT * FROM posts WHERE title ILIKE '%' || query || '%';
$$ LANGUAGE sql;
脆弱(連結)
-- ❌ Vulnerable: dynamic SQL execution
CREATE FUNCTION dynamic_query(tbl text, cond text)
RETURNS json
AS $$
DECLARE result json;
BEGIN
  EXECUTE format('SELECT json_agg(t) FROM %I t WHERE %s', tbl, cond)
  INTO result;
  RETURN result;
END;
$$ LANGUAGE plpgsql;

コンテキスト出力

{
  "rpc_audit": {
    "timestamp": "2025-01-31T11:00:00Z",
    "functions_found": 6,
    "summary": {
      "safe": 3,
      "p0_critical": 3,
      "p1_high": 0
    },
    "findings": [
      {
        "function": "get_all_users",
        "severity": "P0",
        "issue": "RLS bypass via SECURITY DEFINER",
        "impact": "All user data accessible",
        "remediation": "Change to SECURITY INVOKER or add auth checks"
      },
      {
        "function": "dynamic_query",
        "severity": "P0",
        "issue": "SQL injection vulnerability",
        "impact": "Arbitrary SQL execution possible",
        "remediation": "Delete function, use parameterized queries"
      }
    ]
  }
}

RPC 関数のベストプラクティス

1. SECURITY INVOKER を優先する
CREATE FUNCTION my_function()
RETURNS ...
SECURITY INVOKER  -- Respects RLS
AS $$ ... $$;
2. 必ず auth.uid() を確認する
CREATE FUNCTION get_my_data()
RETURNS json
AS $$
  SELECT json_agg(d) FROM data d
  WHERE d.user_id = auth.uid();  -- Always filter by caller
$$ LANGUAGE sql SECURITY INVOKER;
3. 機密性の高い関数には REVOKE を使用する
-- Remove anon access
REVOKE EXECUTE ON FUNCTION admin_function FROM anon;

-- Only authenticated users
GRANT EXECUTE ON FUNCTION admin_function TO authenticated;
4. 動的クエリにはテキストパラメーターを使用しない
-- ❌ Bad
CREATE FUNCTION query(tbl text) ...

-- ✅ Good: use specific functions per table
CREATE FUNCTION get_users() ...
CREATE FUNCTION get_posts() ...

必須:コンテキストファイルの段階的な更新

⚠️ このスキルでは、実行中に追跡ファイルを段階的に更新する必要があります。最後にまとめて更新するだけではいけません。

重要なルール:作業を進めながら書き込む

最後にまとめて書き込むのはやめてください。代わりに、以下を行ってください。

  1. 各関数のテスト前 → .sb-pentest-audit.log に作業内容を記録する
  2. 関数の分析後 → 直ちに .sb-pentest-context.json を更新する
  3. 脆弱性を発見した後 → 直ちにその内容を記録する

これにより、スキルが中断、クラッシュ、またはタイムアウトした場合でも、それまでの調査結果がすべて保持されます。

必須の作業(段階的な更新)
  1. 結果を .sb-pentest-context.json に更新する:

    {
      "rpc_audit": {
        "timestamp": "...",
        "functions_found": 6,
        "summary": { "safe": 3, "p0_critical": 3 },
        "findings": [ ... ]
      }
    }
    
  2. .sb-pentest-audit.log に記録する:

    [TIMESTAMP] [supabase-audit-rpc] [START] Auditing RPC functions
    [TIMESTAMP] [supabase-audit-rpc] [FINDING] P0: dynamic_query has SQL injection
    [TIMESTAMP] [supabase-audit-rpc] [CONTEXT_UPDATED] .sb-pentest-context.json updated
    
  3. ファイルが存在しない場合は、書き込み前に作成する。

コンテキストファイルを更新しないことは許されません。

必須:証拠の収集

📁 証拠ディレクトリ: .sb-pentest-evidence/03-api-audit/rpc-tests/

作成する証拠ファイル
ファイル内容
function-list.json検出されたすべての RPC 関数
vulnerable-functions/[name].json脆弱な関数ごとの詳細
証拠の形式(脆弱な関数)
{
  "evidence_id": "RPC-001",
  "timestamp": "2025-01-31T10:30:00Z",
  "category": "api-audit",
  "type": "rpc_vulnerability",
  "severity": "P0",

  "function": "get_all_users",

  "analysis": {
    "security_definer": true,
    "auth_check": false,
    "rls_bypass": true
  },

  "test": {
    "request": {
      "method": "POST",
      "url": "https://abc123def.supabase.co/rest/v1/rpc/get_all_users",
      "curl_command": "curl -X POST '$URL/rest/v1/rpc/get_all_users' -H 'apikey: $ANON_KEY' -H 'Content-Type: application/json'"
    },
    "response": {
      "status": 200,
      "rows_returned": 1247,
      "sample_data": "[REDACTED - contains user PII]"
    }
  },

  "impact": "Bypasses RLS, returns all 1,247 user records",
  "remediation": "Change to SECURITY INVOKER or add auth.uid() check"
}
curl-commands.sh に追加
# === RPC FUNCTION TESTS ===
# Test get_all_users function (P0 if accessible)
curl -X POST "$SUPABASE_URL/rest/v1/rpc/get_all_users" \
  -H "apikey: $ANON_KEY" \
  -H "Content-Type: application/json"

# Test admin_delete_user function
curl -X POST "$SUPABASE_URL/rest/v1/rpc/admin_delete_user" \
  -H "apikey: $ANON_KEY" \
  -H "Content-Type: application/json" \
  -d '{"target_id": "test-uuid"}'

関連スキル

  • supabase-audit-tables-list — 公開テーブルを一覧表示する
  • supabase-audit-rls — RLS ポリシーをテストする
  • supabase-audit-auth-users — ユーザー列挙テスト

コメント