name: supabase-audit-tables-read description: 尝试从暴露的表中读取数据,以验证实际数据暴露情况和RLS有效性。
表数据访问测试
🔴 关键:需要渐进式文件更新
您必须逐步写入上下文文件,而不是仅在最后。
- 在测试每个表后立即写入
.sb-pentest-context.json- 在每次测试之前和之后记录到
.sb-pentest-audit.log- 不要等到技能完成后再更新文件
- 如果技能崩溃或被中断,所有之前的发现必须已经保存
这是不可选的。未能渐进式写入是关键错误。
这个技能尝试从暴露的表中读取数据,以确定实际上可以访问哪些信息。
何时使用这个技能
- 列出表后,以验证实际访问
- 测试RLS策略有效性
- 评估数据暴露的严重性
- 记录确切可以检索的数据
先决条件
- 已列出表(如果需要,自动调用
supabase-audit-tables-list) - 匿名密钥可用
工作原理
该技能在每个暴露的表上执行SELECT查询:
GET https://[项目].supabase.co/rest/v1/[表]?select=*&limit=5
Authorization: Bearer [匿名密钥]
重要: 这是只读的。不会修改或删除任何数据。
测试模式
| 模式 | 描述 | 查询 |
|---|---|---|
| 快速 | 每个表的前5行 | ?limit=5 |
| 样本 | 跨表的随机样本 | ?limit=10&order=random |
| 计数 | 仅行计数,无数据 | HEAD请求 |
用法
基本读取测试
测试暴露表的读取访问
仅快速计数
计数所有表中可访问的行(无数据检索)
特定表
测试用户表的读取访问
输出格式
═══════════════════════════════════════════════════════════
数据访问测试结果
═══════════════════════════════════════════════════════════
测试模式:快速(每个表5行)
测试表数:8
─────────────────────────────────────────────────────────
按表结果
─────────────────────────────────────────────────────────
1. users
状态:🔴 P0 - 数据暴露
检索行数:5(共1,247行)
样本数据:
┌─────────────────────────────────────────────────────┐
│ id: 550e8400-e29b-41d4-a716-446655440001 │
│ email: john.doe@example.com ← PII暴露 │
│ name: John Doe ← PII暴露 │
│ avatar_url: https://... │
│ created_at: 2025-01-15T10:30:00Z │
└─────────────────────────────────────────────────────┘
发现:用户邮箱和名称无需认证即可访问
2. profiles
状态:🟠 P1 - 部分访问
检索行数:5
备注:仅返回公共字段(RLS部分工作)
可见列:id, bio, website
被阻止列:user_id, social_links, private_notes
3. posts
状态:✅ 预期访问
检索行数:5
备注:仅返回published=true的帖子(RLS工作)
数据:公共内容,访问级别适当
4. orders
状态:✅ 被阻止
响应:403 Forbidden
消息:"new row violates row-level security policy"
备注:RLS正确阻止访问
5. api_keys
状态:✅ 被阻止
响应:403 Forbidden
备注:RLS正确保护秘密
6. products
状态:✅ 预期访问
检索行数:5
备注:公共目录数据,访问适当
7. comments
状态:🟠 P1 - 比预期更多数据
检索行数:5
问题:user_id列暴露(可关联到用户)
建议:使用视图隐藏user_id
8. settings
状态:🔴 P0 - 敏感数据暴露
检索行数:3
样本数据:
┌─────────────────────────────────────────────────────┐
│ key: stripe_webhook_secret │
│ value: whsec_xxxxxxxxxxxx ← 秘密暴露 │
└─────────────────────────────────────────────────────┘
发现:应用程序秘密在可访问表中!
─────────────────────────────────────────────────────────
摘要
─────────────────────────────────────────────────────────
P0(关键):2个表有敏感数据暴露
P1(高):2个表有部分/意外暴露
被阻止:2个表正确保护
预期:2个表有适当公共访问
可访问总行数:1,892个暴露表
立即行动:
1. 修复'settings'表 - 从公开移除或添加RLS
2. 修复'users'表 - 添加RLS以保护邮箱/名称
3. 审查'comments'以隐藏用户关联
═══════════════════════════════════════════════════════════
严重性评估
| 状态 | 严重性 | 标准 |
|---|---|---|
| 🔴 数据暴露 | 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": "应用程序秘密暴露"
}
],
"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
修复示例
对于用户表
-- 启用RLS
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
-- 仅认证用户看到自己的数据
CREATE POLICY "用户看到自己的数据"
ON users FOR SELECT
USING (auth.uid() = id);
-- 或创建具有有限列的公共视图
CREATE VIEW public.users_public AS
SELECT id, avatar_url, created_at FROM users;
对于设置表
-- 完全从公共访问移除
REVOKE ALL ON TABLE settings FROM anon, authenticated;
-- 仅通过边缘函数访问
-- 在边缘函数中:
const { data } = await supabaseAdmin
.from('settings')
.select('*')
.eq('key', 'stripe_webhook_secret')
.single()
对于内容表
-- RLS仅用于已发布内容
CREATE POLICY "公众看到已发布的帖子"
ON posts FOR SELECT
USING (published = true);
-- 作者看到自己的草稿
CREATE POLICY "作者看到自己的帖子"
ON posts FOR SELECT
USING (auth.uid() = author_id);
常见问题
❌ 问题: 所有表返回403 ✅ 解决方案: RLS可能太严格或匿名密钥无效。从安全角度看,这实际上是好的。
❌ 问题: 空结果但无错误 ✅ 解决方案: 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] 测试数据访问 [TIMESTAMP] [supabase-audit-tables-read] [FINDING] P0: users表暴露 [TIMESTAMP] [supabase-audit-tables-read] [CONTEXT_UPDATED] .sb-pentest-context.json更新 -
如果文件不存在,在写入前创建它们。
未能更新上下文文件是不可接受的。
强制:证据收集
📁 证据目录: .sb-pentest-evidence/03-api-audit/data-samples/
要创建的证据文件
| 文件 | 内容 |
|---|---|
data-samples/[表]-sample.json |
从每个可访问表的样本数据 |
data-samples/[表]-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
# === 数据访问测试 ===
# 测试:用户表访问
curl -s "$SUPABASE_URL/rest/v1/users?select=*&limit=5" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ANON_KEY"
# 测试:订单表访问(应被阻止)
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— 生成完整报告