eugenicum/supabase-audit

GitHub: eugenicum/supabase-audit

专为 Supabase 生态设计的黑盒渗透测试与架构审计工具,通过 24 个阶段的实战化攻击模拟,主动挖掘并验证 RLS 绕过、越权访问等安全配置缺陷。

Stars: 0 | Forks: 0

# supabase-audit [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/) [![Supabase](https://img.shields.io/badge/supabase-pentest-3ECF8E.svg)](https://supabase.com) **`supabase-audit`** 是一个针对 Supabase (PostgreSQL + PostgREST + GoTrue + Realtime + Storage + pg_graphql + Edge Functions) 的 24 阶段渗透测试与架构审计工具。它将你的项目视为一个黑盒,像真实的攻击者那样攻击每一个公共接口,并生成一份包含漏洞复现、修复方案和严重程度评级的 Markdown 报告。 开发这个工具是因为 Supabase 控制台只会告诉你_配置了什么_,而不会告诉你攻击者_实际上_能访问什么。 ## 要求 - **Python 3.10+**(推荐 3.12) - **一个你拥有的 Supabase 项目**(免费或付费计划——均支持) - 从 Supabase 控制台获取的 **3 个凭证**(见下方第 2 步的说明) - macOS / Linux / WSL(未经 Windows 原生环境测试) ## 安装 ``` git clone https://github.com/eugenicum/supabase-audit.git cd supabase-audit python3 -m venv .venv && source .venv/bin/activate pip install -r requirements.txt ``` 这就是完整的安装过程。不需要系统级软件包、不需要 Docker、不需要数据库驱动。 ## 设置 —— 哪里需要填写什么内容 审计程序会从 **2 个文件** 中读取信息: | 文件 | 填写内容 | 存放位置 | |---|---|---| | **env 文件** (`.env` 风格) | 你的 3 个 Supabase 凭证(URL + 2 个密钥) | 代码库之外的任何位置。建议存放在:`~/.supabase-audit/myproject.env` | | **项目配置** (`projects/.local.json`) | 审计设置 + env 文件的路径 | 代码库内部(`.local.json` 后缀已被 gitignore 忽略) | ### 第 1 步 — 创建你的 env 文件 将该文件保存在 `~/.supabase-audit/myproject.env`(如需要请先创建该目录)。将值替换为你真实的凭证: ``` SUPABASE_URL=https://.supabase.co SUPABASE_ANON_KEY=eyJhbGc...your-anon-key... SUPABASE_SERVICE_KEY=eyJhbGc...your-service-role-key... # 可选 — 仅用于付费计划检查(PITR,通过 Management API 实现的 branches) SUPABASE_ACCESS_TOKEN=sbp_... ``` 锁定权限:`chmod 600 ~/.supabase-audit/myproject.env` ### 第 2 步 — 从 Supabase 控制台获取这 3 个值 在你的 Supabase 控制台 → **Project Settings → API** 中: | 字段 | 从哪里复制 | 形式 | |---|---|---| | `SUPABASE_URL` | "Project URL" 输入框 | `https://abcdefghij.supabase.co` | | `SUPABASE_ANON_KEY` | "Project API keys" → 标记为 `anon` `public` 的那一行 | 以 `eyJhbGc...` 开头 | | `SUPABASE_SERVICE_KEY` | "Project API keys" → 标记为 `service_role` `secret` 的那一行(点击 "Reveal") | 以 `eyJhbGc...` 开头 | **可选的 `SUPABASE_ACCESS_TOKEN`** — 在 https://supabase.com/dashboard/account/tokens 生成。仅在你需要进行 PITR / 备份 / 分支检查时(付费计划)才需要。否则可跳过。 ### 第 3 步 — 创建你的项目配置 ``` cp projects/_template.json projects/myproject.local.json ``` 编辑 `projects/myproject.local.json` —— 必填字段标有星号: ``` { "name": "myproject", // * "env_file": "/Users/you/.supabase-audit/myproject.env", // * absolute path "skip_tables": [], "skip_table_prefixes": ["pg_", "geometry_", "geography_"], "skip_rpcs": [], "team_table": null, "team_member_col": null, "team_resource_col": null, "allowlist": [] } ``` 必填: - `name` — 用于报告文件名 - `env_file` — 第 1 步中创建的文件的绝对路径 可选: - `skip_tables` / `skip_rpcs` — 要忽略的名称(例如,针对 PostGIS 设置为 `["spatial_ref_sys"]`) - `skip_table_prefixes` — 通常保持默认即可 - `team_table` / `team_member_col` / `team_resource_col` — 如果你的应用具有团队/组织结构,这会启用租户边界测试。例如针对 `team_members(user_id, team_id)` 表的配置: "team_table": "team_members", "team_member_col": "user_id", "team_resource_col": "team_id" - `allowlist` — 标记为已知安全对象的暴露面模式(参见下方的[白名单](#allowlisting-validated-safe-findings)) ### 第 4 步 — 安装静态检查辅助工具(一次性操作,可选) 这会启用针对数据库 Schema 的 CVE 模式扫描(DEFINER 卫生状况、缺失 WITH CHECK 等)。如跳过,这些检查将干净地输出 `SKIP`。 ``` cat install_static_helper.sql | pbcopy # 粘贴到 Supabase Dashboard → SQL Editor → Run ``` 它会创建一个只能由 `service_role` 调用的 `SECURITY DEFINER` 函数(`public._audit_static_query`)。你可以随时删除它: ``` DROP FUNCTION IF EXISTS public._audit_static_query(text); ``` ## 运行 ``` python supabase_audit.py --project myproject.local ``` 审计大约需要 1–5 分钟,具体取决于项目大小。输出内容: - 位于 `reports/myproject-.md` 的 Markdown 报告,包含复现步骤和修复建议 - 结尾处的横幅汇总了各种严重程度的数量 - 详细的逐探测项日志(每个测试,无论通过或失败) ## 测试内容 ### 渗透测试探测(20 个阶段) | 阶段 | 测试内容 | |---|---| | `attack_rls` | 针对每张表的 Anon + 跨租户 SELECT/INSERT/UPDATE/DELETE;结合 service-role 的真实基准进行交叉验证 | | `attack_rpc` | Anon + user_b 调用所有 RPC;通过 `user_id` 参数测试 IDOR;标记特权命名模式 | | `attack_storage` | 每个存储桶的 Anon 列举、Anon 下载、公开标志审查 | | `attack_auth` | 注册频率限制,密码重置用户枚举,JWT 重放,OpenAPI Schema 暴露 | | `attack_postgrest_advanced` | 外键遍历泄漏 (`?select=*,joined(*)`),隐藏列读取,`Content-Range` 计数预言机 | | `attack_graphql` | 通过 pg_graphql 进行 `/graphql/v1` 内省 + 敏感类型查询 | | `attack_realtime` | 在每个已发布的表上进行 Anon WebSocket 连接 | | `attack_realtime_payload` | Service-role INSERT + Anon 监听 —— 确认 RLS 是否阻止了广播 Payload | | `attack_edge_functions` | 针对近 `/functions/v1/` 的字典发现 + 认证绕过 | | `attack_role_escalation` | 使用特权列(`role=admin`, `is_admin=true`, `credits=999999`)PATCH 自有行;软删除绕过 | | `attack_storage_advanced` | 路径遍历 (`../etc-passwd`),URL 编码的 `%2F..%2F`,图片转换 DoS,签名 URL 篡改 | | `attack_auth_advanced` | 匿名认证特性检查;OAuth `redirect_to` 白名单绕过 | | `attack_extensions` | 通过带有 URL 参数的 RPC 进行 pg_net SSRF;vault/解密机密暴露 | | `attack_recon` | CORS 反射,server/version 请求头,PG 版本横幅,健康检查端点 | | `attack_jwt_forge` | 针对近 /auth/v1/user 使用 `alg: none` + 空签名 JWT | | `attack_tenant_matrix` | 通过 `team_table`/`team_resource_col` 进行多团队边界测试 | | `attack_audit_integrity` | 对类似审计表的表(`audit_events`, `*_log`, `*_history`)尝试 INSERT/DELETE | | `attack_logic_flows` | 大规模赋值(未知列),并发相同邮箱注册竞态 | | `attack_data_hygiene` | 错误响应中的 PII 正则扫描;加密列的明文启发式检查;存储桶策略完整性 | | `attack_misc` | PgBouncer 可达性 (5432/6543),PostgREST 操作符滥用,分支检测 | ### 架构审计(4 个阶段) | 阶段 | 测试内容 | |---|---| | `arch_rls_consistency` | 启用了 RLS 但策略数量为 **0** 的表;所有者列命名漂移;策略模式分布 | | `arch_schema_design` | 没有主键的表;缺少支撑索引的外键;RLS 未过滤 `deleted_at IS NULL` 的软删除列 | | `arch_dr_compliance` | 计划检测(免费版与付费版),备份可见性,GDPR 数据导出 RPC 的存在性,用户删除 RPC 的存在性,审计日志表的存在性 | | `arch_operational` | 存在 `auth.users` 插入触发器;每张表都有 `updated_at` 触发器;孤立的触发器函数;pg_cron 作业列表 | ### Schema 级别的静态检查 由设置第 4 步中可选的 `SECURITY DEFINER` 辅助工具提供支持: - 缺少 `SET search_path` 的 `SECURITY DEFINER` 函数(CVE 级别) - 未设置 `security_invoker = on` 的视图(RLS 绕过) - 缺少 `WITH CHECK` 的 UPDATE 策略(允许用户将行转移给其他所有者) - RLS 处于 DISABLED 状态的 public 模式下的表 - 包含敏感表(`auth.*`, `vault.*`)的 `supabase_realtime` 发布 - 已安装的特权扩展(`pg_net`, `http`, `plpython3u`) ## 为什么会存在这个工具 Supabase 非常出色,但它的默认设置为你做了许多决定: - `public` 模式中的新表会暴露给 PostgREST。 - `anon` 和 `authenticated` 角色具有广泛的功能级授权。 - Realtime 默认发布每一张公开表。 - pg_graphql 内省默认开启。 - 存储策略使用 `(storage.foldername(name))[1]` —— 很容易被误用。 每个默认设置单独来看都是合理的。但它们加在一起,就创造了一些你并未明确选择的攻击面。**`supabase-audit` 能找出它们。** 它还能捕捉到黑盒探测无法看到的东西 —— 使用 service-role 辅助工具检查 `pg_proc`、`pg_policy`、`pg_class`,寻找 CVE 模式的配置错误。 ## 输出示例 ``` ============================================================ REPORT: reports/myproject-20260507T182229Z.md CRITICAL=0 HIGH=0 MEDIUM=37 LOW=2 ============================================================ ``` 每个发现项类似于: ``` ### [严重] bucket temp-audio 接受 Path traversal **Surface:** `bucket:temp-audio` **What happened:** user_b uploaded with crafted path `shares//../../etc-passwd` — storage policy didn't sanitize. **Reproduction:** curl -X POST 'https://...supabase.co/storage/v1/object/temp-audio/...' -H 'Authorization: Bearer ' -d 'probe' **Response excerpt:** {'Key': 'temp-audio/etc-passwd', 'Id': '...'} **Fix:** Add a per-user folder constraint to the INSERT policy: WITH CHECK ((storage.foldername(name))[1] = auth.uid()::text) ``` ## 常用参数 ``` # 仅重新测试上次报告的 surfaces(快速) python supabase_audit.py --project myproject --retest # 与特定的较旧报告进行比较 python supabase_audit.py --project myproject --retest reports/myproject-2026-01-01.md # 仅运行特定 phases python supabase_audit.py --project myproject --only attack_rls attack_storage # 将 RLS 攻击限制为特定 table python supabase_audit.py --project myproject --only-tables contacts orders # 将 RPC 攻击限制为特定 functions python supabase_audit.py --project myproject --only-rpcs get_user_stats # 仅 enumerate — 无攻击,无测试用户 python supabase_audit.py --project myproject --dry-run ``` ## 将已验证安全的发现项加入白名单 有些发现项是故意的(公开的目录表、命名约定、平台行为)。将它们添加到项目的 `allowlist` 中: ``` { "allowlist": [ { "surface": "bucket:public-assets", "reason": "Bucket is intentionally public — landing-page images. No PII." }, { "surface": "rpc:show_limit", "reason": "pg_trgm built-in returning Postgres trigram threshold (0.3). Not user data." } ] } ``` 被加入白名单的发现项会降级为 `INFO`,并在报告中附注原因 —— 它们是可审计的,而不是被静音。 ## 安全性 - 测试用户在每次运行时都会创建带有随机后缀的账号,并在 `finally` 代码块中被删除。 - 每次成功的测试 INSERT 之后,都会跟着执行一次 service-role DELETE;失败的操作则会落入报告的“手动清理”部分。 - JWT 和 service key 在 stderr 日志中会被脱敏(仅显示前 8 个字符)。 - `auth.*` 模式会被跳过 —— 那些是 Supabase 的内部机制,不是你的应用。 - 路径遍历探测会写入一些小文本文件;审计程序会将它们清理掉。如果你希望完全跳过某个存储桶,可以在你的项目配置中将它添加到 `skip_buckets` 中(待办 —— 目前请使用 `--only` 来限制阶段)。 - **仅供授权使用。** 旨在测试你自己的 Supabase 项目。请勿将其指向你不拥有的项目。 ## 限制 - 针对 INSERT Payload 的 Schema 推断属于尽力而为;一些具有不寻常的 NOT NULL 约束的表会导致探测返回 400(会被记录为 PASS —— RLS 或检查约束发挥了作用)。 - JWT 重放仅供参考。Supabase 访问令牌在设计上是无状态的,并在过期前一直有效。 - Edge Functions 不会被自动发现(没有公开的列举端点);字典覆盖了大约 50 个常见名称。 - 所有者列的启发式方法会查找 `created_by`、`user_id`、`owner_id`、`owner`、`author_id`。具有非标准所有者列的表无法针对 user_b 泄漏检测进行交叉检查。 - Realtime Payload 泄漏测试最多检查 3 张表并等待 8 秒 —— 如果需要,可以在 `attack_realtime_payload.py` 中增加该值。 - 某些 Management API 检查(PITR、分支)需要 Supabase 个人访问令牌 —— 如果你需要这些检查,请在项目 JSON 中设置 `management_pat`。 - 免费的 Supabase 计划不会暴露所有的 Management API 端点;受影响的检查会输出 `INFO` 并附带“paid plan only”的注释## 发现项的评分方式 | 严重程度 | 含义 | |---|---| | CRITICAL | 当前即可从开放互联网进行活跃的数据外泄。立即停止手头工作并优先修复。 | | HIGH | 具有可衡量影响(特定数据类别、特定角色)的具体利用路径。本周内修复。 | | MEDIUM | 加固 / 纵深防御。在处理周边相关代码时进行修复。 | | LOW | 最佳实践、性能或运维卫生。在季度清理时修复。 | | INFO / ALLOWLISTED | 故意为之或超出你控制范围的平台行为。已记录在案,不是 Bug。 | ## 架构 ``` supabase_audit.py # CLI orchestrator + run modes audit/__init__.py # Finding / TestResult / AuditContext dataclasses audit/client.py # SupabaseClient (anon, user, service-role) audit/enumerate.py # Tables / RPCs / buckets discovery via OpenAPI + Storage API audit/attack_*.py # 20 pentest phases audit/arch_*.py # 4 architectural phases audit/sql_static_checks.py # CVE-pattern scans via _audit_static_query helper audit/report.py # Markdown report writer projects/_template.json # Project config template install_static_helper.sql # One-time SECURITY DEFINER helper for static checks reports/ # Output reports (gitignored) ``` 每个 `attack_*.py` / `arch_*.py` 阶段都会导出一个 `run(ctx)` 函数,该函数会修改 `ctx.findings` 和 `ctx.results`。通过在 `supabase_audit.py` 的 `PHASES` 元组中进行添加,即可接入新的阶段。 ## 贡献 欢迎提交 PR。要添加新的审计阶段: 1. 创建 `audit/attack_yourthing.py`,导出 `run(ctx: AuditContext) -> None`。 2. 将阶段名称添加到 `supabase_audit.py` 的 `PHASES` 中。 3. 在 `run_audit()` 中添加 `if should("attack_yourthing"):` 块。 4. 在 README 的阶段表格中添加一行。 保持模块小巧 —— 一个文件只关注一个功能,最多约 200 行。复用 `audit.client.anon_client` / `service_client` / `user_client`。优先使用 `ctx.add_finding(...)` + `ctx.add_result(...)` 而不是 print 语句。 ## 相关工作 - [Supabase Database Linter](https://supabase.com/docs/guides/database/database-linter) — 内置于控制台的静态检查 - [Supabase Security Advisor](https://supabase.com/dashboard/project/_/database/security-advisor) — 第一方安全建议 - [pgaudit](https://www.pgaudit.org/) — Postgres 审计日志扩展 `supabase-audit` 通过**主动的黑盒探测**和**多用户攻击模拟**对这些工具进行了补充 —— 而不仅仅是静态的规则匹配。 ## 许可证 MIT — 详见 [LICENSE](LICENSE)。 ## 关键词 supabase security audit · supabase pentest · postgres rls scanner · postgrest security · supabase vulnerability scanner · pg_graphql audit · supabase storage security · realtime rls audit · row-level-security audit · postgrest pentesting · supabase compliance audit · supabase gdpr audit
标签:API安全, CISA项目, DevSecOps, GraphQL, JSON输出, JWT, PostgreSQL, Python, RLS, RPC, Supabase, Web安全, Web报告查看器, 上游代理, 存储安全, 安全合规, 实时通信, 开源安全工具, 无后门, 架构审计, 测试用例, 网络代理, 蓝队分析, 行级安全, 边缘函数, 逆向工具, 逆向工程平台, 黑盒测试