关于 supabase-audit-tables-read
用于测试 Supabase 表数据读取权限的安全审计技能,面向检查 RLS 策略效果和评估数据暴露程度的审计人员。它对列出的表执行只读查询,并按数据敏感度评估结果。
主要功能
- 01对表执行只读读取测试
- 02支持快速、抽样和计数模式
- 03按敏感数据类型评估严重性
- 04逐表保存结果并记录审计日志
- 05生成访问证据和 curl 示例
适用场景
- 01验证 RLS 是否阻止未授权读取
- 02确认暴露表实际可读取的数据
- 03在不获取行数据的情况下统计可访问记录
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ yoanbernabeu/ supabase-pentest-skills --skill supabase-audit-tables-read安装后在 Claude Code 中输入/supabase-audit-tables-read确认已加载。
详细介绍
表数据访问测试
🔴 严重:必须渐进式更新文件
你必须边执行边写入上下文文件,而不是只在最后写入。
- 每测试完一张表,立即写入
.sb-pentest-context.json- 每次测试前后都要记录到
.sb-pentest-audit.log- 不要等技能执行完毕后才更新文件
- 如果技能崩溃或中断,之前的所有发现都必须已经保存
这不是可选项。不进行渐进式写入属于严重错误。
此技能尝试读取已暴露表中的数据,以确定实际可访问的信息。
何时使用此技能
- 列出表之后,用于验证实际访问权限
- 测试 RLS 策略是否有效
- 评估数据暴露的严重程度
- 准确记录可以检索哪些数据
前提条件
- 已列出表(如有需要,将自动调用
supabase-audit-tables-list) - 有可用的 Anon key
工作原理
此技能会对每张已暴露的表执行 SELECT 查询:
GET https://[project].supabase.co/rest/v1/[table]?select=*&limit=5
Authorization: Bearer [anon-key]
**重要:**此操作为只读操作,不会修改或删除任何数据。
测试模式
| 模式 | 描述 | 查询 |
|---|---|---|
| 快速 | 从每张表读取前 5 行 | ?limit=5 |
| 抽样 | 从各表随机抽样 | ?limit=10&order=random |
| 计数 | 仅统计行数,不读取数据 | HEAD 请求 |
用法
基本读取测试
Test read access on exposed tables
仅快速计数
Count accessible rows in all tables (no data retrieval)
指定表
Test read access on the users table
输出格式
═══════════════════════════════════════════════════════════
DATA ACCESS TEST RESULTS
═══════════════════════════════════════════════════════════
Test Mode: Quick (5 rows per table)
Tables Tested: 8
─────────────────────────────────────────────────────────
Results by Table
─────────────────────────────────────────────────────────
1. users
Status: 🔴 P0 - DATA EXPOSED
Rows Retrieved: 5 (of 1,247 total)
Sample Data:
┌─────────────────────────────────────────────────────┐
│ id: 550e8400-e29b-41d4-a716-446655440001 │
│ email: [email protected] ← PII EXPOSED │
│ name: John Doe ← PII EXPOSED │
│ avatar_url: https://... │
│ created_at: 2025-01-15T10:30:00Z │
└─────────────────────────────────────────────────────┘
Finding: User emails and names accessible without auth
2. profiles
Status: 🟠 P1 - PARTIAL ACCESS
Rows Retrieved: 5
Note: Only public fields returned (RLS working partially)
Columns Visible: id, bio, website
Columns Blocked: user_id, social_links, private_notes
3. posts
Status: ✅ EXPECTED ACCESS
Rows Retrieved: 5
Note: Only published=true posts returned (RLS working)
Data: Public content, appropriate access level
4. orders
Status: ✅ BLOCKED
Response: 403 Forbidden
Message: "new row violates row-level security policy"
Note: RLS properly blocking access
5. api_keys
Status: ✅ BLOCKED
Response: 403 Forbidden
Note: RLS properly protecting secrets
6. products
Status: ✅ EXPECTED ACCESS
Rows Retrieved: 5
Note: Public catalog data, appropriate access
7. comments
Status: 🟠 P1 - MORE DATA THAN EXPECTED
Rows Retrieved: 5
Issue: user_id column exposed (can correlate to users)
Recommendation: Use a view to hide user_id
8. settings
Status: 🔴 P0 - SENSITIVE DATA EXPOSED
Rows Retrieved: 3
Sample Data:
┌─────────────────────────────────────────────────────┐
│ key: stripe_webhook_secret │
│ value: whsec_xxxxxxxxxxxx ← SECRET EXPOSED │
└─────────────────────────────────────────────────────┘
Finding: Application secrets in accessible table!
─────────────────────────────────────────────────────────
Summary
─────────────────────────────────────────────────────────
P0 (Critical): 2 tables with sensitive data exposed
P1 (High): 2 tables with partial/unexpected exposure
Blocked: 2 tables properly protected
Expected: 2 tables with appropriate public access
Total Rows Accessible: 1,892 across exposed tables
Immediate Actions:
1. Fix 'settings' table - remove from public or add RLS
2. Fix 'users' table - add RLS to protect email/name
3. Review 'comments' to hide user correlation
═══════════════════════════════════════════════════════════
严重程度评估
| 状态 | 严重程度 | 标准 |
|---|---|---|
| 🔴 数据已暴露 | P0 | 可访问敏感数据(PII、机密信息、财务信息) |
| 🟠 部分访问 | P1 | 可访问的数据超出预期,但不属于关键数据 |
| 🟡 出乎预期 | P2 | 可访问,但数据风险较低 |
| ✅ 已阻止 | - | RLS 正确阻止访问 |
| ✅ 符合预期 | - | 公开数据,访问权限适当 |
数据分类
此技能可识别以下敏感数据类型:
| 类型 | 模式 | 暴露时的严重程度 |
|---|---|---|
| PII | email、phone、name、address | P0 |
| 财务信息 | amount、total、card、payment | P0 |
| 机密信息 | key、secret、token、password | P0 |
| 身份验证 | user_id、session、jwt | P1 |
| 元数据 | created_at、updated_at | P2 |
上下文输出
{
"data_access": {
"timestamp": "2025-01-31T10:30:00Z",
"tables_tested": 8,
"summary": {
"p0_exposed": 2,
"p1_partial": 2,
"blocked": 2,
"expected": 2
},
"results": [
{
"table": "users",
"status": "exposed",
"severity": "P0",
"rows_accessible": 1247,
"sensitive_columns": ["email", "name"],
"sample_redacted": true
},
{
"table": "settings",
"status": "exposed",
"severity": "P0",
"rows_accessible": 3,
"sensitive_data_types": ["secrets"],
"finding": "Application secrets exposed"
}
],
"total_rows_accessible": 1892
}
}
审计日志条目
[2025-01-31T10:30:00Z] READ_TEST_START tables=8
[2025-01-31T10:30:01Z] READ_TEST table=users status=200 rows=5 severity=P0
[2025-01-31T10:30:01Z] READ_TEST table=orders status=403 severity=none
[2025-01-31T10:30:02Z] READ_TEST_COMPLETE exposed=4 blocked=2
修复示例
用户表
-- Enable RLS
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
-- Only authenticated users see their own data
CREATE POLICY "Users see own data"
ON users FOR SELECT
USING (auth.uid() = id);
-- Or create a public view with limited columns
CREATE VIEW public.users_public AS
SELECT id, avatar_url, created_at FROM users;
设置表
-- Remove from public access entirely
REVOKE ALL ON TABLE settings FROM anon, authenticated;
-- Access only via Edge Functions
-- In your Edge Function:
const { data } = await supabaseAdmin
.from('settings')
.select('*')
.eq('key', 'stripe_webhook_secret')
.single()
内容表
-- RLS for published content only
CREATE POLICY "Public sees published posts"
ON posts FOR SELECT
USING (published = true);
-- Authors see their own drafts
CREATE POLICY "Authors see own posts"
ON posts FOR SELECT
USING (auth.uid() = author_id);
常见问题
❌ **问题:**所有表都返回 403
✅ **解决方案:**RLS 可能限制过严,或者 anon key 无效。从安全角度来看,这实际上是好事。
❌ **问题:**结果为空,但没有报错
✅ **解决方案:**RLS 正在筛除所有行。表结构已暴露,但没有数据。
❌ **问题:**大型表超时
✅ **解决方案:**使用计数模式或减少限制数量。
强制要求:渐进式更新上下文文件
⚠️ 此技能必须在执行过程中渐进式更新跟踪文件,而不是只在最后更新。
关键规则:边执行边写入
不要在最后批量写入。请改为:
- 测试每张表之前 → 将操作记录到
.sb-pentest-audit.log - 每测试完一张表之后 → 立即将结果更新到
.sb-pentest-context.json - 每发现一项问题之后 → 将严重程度记录到
.sb-pentest-audit.log
这样可以确保如果技能被中断、崩溃或超时,截至当时的所有发现都已保存。
必须执行的操作(渐进式)
-
更新
.sb-pentest-context.json,写入结果:{ "data_access": { "timestamp": "...", "tables_tested": 8, "summary": { "p0_exposed": 2, ... }, "results": [ ... ], "total_rows_accessible": 1892 } } -
记录到
.sb-pentest-audit.log:[TIMESTAMP] [supabase-audit-tables-read] [START] Testing data access [TIMESTAMP] [supabase-audit-tables-read] [FINDING] P0: users table exposed [TIMESTAMP] [supabase-audit-tables-read] [CONTEXT_UPDATED] .sb-pentest-context.json updated -
如果文件不存在,请在写入前创建。
不更新上下文文件是不可接受的。
强制要求:收集证据
📁 证据目录: .sb-pentest-evidence/03-api-audit/data-samples/
要创建的证据文件
| 文件 | 内容 |
|---|---|
data-samples/[table]-sample.json | 每个可访问表的示例数据 |
data-samples/[table]-blocked.json | 访问受阻的证明(403 响应) |
证据格式(数据已暴露)
{
"evidence_id": "API-READ-001",
"timestamp": "2025-01-31T10:20:00Z",
"category": "api-audit",
"type": "data_access",
"severity": "P0",
"finding_id": "P0-002",
"table": "users",
"request": {
"method": "GET",
"url": "https://abc123def.supabase.co/rest/v1/users?select=*&limit=5",
"headers": {
"apikey": "[REDACTED]",
"Authorization": "Bearer [REDACTED]"
},
"curl_command": "curl -s 'https://abc123def.supabase.co/rest/v1/users?select=*&limit=5' -H 'apikey: $ANON_KEY' -H 'Authorization: Bearer $ANON_KEY'"
},
"response": {
"status": 200,
"headers": {
"content-range": "0-4/1247"
},
"total_rows": 1247,
"sample_data": [
{
"id": "550e8400-e29b-41d4-...",
"email": "[REDACTED]@example.com",
"name": "[REDACTED]",
"created_at": "2025-01-15T10:30:00Z"
}
],
"data_redacted": true
},
"analysis": {
"severity": "P0",
"pii_exposed": ["email", "name"],
"total_records_accessible": 1247,
"authentication_required": false
}
}
证据格式(已正确阻止)
{
"evidence_id": "API-READ-002",
"timestamp": "2025-01-31T10:21:00Z",
"table": "orders",
"severity": null,
"response": {
"status": 403,
"body": {"message": "new row violates row-level security policy"}
},
"analysis": {
"rls_working": true,
"access_blocked": true
}
}
添加到 curl-commands.sh
# === DATA ACCESS TESTS ===
# Test: Users table access
curl -s "$SUPABASE_URL/rest/v1/users?select=*&limit=5" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ANON_KEY"
# Test: Orders table access (should be blocked)
curl -s "$SUPABASE_URL/rest/v1/orders?select=*&limit=5" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ANON_KEY"
相关技能
supabase-audit-tables-list— 首先列出表supabase-audit-rls— 深入了解 RLS 策略supabase-report— 生成完整报告
评论
支持 Markdown