Strix supabase 技能深度解析:Supabase 安全测试方法论,从 RLS 绕过到 service_role 密钥泄露
【免费下载链接】strixOpen-source AI penetration testing tool to find and fix your app’s vulnerabilities.项目地址: https://gitcode.com/GitHub_Trending/strix/strix
Strix 内置的supabase技能(supabase.md)是其 Skills 体系中专攻 Supabase 平台的安全测试知识包,覆盖 Row Level Security 失守、PostgREST 越权、RPC 函数滥用、Storage 策略失配、Edge Functions 信任边界与service_role密钥泄露等核心风险面。本文完整继承该技能文档的攻击面模型、端点架构、逐类漏洞测试请求与验证要求,并结合 Strix 源码说明该技能如何被解析、注入 Agent 系统提示词并被执行,帮助读者既掌握一套可复制的 Supabase 渗透测试方法,也理解 AI 渗透工具如何把这类领域知识工程化。
一、技能定位:Supabase 是 Strix technologies 类别下的专项知识包
Strix 的 Skills 是“专业知识包”(knowledge packages):每个技能是一个带 YAML frontmatter 的 Markdown 文件,按需注入 Agent 的上下文,把通用大模型转化为针对特定漏洞类别、协议、框架或第三方平台的专家。官方文档 skills.mdx 在 Technologies 类别中将其描述为:
| Skill | Coverage |
|---|---|
supabase | Supabase RLS bypasses, auth issues |
该技能文件的 frontmatter 声明了元数据:
--- name: supabase description: Supabase security testing covering Row Level Security, PostgREST, Edge Functions, and service key exposure ---从源码结构看,技能文件遵循<root>/<category>/<name>.md的目录约定,内置技能位于 strix/skills/ 下,technologies与vulnerabilities、protocols、tooling、cloud等并列;skills README 对各类别的分工给出了总览,其中/technologies即“针对 Supabase、Firebase、Auth0 等第三方服务的专项技术”。
二、Supabase 攻击面与端点架构
技能开篇即给出 Supabase 安全测试要聚焦的五个风险点:失域(mis-scoped)RLS 策略、不安全的 RPC 函数、泄露的service_role密钥、宽松的 Storage 策略,以及不校验签发方/受众/租户就信任请求头的 Edge Functions。
2.1 攻击面划分
| 面 | 覆盖内容 |
|---|---|
| 数据访问 | PostgREST(表 CRUD、过滤器、嵌入、RPC 远程函数)、GraphQL(pg_graphql 基于 Postgres schema 并与 RLS 交互)、Realtime(复制订阅、broadcast/presence 频道) |
| 存储 | Buckets、对象、签名 URL、public/private 策略 |
| 认证 | Auth(GoTrue):JWT、cookie/session、magic links、OAuth 流程 |
| 服务端 | Edge Functions(Deno):持有密钥调用 Supabase 的服务端代码 |
2.2 端点模型
所有请求都围绕<ref>(项目引用)展开:
| 服务 | 端点 |
|---|---|
| REST | https://<ref>.supabase.co/rest/v1/<table> |
| RPC | https://<ref>.supabase.co/rest/v1/rpc/<fn> |
| Storage | https://<ref>.supabase.co/storage/v1 |
| GraphQL | https://<ref>.supabase.co/graphql/v1 |
| Realtime | wss://<ref>.supabase.co/realtime/v1 |
| Auth | https://<ref>.supabase.co/auth/v1 |
| Functions | https://<ref>.functions.supabase.co/ |
请求头只有两个关键身份要素:
apikey: <anon-or-service>—— 仅用于标识项目(project-scoped),不是用户身份;Authorization: Bearer <JWT>—— 绑定用户上下文,用户级鉴权真正发生在这里。
角色模型分三层:
anon、authenticated—— 标准角色,受 RLS 约束;service_role——绕过 RLS,绝不应出现在客户端可见的位置(前端 bundle、API 响应、错误栈)。
技能文档反复强调的一条关键原则是:
auth.uid()从 JWT 中返回当前用户 UUID。策略(policy)绝不能信任客户端提供的 ID 而忽视服务端上下文。
这条原则是后文所有 RLS/RPC/租户隔离测试的判据。
2.3 高价值目标清单
测试前优先锁定以下对象:
- 含敏感数据的表(users、orders、payments、PII);
- RPC 函数(尤其是
SECURITY DEFINER的); - 存放私有文件的 Storage buckets;
- 持有
service_role访问权限的 Edge Functions; - 生成签名输出的导出/报表端点;
- 管理员/员工路由与授予权限的端点。
三、侦察:枚举面与获取主体
3.1 枚举表面
按技能文档给出的路径模式逐一探测:
/rest/v1/<table> /rest/v1/rpc/<fn> /storage/v1/object/public/<bucket>/ /storage/v1/object/list/<bucket>?prefix= /graphql/v1 /auth/v13.2 获取测试主体(Principals)
- 未认证身份(仅 anon key);
- 普通用户 A、用户 B(两个横向对比账号);
- 管理员/员工身份(若可获取);
- 检查
service_role密钥是否已泄露在客户端 bundle 或 Edge Function 响应中——一旦发现,整个 RLS 防线等于不存在,应第一时间按最高优先级记录。
后续所有测试都围绕“资源 × 操作 × 主体”的矩阵展开。
四、核心漏洞测试方法
4.1 Row Level Security(RLS)
基线要求:每一张非公共表都必须启用 RLS;未启用或存在“全放行”(permit-all)策略即意味着批量数据暴露。
常见缺口
- 策略只对 SELECT 检查
auth.uid(),却忘了 UPDATE/DELETE/INSERT; - 缺少租户约束(
org_id/tenant_id),导致跨租户访问; - 策略依赖客户端传入的列(如 payload 里的
user_id)而非 JWT 身份; - 复杂 join 场景下策略在过滤器之后才生效,可通过计数差推断数据。
测试请求
# 对比两个用户可见行数 GET /rest/v1/<table>?select=*&Prefer=count=exact # 跨租户探测 GET /rest/v1/<table>?org_id=eq.<other_org> GET /rest/v1/<table>?or=(org_id.eq.other,org_id.is.null) # 写路径 PATCH /rest/v1/<table>?id=eq.<foreign_id> DELETE /rest/v1/<table>?id=eq.<foreign_id> POST /rest/v1/<table> # 携带外部的 owner_id4.2 PostgREST 与 REST 层
过滤器面:eq、neq、lt、gt、ilike、or、is、in;关系嵌入select=*,profile(*)会在 resolver 跳过逐行检查时造成过度取数(overfetch);宽松的LIKE/ILIKE过滤叠加缺失 RLS 会经通配查询导致批量泄露。
关键请求头
| 请求头 | 作用 |
|---|---|
Prefer: return=representation | 回显写入结果 |
Prefer: count=exact | 通过计数暴露数据存在性 |
Accept-Profile/Content-Profile | 选择 schema |
IDOR 模式
/rest/v1/<table>?select=*&id=eq.<other_id> /rest/v1/<table>?select=*&slug=eq.<other_slug> /rest/v1/<table>?select=*&email=eq.<other_email>批量赋值(Mass Assignment):若接口未走 RPC 收敛,PATCH 可能更新到不该更新的列;需确认受限列是否通过数据库权限/策略真正锁死,而不只是接口层过滤。
4.3 RPC 函数
RPC 端点映射到 SQL 函数:SECURITY DEFINER以属主权限运行,会绕过 RLS(除非函数体内做了严谨检查);SECURITY INVOKER则以调用者权限运行、尊重 RLS。
反模式
SECURITY DEFINER+ 缺少属主校验 → 垂直/水平越权;set search_path暴露给 public,函数解析到不安全对象;- 信任客户端传入的
user_id/tenant_id而非auth.uid()。
测试
# 以不同用户身份、携带外部 ID 调用 POST /rest/v1/rpc/<fn> {"user_id": "<foreign_id>"} # 直接移除 JWT 身份 Authorization: Bearer <anon_token>验证要点:函数是否在 SQL 内部做了显式的属主/租户检查。
4.4 Storage
对象存放在storage.objects表上,受类 RLS 策略约束。
常见失配
# 公开桶里放了敏感数据 GET /storage/v1/object/public/<bucket>/<path> # 无认证列前缀 GET /storage/v1/object/list/<bucket>?prefix= # 签名 URL 跨租户/跨路径复用Content-Type 滥用:上传 HTML/SVG 后被以text/html或image/svg+xml返回,需检查X-Content-Type-Options: nosniff与Content-Disposition: attachment是否生效。
路径混淆:大小写混用、URL 编码、..段可能在 UI 被拒绝却被 API 接受——重点测试客户端校验与服务端路径归一化之间的差异。
4.5 Realtime
端点为wss://<ref>.supabase.co/realtime/v1。风险点:
- 频道名由表/schema/过滤器派生,RLS 或频道守卫弱时会泄露其他用户的更新;
- broadcast/presence 频道允许未认证跨房间加入/发布。
测试方法:订阅受保护表的public:realtime变更,确认可见性与 RLS 一致;尝试加入他人频道(room:<user_id>、org:<org_id>)。
4.6 GraphQL
端点/graphql/v1(pg_graphql,同样受 RLS 约束)。风险:introspection 泄露 schema 关系;嵌套关系过度取数;全局 node ID 泄露后可被其他视图者复用。
测试方法:对同一主体与同一查询形态,对比 REST 与 GraphQL 的响应;查询深层嵌套字段,验证 RLS 在每一层边上都成立。
4.7 认证与令牌
GoTrue 签发的 JWT 携带sub=uid、role、aud=authenticated等声明。验证要求覆盖签发方(issuer)、受众(audience)、过期、签名与租户上下文。
常见陷阱
- 令牌存 localStorage → 可被 XSS 窃取;
- 把
apikey当身份用(它只是项目作用域标识); service_role密钥暴露在前端 bundle 或 Edge Function 响应中;- Refresh token 管理失当导致会话远超预期 TTL。
测试:把令牌跨服务重放,检查 audience/issuer 是否被固定;用降级令牌(过期/其他受众)打自定义端点。
4.8 Edge Functions
Deno 函数通常以service_role初始化 Supabase 客户端。风险:
- 不校验 JWT 的签发方/受众就信任 Authorization/apikey 头;
- CORS 通配 origin 带凭证、响应中反射 Authorization;
- fetch 触发 SSRF;错误栈或日志暴露密钥。
测试
- 带/不带 Authorization 分别调用函数,对比行为差异;
- payload 中塞入外部资源 ID,验证服务端是否从 JWT 重新推导用户/租户;
- 尝试通过函数内 fetch 触达内部端点(如元数据服务)。
4.9 租户隔离
每条查询都应 join 或过滤 JWT 上下文派生的tenant_id/org_id,而不是客户端输入。测试时保持 JWT 租户不变,只改子域/请求头/路径中的租户选择器;对导出/报表端点确认查询运行在调用者作用域内。
五、进阶技巧:绕过与盲枚举
绕过技术
- 内容类型切换:
application/json↔application/x-www-form-urlencoded↔multipart/form-data; - 参数污染:JSON/query 中重复键(PostgREST 依解析器取第一个或最后一个);
- GraphQL+REST 对等性探测:保护逻辑常在两条路径间漂移,走较弱的一条;
- 竞态窗口:并行写入以绕过插入后的属主更新。
盲枚举(无响应体差异时的推断)
Prefer: count=exact与 ETag/长度差推断未授权行的存在;- 条件请求(
If-None-Match)检测对象存在性; - Storage 签名 URL 的耗时/长度差,用于区分有效与无效令牌。
六、标准测试流程与配套工具
技能文档给出的六步方法论是整套技能的“执行骨架”:
- Inventory surfaces—— 测绘 REST、Storage、GraphQL、Realtime、Auth、Functions 端点;
- Obtain principals—— 收集 anon、用户 A/B、管理员令牌;检查
service_role泄露; - Build matrix—— 建立“资源 × 操作 × 主体”矩阵;
- REST vs GraphQL—— 双通道测试以发现对等性缺口;
- Seed IDs—— 先从列表/搜索端点收集 ID;
- Cross-principal—— 跨主体交换 ID、租户与传输通道。
工具集
| 领域 | 工具与做法 |
|---|---|
| PostgREST | httpie/curl + jq;枚举表;fuzz 过滤器(or=、ilike、neq、is.null) |
| GraphQL | graphql-inspector、voyager;深度查询验证字段级强制 |
| Realtime | 自定义 ws 客户端;订阅可疑频道;按主体 diff payload |
| Storage | 枚举桶列 API;脚本化签名 URL 模式 |
| Auth/JWT | jwt-cli/jose 校验 audience/issuer;向 Edge Functions 重放 |
| 策略 diff | 按角色维护请求集,跨版本对比结果 |
验证要求(报告成立门槛)
- REST/GraphQL 上“属主 vs 非属主”请求,展示未授权访问(内容或元数据层面);
- 失域 RPC 或 Storage 签名 URL 可被另一用户/租户使用;
- Realtime 或 GraphQL 暴露与缺失的策略检查一一对应;
- 附带最小可复现请求,并记录所用角色上下文。
七、Strix 如何加载并执行这个技能
理解了技能内容,再看 Strix 如何把它变成 Agent 的能力。整个链路在源码中清晰可查:
7.1 解析与加载:strix/skills/__init__.py
load_skills(skill_names)按strix/skills/<category>/<name>.md解析技能,支持裸名supabase或限定名technologies/supabase,解析后剥掉 frontmatter 只注入 Markdown 正文(init.py 中的load_skills与_parse_skill_content);validate_requested_skills对每次请求做三重校验:单 Agent 最多 5 个技能、名称必须存在、跨类别重名时必须用category/name限定;get_available_skills()按类别聚合所有技能的 name + description,供系统提示词中的<available_skills>清单渲染。
7.2 注入系统提示词:strix/agents/prompt.py
render_system_prompt调用_resolve_skills构建有序技能列表:调用方显式请求的技能在前,随后固定追加scan_modes/<mode>、tooling/agent_browser、tooling/python、analysis/counterevidence、analysis/severity_calibration等,根 Agent 还会追加coordination/root_agent(prompt.py)。技能正文经 Jinja 模板渲染进系统提示词,位于 system_prompt.jinja 的<specialized_knowledge>区块中,每个技能独立包在<supabase>等标签内;未预载的技能则列在<available_skills>清单里,由 Agent 运行时按需拉取。
7.3 运行时拉取与子代理分配
两条路径都能让supabase技能生效:
- 临时内联:Agent 在动手测试前调用 load_skill 工具,技能正文作为工具结果直接进入对话上下文(“no permanent prompt change, just in-conversation reference”);
- 永久分配:根 Agent 通过
create_agent(task=..., skills=[...])生成专职子代理时传入技能列表,子代理系统提示词会完整内联该技能。系统提示词中的多代理规则明确要求“每个 Agent 1-3 个相关技能、最多 5 个”,并给出了类似"Auth Testing Agent" with skills: authentication_jwt, business_logic的专项化范例——对 Supabase 目标,一个典型的委派形态就是“Supabase 数据面 Agent(skills:supabase,idor)”加“Supabase 服务端 Agent(skills:supabase,ssrf)”这样的拆分。
build_strix_agent(factory.py)负责把 skills 参数传给render_system_prompt,并统一挂载think、load_skill、create_vulnerability_report、record_coverage等基础工具——也就是说,技能文档第六节的“最小可复现请求 + 角色上下文”验证要求,最终由报告工具create_vulnerability_report的字段强制落地,且报告与修复(fix_before/fix_after)在同一步骤内完成。
八、实战入口:在 Strix 中发起一次 Supabase 目标扫描
该技能的适用前提:目标是自托管 Supabase 实例或*.supabase.co项目,且测试已获授权。Strix 的扫描入口按 scan-modes.mdx 提供三档深度,deep 为默认模式:
# 对本地代码 + Supabase 部署目标做深度扫描(白盒 + 动态验证) strix --target ./app --scan-mode deep # CI 场景的快速冒烟 strix --target https://<ref>.supabase.co --scan-mode quick结合技能文档的方法论,一次典型的 Strix 运行会呈现如下轨迹:根 Agent 只做编排(系统提示词中的 root_agent_directive 明确禁止根 Agent 亲自发 payload),委派子代理按“侦察枚举端点 → 收集 anon/用户 A/B 主体 → 建立资源×操作×主体矩阵 → RLS/RPC/Storage/Realtime/GraphQL 逐面验证 → 用create_vulnerability_report提交带最小可复现请求的报告”的流程执行,每个面(包括干净结果)都会写入record_coverage台账。白盒场景下,仓库中的 supabase client 初始化代码(哪一侧持有service_role)、migration 里的 RLS 策略定义,会成为动态验证的优先线索——这正是技能“策略必须从 JWT 而非客户端输入推导身份”原则的源码级印证方式。
九、小结
supabase技能的价值在于把 Supabase 特有的信任边界(apikey 与 JWT 的分层、auth.uid()作为唯一身份源、service_role的绝对隔离、RLS 覆盖全部 DML 动词)翻译成了一份可直接执行的测试清单:七个端点、九类漏洞面、每类都配了请求级 PoC 与验证门槛。对使用者,它是 Strix 子代理的“领域手册”;对维护者,strix/skills/technologies/supabase.md 本身就是一个符合 Skills 规范 的模板——frontmatter 元数据、攻击面/方法论/绕过/验证的章节骨架,与strix/skills/README.md中对技能结构的定义一一对应。
【免费下载链接】strixOpen-source AI penetration testing tool to find and fix your app’s vulnerabilities.项目地址: https://gitcode.com/GitHub_Trending/strix/strix
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考