From f709fc0cdca790001f29bf17e87f95b8cd80ede0 Mon Sep 17 00:00:00 2001 From: Translator Date: Mon, 29 Sep 2025 23:05:50 +0000 Subject: [PATCH] Translated ['', 'src/pentesting-ci-cd/supabase-security.md'] to zh --- src/pentesting-ci-cd/supabase-security.md | 194 +++++++++++++++++----- 1 file changed, 157 insertions(+), 37 deletions(-) diff --git a/src/pentesting-ci-cd/supabase-security.md b/src/pentesting-ci-cd/supabase-security.md index 00d1511ba..77d9df818 100644 --- a/src/pentesting-ci-cd/supabase-security.md +++ b/src/pentesting-ci-cd/supabase-security.md @@ -4,42 +4,42 @@ ## 基本信息 -根据他们的 [**着陆页**](https://supabase.com/): Supabase 是一个开源的 Firebase 替代品。使用 Postgres 数据库、身份验证、即时 API、边缘函数、实时订阅、存储和向量嵌入开始您的项目。 +根据他们的 [**官网首页**](https://supabase.com/):Supabase 是一个开源的 Firebase 替代品。使用 Postgres 数据库、Authentication、instant APIs、Edge Functions、Realtime subscriptions、Storage 和 Vector embeddings 启动你的项目。 ### 子域名 -基本上,当创建一个项目时,用户将收到一个 supabase.co 子域名,如:**`jnanozjdybtpqgcwhdiz.supabase.co`** +基本上,当创建项目时,用户会收到一个 supabase.co 子域名,例如:**`jnanozjdybtpqgcwhdiz.supabase.co`** ## **数据库配置** > [!TIP] -> **可以通过链接访问这些数据,如 `https://supabase.com/dashboard/project//settings/database`** +> **这些数据可以通过类似 `https://supabase.com/dashboard/project//settings/database` 的链接访问** -这个 **数据库** 将部署在某个 AWS 区域,为了连接到它,可以通过以下方式连接:`postgres://postgres.jnanozjdybtpqgcwhdiz:[YOUR-PASSWORD]@aws-0-us-west-1.pooler.supabase.com:5432/postgres`(这是在 us-west-1 创建的)。\ -密码是用户之前设置的 **密码**。 +这个 **数据库** 会部署在某个 AWS 区域,要连接它可以使用:`postgres://postgres.jnanozjdybtpqgcwhdiz:[YOUR-PASSWORD]@aws-0-us-west-1.pooler.supabase.com:5432/postgres`(此示例在 us-west-1 创建)。\ +该密码是用户之前设置的。 -因此,由于子域名是已知的,并且它被用作用户名,而 AWS 区域是有限的,可能可以尝试 **暴力破解密码**。 +因此,由于子域名是已知的,并且它被用作用户名且 AWS 区域有限,可能可以尝试 **brute force the password**。 本节还包含以下选项: - 重置数据库密码 - 配置连接池 -- 配置 SSL:拒绝明文连接(默认情况下启用) +- 配置 SSL:拒绝明文连接(默认启用) - 配置磁盘大小 -- 应用网络限制和禁令 +- 应用网络限制和封禁 ## API 配置 > [!TIP] -> **可以通过链接访问这些数据,如 `https://supabase.com/dashboard/project//settings/api`** +> **这些数据可以通过类似 `https://supabase.com/dashboard/project//settings/api` 的链接访问** -访问您项目中 supabase API 的 URL 将类似于:`https://jnanozjdybtpqgcwhdiz.supabase.co`。 +访问项目中 supabase API 的 URL 类似于:`https://jnanozjdybtpqgcwhdiz.supabase.co`。 -### 匿名 API 密钥 +### anon API 密钥 -它还将生成一个 **匿名 API 密钥** (`role: "anon"`),如:`eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImpuYW5vemRyb2J0cHFnY3doZGl6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTQ5OTI3MTksImV4cCI6MjAzMDU2ODcxOX0.sRN0iMGM5J741pXav7UxeChyqBE9_Z-T0tLA9Zehvqk`,应用程序需要使用它来联系我们示例中的 API 密钥。 +它还会生成一个 **anon API key** (`role: "anon"`),例如:`eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImpuYW5vemRyb2J0cHFnY3doZGl6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTQ5OTI3MTksImV4cCI6MjAzMDU2ODcxOX0.sRN0iMGM5J741pXav7UxeChyqBE9_Z-T0tLA9Zehvqk`,应用需要使用该 key 来访问 API,示例中暴露的内容如下。 -可以在 [**文档**](https://supabase.com/docs/reference/self-hosting-auth/returns-the-configuration-settings-for-the-gotrue-server) 中找到联系此 API 的 API REST,但最有趣的端点将是: +可以在 [**docs**](https://supabase.com/docs/reference/self-hosting-auth/returns-the-configuration-settings-for-the-gotrue-server) 中找到访问该 API 的 REST 文档,但最有趣的端点是:
@@ -99,61 +99,181 @@ Priority: u=1, i ```
-因此,每当您发现客户使用其被授予的子域名的 supabase(公司可能有一个 CNAME 指向他们的 supabase 子域名),您可以尝试 **使用 supabase API 创建一个新账户**。 +因此,每当你发现客户在使用 supabase 且使用了他们被授予的子域(公司某个子域可能对他们的 supabase 子域设置了 CNAME),你可以尝试 **使用 supabase API 在平台上创建一个新账户**。 -### secret / service_role api keys +### secret / service_role API 密钥 -将生成一个秘密 API 密钥,**`role: "service_role"`**。这个 API 密钥应该是秘密的,因为它能够绕过 **行级安全**。 +还会生成一个带有 **`role: "service_role"`** 的 secret API key。这个 API 密钥应该保密,因为它能绕过 **Row Level Security**。 -API 密钥看起来像这样:`eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImpuYW5vemRyb2J0cHFnY3doZGl6Iiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTcxNDk5MjcxOSwiZXhwIjoyMDMwNTY4NzE5fQ.0a8fHGp3N_GiPq0y0dwfs06ywd-zhTwsm486Tha7354` +API 密钥看起来像这样: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImpuYW5vemRyb2J0cHFnY3doZGl6Iiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTcxNDk5MjcxOSwiZXhwIjoyMDMwNTY4NzE5fQ.0a8fHGp3N_GiPq0y0dwfs06ywd-zhTwsm486Tha7354` ### JWT Secret -还将生成一个 **JWT Secret**,以便应用程序可以 **创建和签名自定义 JWT 令牌**。 +还会生成一个 **JWT Secret**,以便应用可以 **创建并签署自定义 JWT tokens**。 -## 认证 +## 身份验证 ### 注册 > [!TIP] -> 默认情况下,supabase 将允许 **新用户在您的项目中创建账户**,使用之前提到的 API 端点。 +> 默认情况下,supabase 将允许 **新用户通过前面提到的 API 端点在你的项目上创建账号**。 -然而,这些新账户默认情况下 **需要验证他们的电子邮件地址** 才能登录账户。可以启用 **“允许匿名登录”** 以允许人们在不验证电子邮件地址的情况下登录。这可能会授予对 **意外数据** 的访问(他们获得 `public` 和 `authenticated` 角色)。\ -这是一个非常糟糕的主意,因为 supabase 按活跃用户收费,因此人们可以创建用户并登录,而 supabase 将为这些用户收费: +但是,这些新账户默认情况下**需要验证他们的电子邮件地址**才能登录。可以启用 **"Allow anonymous sign-ins"** 以允许用户在不验证邮箱的情况下登录。这可能会授予对**意外数据**的访问(他们会获得 `public` 和 `authenticated` 角色)。\ +这是非常糟糕的做法,因为 supabase 按活跃用户收费,所以人们可以创建用户并登录,supabase 会对这些用户收费:
-### 密码和会话 +#### Auth: 服务器端注册强制执行 -可以指示最小密码长度(默认),要求(默认不要求)并禁止使用泄露的密码。\ -建议 **提高要求,因为默认要求较弱**。 +仅在前端隐藏注册按钮并不足够。如果 **Auth server 仍然允许注册**,攻击者可以使用公共的 `anon` key 直接调用 API 并创建任意用户。 -- 用户会话:可以配置用户会话的工作方式(超时,每个用户 1 个会话...) -- 机器人和滥用保护:可以启用验证码。 +快速测试(来自未认证的客户端): +```bash +curl -X POST \ +-H "apikey: " \ +-H "Authorization: Bearer " \ +-H "Content-Type: application/json" \ +-d '{"email":"attacker@example.com","password":"Sup3rStr0ng!"}' \ +https://.supabase.co/auth/v1/signup +``` +预期的加固措施: +- 在 Dashboard 中禁用电子邮件/密码注册:Authentication → Providers → Email → Disable sign ups (invite-only),或设置等效的 GoTrue setting。 +- 验证 API 现在对之前的调用返回 4xx,并且没有创建新用户。 +- 如果依赖邀请或 SSO,确保所有其他 providers 被禁用,除非明确需要。 -### SMTP 设置 +## RLS and Views: Write bypass via PostgREST -可以设置 SMTP 以发送电子邮件。 +使用 Postgres VIEW 来“隐藏”敏感列并通过 PostgREST 暴露,可能会改变权限的评估方式。在 PostgreSQL: +- 普通视图默认以视图所有者的权限执行(definer semantics)。在 PG ≥15 中可以选择使用 `security_invoker`。 +- Row Level Security (RLS) 作用于基表。表所有者会绕过 RLS,除非在表上设置了 `FORCE ROW LEVEL SECURITY`。 +- 可更新视图可以接受 INSERT/UPDATE/DELETE,这些操作随后应用到基表。没有 `WITH CHECK OPTION` 的情况下,不符合视图谓词的写入可能仍然成功。 + +在实战中观察到的风险模式: +- 一个列被减少的视图通过 Supabase REST 暴露并授予给 `anon`/`authenticated`。 +- PostgREST 允许对可更新视图进行 DML,且该操作以视图所有者的权限进行评估,从而有效地绕过基表上原本的 RLS 策略。 +- 结果:低权限客户端可以批量编辑他们不应修改的行(例如 profile bios/avatars)。 + +示例:通过视图进行的写操作(从公共客户端尝试): +```bash +curl -X PATCH \ +-H "apikey: " \ +-H "Authorization: Bearer " \ +-H "Content-Type: application/json" \ +-H "Prefer: return=representation" \ +-d '{"bio":"pwned","avatar_url":"https://i.example/pwn.png"}' \ +"https://.supabase.co/rest/v1/users_view?id=eq." +``` +Hardening checklist for views and RLS: +- 优先公开基础表,并使用明确的最小权限授权和精确的 RLS 策略。 +- If you must expose a view: +- 如果必须公开 view: +- Make it non-updatable (e.g., include expressions/joins) or deny `INSERT/UPDATE/DELETE` on the view to all untrusted roles. +- 使其不可更新(例如,包含表达式/连接),或对所有不受信任的角色拒绝 `INSERT/UPDATE/DELETE` 对该 view 的操作。 +- Enforce `ALTER VIEW SET (security_invoker = on)` so the invoker’s privileges are used instead of the owner’s. +- 强制使用 `ALTER VIEW SET (security_invoker = on)`,以便使用调用者的权限而不是所有者的权限。 +- On base tables, use `ALTER TABLE FORCE ROW LEVEL SECURITY;` so even owners are subject to RLS. +- 在基础表上使用 `ALTER TABLE FORCE ROW LEVEL SECURITY;`,这样即使是所有者也会受到 RLS 约束。 +- If allowing writes via an updatable view, add `WITH [LOCAL|CASCADED] CHECK OPTION` and complementary RLS on base tables to ensure only allowed rows can be written/changed. +- 如果通过可更新的 view 允许写入,添加 `WITH [LOCAL|CASCADED] CHECK OPTION` 并在基础表上配套设置 RLS,以确保只能写入/修改被允许的行。 +- In Supabase, avoid granting `anon`/`authenticated` any write privileges on views unless you have verified end-to-end behavior with tests. +- 在 Supabase 中,除非通过测试验证了端到端行为,否则不要授予 `anon`/`authenticated` 对 view 的任何写权限。 + +Detection tip: +- 检测提示: +- From `anon` and an `authenticated` test user, attempt all CRUD operations against every exposed table/view. Any successful write where you expected denial indicates a misconfiguration. +- 使用 `anon` 和 `authenticated` 测试用户,对每个公开的表/视图尝试所有 CRUD 操作。任何本应被拒绝但实际成功的写操作都表明存在配置错误。 + +### OpenAPI-driven CRUD probing from anon/auth roles + +PostgREST exposes an OpenAPI document that you can use to enumerate all REST resources, then automatically probe allowed operations from low-privileged roles. + +PostgREST 会暴露一个 OpenAPI 文档,可用于枚举所有 REST 资源,然后自动探测低权限角色允许的操作。 + +Fetch the OpenAPI (works with the public anon key): +```bash +curl -s https://.supabase.co/rest/v1/ \ +-H "apikey: " \ +-H "Authorization: Bearer " \ +-H "Accept: application/openapi+json" | jq '.paths | keys[]' +``` +探测模式(示例): +- 读取单行(根据 RLS 预期返回 401/403/200): +```bash +curl -s "https://.supabase.co/rest/v1/?select=*&limit=1" \ +-H "apikey: " \ +-H "Authorization: Bearer " +``` +- 测试 UPDATE 被阻止(使用不存在的 filter 来避免在测试期间更改数据): +```bash +curl -i -X PATCH \ +-H "apikey: " \ +-H "Authorization: Bearer " \ +-H "Content-Type: application/json" \ +-H "Prefer: return=minimal" \ +-d '{"__probe":true}' \ +"https://.supabase.co/rest/v1/?id=eq.00000000-0000-0000-0000-000000000000" +``` +- 测试 INSERT 被阻止: +```bash +curl -i -X POST \ +-H "apikey: " \ +-H "Authorization: Bearer " \ +-H "Content-Type: application/json" \ +-H "Prefer: return=minimal" \ +-d '{"__probe":true}' \ +"https://.supabase.co/rest/v1/" +``` +- 测试 DELETE 被阻止: +```bash +curl -i -X DELETE \ +-H "apikey: " \ +-H "Authorization: Bearer " \ +"https://.supabase.co/rest/v1/?id=eq.00000000-0000-0000-0000-000000000000" +``` +Recommendations: +- 将之前的探测针对 `anon` 和最低权限的 `authenticated` 用户自动化,并将其集成到 CI 中以捕捉回归。 +- 将每个暴露的 表/视图/函数 视为一级攻击面。不要假设视图会“继承”其基表相同的 RLS 姿态。 + +### 密码与会话 + +可以指定最小密码长度(默认)、密码要求(默认没有)并禁止使用 leaked passwords。\ +建议 **改进默认的密码要求,因为默认设置较弱**。 + +- User Sessions: 可以配置用户会话的工作方式(超时、每个用户 1 个会话...) +- Bot and Abuse Protection: 可以启用 Captcha。 + +### SMTP Settings + +可以设置 SMTP 来发送邮件。 ### 高级设置 - 设置访问令牌的过期时间(默认 3600) -- 设置检测和撤销潜在被泄露的刷新令牌和超时 -- MFA:指示每个用户可以同时注册多少个 MFA 因素(默认 10) -- 最大直接数据库连接:用于身份验证的最大连接数(默认 10) -- 最大请求持续时间:身份验证请求允许持续的最大时间(默认 10 秒) +- 设置以检测并撤销可能被妥协的 refresh tokens 并设置超时 +- MFA:指定每个用户一次可注册多少 MFA 因子(默认 10) +- Max Direct Database Connections:用于 auth 的最大直接数据库连接数(默认 10) +- Max Request Duration:Auth 请求允许的最长时间(默认 10s) ## 存储 > [!TIP] -> Supabase 允许 **存储文件** 并通过 URL 使其可访问(使用 S3 存储桶)。 +> Supabase allows **to store files** and make them accesible over a URL (it uses S3 buckets). - 设置上传文件大小限制(默认 50MB) -- S3 连接通过以下 URL 提供:`https://jnanozjdybtpqgcwhdiz.supabase.co/storage/v1/s3` -- 可以 **请求 S3 访问密钥**,由 `access key ID`(例如 `a37d96544d82ba90057e0e06131d0a7b`)和 `secret access key`(例如 `58420818223133077c2cec6712a4f909aec93b4daeedae205aa8e30d5a860628`)组成 +- The S3 connection is given with a URL like: `https://jnanozjdybtpqgcwhdiz.supabase.co/storage/v1/s3` +- 可以 **request S3 access key**,由 `access key ID`(例如 `a37d96544d82ba90057e0e06131d0a7b`)和 `secret access key`(例如 `58420818223133077c2cec6712a4f909aec93b4daeedae205aa8e30d5a860628`)组成 ## Edge Functions -还可以在 supabase 中 **存储秘密**,这些秘密将由 **边缘函数** 访问(可以从网页创建和删除,但无法直接访问其值)。 +可以在 supabase 中 **store secrets**,这些 secrets 将被 **accessible by edge functions**(可以通过 web 创建和删除,但无法直接获取其值)。 + +## 参考资料 + +- [Building Hacker Communities: Bug Bounty Village, getDisclosed’s Supabase Misconfig, and the LHE Squad (Ep. 133) – YouTube](https://youtu.be/NI-eXMlXma4) +- [Critical Thinking Podcast – Episode 133 page](https://www.criticalthinkingpodcast.io/episode-133-building-hacker-communities-bug-bounty-village-getdisclosed-and-the-lhe-squad/) +- [Supabase: Row Level Security (RLS)](https://supabase.com/docs/guides/auth/row-level-security) +- [PostgreSQL: Row Security Policies](https://www.postgresql.org/docs/current/ddl-rowsecurity.html) +- [PostgreSQL: CREATE VIEW (security_invoker, check option)](https://www.postgresql.org/docs/current/sql-createview.html) +- [PostgREST: OpenAPI documentation](https://postgrest.org/en/stable/references/api.html#openapi-documentation) {{#include ../banners/hacktricks-training.md}}