supabase-audit-rpc
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRPC Functions Audit
RPC函数安全审计
🔴 CRITICAL: PROGRESSIVE FILE UPDATES REQUIREDYou MUST write to context files AS YOU GO, not just at the end.
- Write to
IMMEDIATELY after each function tested.sb-pentest-context.json- Log to
BEFORE and AFTER each function test.sb-pentest-audit.log- DO NOT wait until the skill completes to update files
- If the skill crashes or is interrupted, all prior findings must already be saved
This is not optional. Failure to write progressively is a critical error.
This skill discovers and tests PostgreSQL functions exposed via Supabase's RPC endpoint.
🔴 严重警告:必须逐步更新文件你必须逐步写入上下文文件,而不是只在最后一次性写入。
- 测试完每个函数后立即写入
.sb-pentest-context.json- 测试每个函数前后都要记录到
.sb-pentest-audit.log- 禁止等到技能完成后再更新文件
- 如果技能崩溃或中断,所有已发现的问题必须已保存
此要求为强制性,未逐步写入属于严重错误。
本技能用于发现并测试通过Supabase RPC端点暴露的PostgreSQL函数。
When to Use This Skill
何时使用本技能
- To discover exposed database functions
- To test if functions bypass RLS
- To check for SQL injection in function parameters
- As part of comprehensive API security testing
- 发现暴露的数据库函数
- 测试函数是否绕过RLS
- 检查函数参数中是否存在SQL注入风险
- 作为全面API安全测试的一部分
Prerequisites
前置条件
- Supabase URL and anon key available
- Tables audit completed (recommended)
- 已获取Supabase URL和anon密钥
- 已完成表审计(推荐)
Understanding Supabase RPC
理解Supabase RPC
Supabase exposes PostgreSQL functions via:
POST https://[project].supabase.co/rest/v1/rpc/[function_name]Functions can:
- ✅ Respect RLS (if using and proper security)
auth.uid() - ❌ Bypass RLS (if without checks)
SECURITY DEFINER - ❌ Execute arbitrary SQL (if poorly written)
Supabase通过以下方式暴露PostgreSQL函数:
POST https://[project].supabase.co/rest/v1/rpc/[function_name]函数可能:
- ✅ 遵循RLS(如果使用和正确的安全设置)
auth.uid() - ❌ 绕过RLS(如果使用但未做检查)
SECURITY DEFINER - ❌ 执行任意SQL(如果编写不当)
Risk Levels for Functions
函数风险等级
| Type | Risk | Description |
|---|---|---|
| Lower | Runs with caller's permissions |
| Higher | Runs with definer's permissions |
| Accepts text/json | Higher | Potential for injection |
| Returns setof | Higher | Can return multiple rows |
| 类型 | 风险 | 描述 |
|---|---|---|
| 较低 | 调用者权限运行 |
| 较高 | 定义者权限运行 |
| 接受text/json参数 | 较高 | 存在注入风险 |
| 返回setof类型 | 较高 | 可返回多行数据 |
Usage
使用方法
Basic RPC Audit
基础RPC审计
Audit RPC functions on my Supabase projectAudit RPC functions on my Supabase projectTest Specific Function
测试特定函数
Test the get_user_data RPC functionTest the get_user_data RPC functionOutput Format
输出格式
═══════════════════════════════════════════════════════════
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
══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════
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
═══════════════════════════════════════════════════════════Injection Testing
注入测试
The skill tests for SQL injection in text/varchar parameters:
本技能会测试text/varchar参数中的SQL注入风险:
Safe (Parameterized)
安全(参数化)
sql
-- ✅ Safe: uses parameter placeholder
CREATE FUNCTION search_posts(query text)
RETURNS setof posts
AS $$
SELECT * FROM posts WHERE title ILIKE '%' || query || '%';
$$ LANGUAGE sql;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 (Concatenation)
易受攻击(字符串拼接)
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;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;Context Output
上下文输出
json
{
"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"
}
]
}
}json
{
"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"
}
]
}
}Best Practices for RPC Functions
RPC函数最佳实践
1. Prefer SECURITY INVOKER
1. 优先使用SECURITY INVOKER
sql
CREATE FUNCTION my_function()
RETURNS ...
SECURITY INVOKER -- Respects RLS
AS $$ ... $$;sql
CREATE FUNCTION my_function()
RETURNS ...
SECURITY INVOKER -- Respects RLS
AS $$ ... $$;2. Always Check auth.uid()
2. 始终检查auth.uid()
sql
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;sql
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. Use REVOKE for Sensitive Functions
3. 对敏感函数使用REVOKE
sql
-- Remove anon access
REVOKE EXECUTE ON FUNCTION admin_function FROM anon;
-- Only authenticated users
GRANT EXECUTE ON FUNCTION admin_function TO authenticated;sql
-- Remove anon access
REVOKE EXECUTE ON FUNCTION admin_function FROM anon;
-- Only authenticated users
GRANT EXECUTE ON FUNCTION admin_function TO authenticated;4. Avoid Text Parameters for Dynamic Queries
4. 避免使用文本参数进行动态查询
sql
-- ❌ Bad
CREATE FUNCTION query(tbl text) ...
-- ✅ Good: use specific functions per table
CREATE FUNCTION get_users() ...
CREATE FUNCTION get_posts() ...sql
-- ❌ Bad
CREATE FUNCTION query(tbl text) ...
-- ✅ Good: use specific functions per table
CREATE FUNCTION get_users() ...
CREATE FUNCTION get_posts() ...MANDATORY: Progressive Context File Updates
强制性要求:逐步更新上下文文件
⚠️ This skill MUST update tracking files PROGRESSIVELY during execution, NOT just at the end.
⚠️ 本技能必须在执行过程中逐步更新跟踪文件,而不是只在最后一次性更新。
Critical Rule: Write As You Go
关键规则:逐步写入
DO NOT batch all writes at the end. Instead:
- Before testing each function → Log the action to
.sb-pentest-audit.log - After each function analyzed → Immediately update
.sb-pentest-context.json - After each vulnerability found → Log the finding immediately
This ensures that if the skill is interrupted, crashes, or times out, all findings up to that point are preserved.
禁止批量写入,而是:
- 测试每个函数前 → 将操作记录到
.sb-pentest-audit.log - 分析完每个函数后 → 立即更新
.sb-pentest-context.json - 发现每个漏洞后 → 立即记录问题
这样可以确保如果技能被中断、崩溃或超时,截至该点的所有发现都能被保留。
Required Actions (Progressive)
必须执行的逐步操作
-
Updatewith results:
.sb-pentest-context.jsonjson{ "rpc_audit": { "timestamp": "...", "functions_found": 6, "summary": { "safe": 3, "p0_critical": 3 }, "findings": [ ... ] } } -
Log to:
.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 -
If files don't exist, create them before writing.
FAILURE TO UPDATE CONTEXT FILES IS NOT ACCEPTABLE.
-
更新记录结果:
.sb-pentest-context.jsonjson{ "rpc_audit": { "timestamp": "...", "functions_found": 6, "summary": { "safe": 3, "p0_critical": 3 }, "findings": [ ... ] } } -
记录到:
.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 -
如果文件不存在,先创建再写入。
不更新上下文文件是不被允许的。
MANDATORY: Evidence Collection
强制性要求:收集证据
📁 Evidence Directory:
.sb-pentest-evidence/03-api-audit/rpc-tests/📁 证据目录:
.sb-pentest-evidence/03-api-audit/rpc-tests/Evidence Files to Create
需要创建的证据文件
| File | Content |
|---|---|
| All discovered RPC functions |
| Details for each vulnerable function |
| 文件 | 内容 |
|---|---|
| 所有发现的RPC函数 |
| 每个易受攻击函数的详细信息 |
Evidence Format (Vulnerable Function)
易受攻击函数的证据格式
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"
}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"
}Add to curl-commands.sh
添加到curl-commands.sh
bash
undefinedbash
undefined=== RPC FUNCTION TESTS ===
=== RPC FUNCTION TESTS ===
Test get_all_users function (P0 if accessible)
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"
-H "apikey: $ANON_KEY"
-H "Content-Type: application/json"
curl -X POST "$SUPABASE_URL/rest/v1/rpc/get_all_users"
-H "apikey: $ANON_KEY"
-H "Content-Type: application/json"
-H "apikey: $ANON_KEY"
-H "Content-Type: application/json"
Test admin_delete_user function
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"}'
-H "apikey: $ANON_KEY"
-H "Content-Type: application/json"
-d '{"target_id": "test-uuid"}'
undefinedcurl -X POST "$SUPABASE_URL/rest/v1/rpc/admin_delete_user"
-H "apikey: $ANON_KEY"
-H "Content-Type: application/json"
-d '{"target_id": "test-uuid"}'
-H "apikey: $ANON_KEY"
-H "Content-Type: application/json"
-d '{"target_id": "test-uuid"}'
undefinedRelated Skills
相关技能
- — List exposed tables
supabase-audit-tables-list - — Test RLS policies
supabase-audit-rls - — User enumeration tests
supabase-audit-auth-users
- — 列出暴露的表
supabase-audit-tables-list - — 测试RLS策略
supabase-audit-rls - — 用户枚举测试
supabase-audit-auth-users