supabase-audit-auth-users

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

User Enumeration Audit

用户枚举漏洞审计

🔴 CRITICAL: PROGRESSIVE FILE UPDATES REQUIRED
You MUST write to context files AS YOU GO, not just at the end.
  • Write to
    .sb-pentest-context.json
    IMMEDIATELY after each endpoint tested
  • Log to
    .sb-pentest-audit.log
    BEFORE 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.
🔴 CRITICAL: 需逐步更新文件
你必须随时写入上下文文件,而不是只在最后写入。
  • 测试每个端点后立即写入
    .sb-pentest-context.json
  • 每次测试前后都要记录到
    .sb-pentest-audit.log
  • 禁止等到技能完成后再更新文件
  • 如果技能崩溃或被中断,所有已有的发现必须已保存
这不是可选要求。不逐步写入属于严重错误。
本技能用于测试认证流程中的用户枚举漏洞。

When to Use This Skill

何时使用本技能

  • To check if user existence can be detected
  • To test login, signup, and recovery flows for information leakage
  • As part of authentication security audit
  • Before production deployment
  • 检查是否可以检测到用户账号是否存在
  • 测试登录、注册和找回密码流程中的信息泄露问题
  • 作为认证安全审计的一部分
  • 生产部署前的检测

Prerequisites

前提条件

  • Supabase URL and anon key available
  • Auth endpoints accessible
  • 已获取Supabase URL和匿名密钥(anon key)
  • 认证端点可访问

What is User Enumeration?

什么是用户枚举?

User enumeration occurs when an application reveals whether a user account exists through:
VectorIndicator
Different error messages"User not found" vs "Wrong password"
Response timingFast for non-existent, slow for existing
Response codes404 vs 401
Signup response"Email already registered"
用户枚举指应用通过以下方式泄露用户账号是否存在的信息:
攻击向量识别指标
不同的错误提示"用户不存在" vs "密码错误"
响应时间不存在的用户响应快,存在的用户响应慢
响应状态码404 vs 401
注册响应"该邮箱已注册"

Why It Matters

为什么这很重要

RiskImpact
Targeted attacksAttackers know valid accounts
PhishingConfirm targets have accounts
Credential stuffingReduce attack scope
PrivacyReveal user presence
风险影响
定向攻击攻击者知晓有效账号
钓鱼攻击确认目标拥有账号
凭证填充攻击缩小攻击范围
隐私问题泄露用户是否使用该服务

Tests Performed

执行的测试

EndpointTest 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
端点测试方法
/auth/v1/signup
尝试注册已存在的邮箱
/auth/v1/token
使用不同邮箱尝试登录
/auth/v1/recover
尝试重置密码
/auth/v1/otp
使用不同邮箱请求OTP

Usage

使用方法

Basic Enumeration Test

基础枚举测试

Test for user enumeration vulnerabilities
测试用户枚举漏洞

Test Specific Endpoint

测试特定端点

Test login endpoint for user enumeration
测试登录端点的用户枚举漏洞

Output Format

输出格式

═══════════════════════════════════════════════════════════
 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:
python
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 ─────────────────────────────────────────────────────────
  1. CONSISTENT RESPONSES Return identical messages for all scenarios: "If an account exists, you will receive an email"
  2. CONSISTENT TIMING Add artificial delay to normalize response times:
    typescript
    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;
  3. RATE LIMITING Already enabled: 3/hour per IP Consider per-email rate limiting too.
  4. CAPTCHA Add CAPTCHA for repeated attempts:
    • After 3 failed logins
    • For password recovery
    • For signup
  5. MONITORING Alert on enumeration patterns:
    • Many requests with different emails
    • Sequential email patterns (user1@, user2@, ...)
═══════════════════════════════════════════════════════════
undefined
═══════════════════════════════════════════════════════════
 用户枚举漏洞审计
═══════════════════════════════════════════════════════════

 项目: 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": "User already registered"}
 # 若为新用户: 用户创建成功或需要确认
───────────────────────────────────────────────────────── 登录端点 (/auth/v1/token) ─────────────────────────────────────────────────────────
测试: 针对不同邮箱场景发起POST请求
已存在邮箱,密码错误: ├── 响应: {"error": "Invalid login credentials"} ├── 响应时间: 245ms └── 状态码: 400
不存在的邮箱: ├── 响应: {"error": "Invalid login credentials"} ├── 响应时间: 52ms ← 明显更快! └── 状态码: 400
状态: 🟠 P2 - 可通过时序攻击枚举
虽然错误提示相同,但响应时间存在明显差异: ├── 已存在用户: ~200-300ms(密码哈希验证) └── 不存在用户: ~50-100ms(无哈希验证)
时序攻击PoC:
python
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": "Password recovery email sent"} ├── 响应时间: 1250ms(实际发送了邮件) └── 状态码: 200
不存在的邮箱: ├── 响应: {"message": "Password recovery email sent"} ├── 响应时间: 85ms ← 快得多(未发送邮件) └── 状态码: 200
状态: 🟠 P2 - 可通过时序攻击枚举
提示信息相同,但响应时间泄露了用户是否存在。 已存在用户会触发实际邮件发送(~1秒以上)。
───────────────────────────────────────────────────────── 魔术链接/OTP (/auth/v1/otp) ─────────────────────────────────────────────────────────
测试: 针对不同邮箱请求OTP
已存在邮箱: ├── 响应: {"message": "OTP sent"} ├── 响应时间: 1180ms └── 状态码: 200
不存在的邮箱: ├── 响应: {"error": "User not found"} ├── 响应时间: 95ms └── 状态码: 400
状态: 🔴 P1 - 可直接枚举
错误提示明确说明用户不存在。
───────────────────────────────────────────────────────── 总结 ─────────────────────────────────────────────────────────
测试的端点数量: 4 可枚举的端点数量: 4 (100%)
漏洞严重程度: ├── 🔴 P1: OTP端点(明确提示) ├── 🟠 P2: 注册端点(明确提示) ├── 🟠 P2: 登录端点(时序攻击) └── 🟠 P2: 找回密码端点(时序攻击)
整体用户枚举风险: 高
攻击者可以确定任意邮箱是否在你的应用中拥有账号。
───────────────────────────────────────────────────────── 修复建议 ─────────────────────────────────────────────────────────
  1. 统一响应 所有场景返回相同的提示信息: "如果账号存在,你将收到一封邮件"
  2. 统一响应时间 添加人工延迟以标准化响应时间:
    typescript
    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;
  3. 速率限制 已启用: 每IP每小时3次 考虑同时添加基于邮箱的速率限制。
  4. CAPTCHA 针对重复尝试添加CAPTCHA:
    • 3次登录失败后
    • 密码找回请求
    • 注册请求
  5. 监控 针对枚举模式触发告警:
    • 大量使用不同邮箱的请求
    • 顺序邮箱模式(user1@, user2@, ...)
═══════════════════════════════════════════════════════════
undefined

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
本技能通过测量响应时间来检测基于时序的枚举漏洞:
已存在用户:
├── 密码哈希验证: ~200-300ms
├── 邮件发送: ~1000-2000ms
└── 数据库查询: ~5-20ms

不存在用户:
├── 无哈希验证: 0ms
├── 无邮件发送: 0ms
└── 数据库查询: ~5-20ms(未找到)
阈值检测:
  • 差异>100ms: 可能存在时序泄露
  • 差异>500ms: 确定存在时序泄露

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
      }
    ]
  }
}
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
      }
    ]
  }
}

Mitigation Code Examples

修复代码示例

Consistent Response Time

统一响应时间

typescript
// 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"
    }));
  }
});
typescript
// 标准化响应时间的边缘函数
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: "如果你有账号,请检查你的邮箱"
    }));
  }
});

Generic Error Messages

通用错误提示

typescript
// 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;
}
typescript
// 不泄露用户是否存在
async function requestPasswordReset(email: string) {
  // 始终返回成功提示
  const response = {
    message: "如果该邮箱对应的账号存在," +
             "你将收到一封密码重置链接。"
  };

  // 在后台执行实际重置操作(不等待)
  supabase.auth.resetPasswordForEmail(email).catch(() => {});

  return response;
}

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:
  1. Before testing each endpoint → Log the action to
    .sb-pentest-audit.log
  2. After each timing measurement → Immediately update
    .sb-pentest-context.json
  3. After each enumeration vector 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.
禁止批量在最后写入。请按以下方式操作:
  1. 测试每个端点前 → 将操作记录到
    .sb-pentest-audit.log
  2. 每次时序测量后 → 立即更新
    .sb-pentest-context.json
  3. 每次发现枚举向量后 → 立即记录该发现
这样可以确保如果技能被中断、崩溃或超时,截至该点的所有发现都已被保存。

Required Actions (Progressive)

必须执行的操作(逐步)

  1. Update
    .sb-pentest-context.json
    with results:
    json
    {
      "user_enumeration": {
        "timestamp": "...",
        "endpoints_tested": 4,
        "vulnerabilities": [ ... ]
      }
    }
  2. 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
  3. If files don't exist, create them before writing.
FAILURE TO UPDATE CONTEXT FILES IS NOT ACCEPTABLE.
  1. 更新
    .sb-pentest-context.json
    记录结果:
    json
    {
      "user_enumeration": {
        "timestamp": "...",
        "endpoints_tested": 4,
        "vulnerabilities": [ ... ]
      }
    }
  2. 记录到
    .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已更新
  3. 如果文件不存在,在写入前创建它们。
不更新上下文文件是不被接受的。

MANDATORY: Evidence Collection

必须:收集证据

📁 Evidence Directory:
.sb-pentest-evidence/05-auth-audit/enumeration-tests/
📁 证据目录:
.sb-pentest-evidence/05-auth-audit/enumeration-tests/

Evidence Files to Create

需要创建的证据文件

FileContent
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
文件内容
enumeration-tests/login-timing.json
登录端点时序分析
enumeration-tests/recovery-timing.json
找回密码端点时序分析
enumeration-tests/otp-enumeration.json
OTP端点提示信息分析

Evidence Format

证据格式

json
{
  "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\"}'"
  ]
}
json
{
  "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\"}'"
  ]
}

Related Skills

相关技能

  • supabase-audit-auth-config
    — Full auth configuration
  • supabase-audit-auth-signup
    — Signup flow testing
  • supabase-report
    — Include in final report
  • supabase-audit-auth-config
    — 完整认证配置审计
  • supabase-audit-auth-signup
    — 注册流程测试
  • supabase-report
    — 包含在最终报告中