Translated ['', 'src/pentesting-ci-cd/supabase-security.md'] to zh

This commit is contained in:
Translator
2025-09-29 23:05:50 +00:00
parent 8444f9f9b9
commit f709fc0cdc

View File

@@ -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/<project-id>/settings/database`**
> **这些数据可以通过类似 `https://supabase.com/dashboard/project/<project-id>/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/<project-id>/settings/api`**
> **这些数据可以通过类似 `https://supabase.com/dashboard/project/<project-id>/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 文档,但最有趣的端点是:
<details>
@@ -99,61 +99,181 @@ Priority: u=1, i
```
</details>
因此,每当发现客户使用其被授予的子域名的 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 会对这些用户收费:
<figure><img src="../images/image (1) (1) (1) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>
### 密码和会话
#### Auth: 服务器端注册强制执行
可以指示最小密码长度(默认),要求(默认不要求)并禁止使用泄露的密码。\
建议 **提高要求,因为默认要求较弱**
仅在前端隐藏注册按钮并不足够。如果 **Auth server 仍然允许注册**,攻击者可以使用公共的 `anon` key 直接调用 API 并创建任意用户。
- 用户会话:可以配置用户会话的工作方式(超时,每个用户 1 个会话...
- 机器人和滥用保护:可以启用验证码。
快速测试(来自未认证的客户端):
```bash
curl -X POST \
-H "apikey: <SUPABASE_ANON_KEY>" \
-H "Authorization: Bearer <SUPABASE_ANON_KEY>" \
-H "Content-Type: application/json" \
-d '{"email":"attacker@example.com","password":"Sup3rStr0ng!"}' \
https://<PROJECT_REF>.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: <SUPABASE_ANON_KEY>" \
-H "Authorization: Bearer <SUPABASE_ANON_KEY>" \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d '{"bio":"pwned","avatar_url":"https://i.example/pwn.png"}' \
"https://<PROJECT_REF>.supabase.co/rest/v1/users_view?id=eq.<victim_user_id>"
```
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 <v> SET (security_invoker = on)` so the invokers privileges are used instead of the owners.
- 强制使用 `ALTER VIEW <v> SET (security_invoker = on)`,以便使用调用者的权限而不是所有者的权限。
- On base tables, use `ALTER TABLE <t> FORCE ROW LEVEL SECURITY;` so even owners are subject to RLS.
- 在基础表上使用 `ALTER TABLE <t> 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://<PROJECT_REF>.supabase.co/rest/v1/ \
-H "apikey: <SUPABASE_ANON_KEY>" \
-H "Authorization: Bearer <SUPABASE_ANON_KEY>" \
-H "Accept: application/openapi+json" | jq '.paths | keys[]'
```
探测模式(示例):
- 读取单行(根据 RLS 预期返回 401/403/200
```bash
curl -s "https://<PROJECT_REF>.supabase.co/rest/v1/<table>?select=*&limit=1" \
-H "apikey: <SUPABASE_ANON_KEY>" \
-H "Authorization: Bearer <SUPABASE_ANON_KEY>"
```
- 测试 UPDATE 被阻止(使用不存在的 filter 来避免在测试期间更改数据):
```bash
curl -i -X PATCH \
-H "apikey: <SUPABASE_ANON_KEY>" \
-H "Authorization: Bearer <SUPABASE_ANON_KEY>" \
-H "Content-Type: application/json" \
-H "Prefer: return=minimal" \
-d '{"__probe":true}' \
"https://<PROJECT_REF>.supabase.co/rest/v1/<table_or_view>?id=eq.00000000-0000-0000-0000-000000000000"
```
- 测试 INSERT 被阻止:
```bash
curl -i -X POST \
-H "apikey: <SUPABASE_ANON_KEY>" \
-H "Authorization: Bearer <SUPABASE_ANON_KEY>" \
-H "Content-Type: application/json" \
-H "Prefer: return=minimal" \
-d '{"__probe":true}' \
"https://<PROJECT_REF>.supabase.co/rest/v1/<table_or_view>"
```
- 测试 DELETE 被阻止:
```bash
curl -i -X DELETE \
-H "apikey: <SUPABASE_ANON_KEY>" \
-H "Authorization: Bearer <SUPABASE_ANON_KEY>" \
"https://<PROJECT_REF>.supabase.co/rest/v1/<table_or_view>?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 DurationAuth 请求允许的最时间(默认 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, getDiscloseds 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}}