2023 年 11 月 9 日,Sentry 在其博客上发布了一篇题为Next.js SDK 安全建议 - CVE-2023-46729 的文章。文章讨论了CVE-2023-46729漏洞的详细信息,包括其原因、发现时间和修补时间。
虽然该漏洞于11/9正式公布,但实际上已在10/31发布的7.77.0版本中修复。给开发人员一些时间来修补该漏洞。
下面我们简单讨论一下这个漏洞的成因和攻击方法。
漏洞分析
GitHub 上还有更多技术说明:CVE-2023-46729:SSRF via Next.js SDK 隧道端点:
https://github.com/getsentry/sentry-javascript/security/advisories/GHSA-2rmr-xw8m-22q9你可以看这一段:
Next.js SDK 隧道端点的未经净化的输入允许将 HTTP 请求发送到任意 URL 并将响应反射回用户。在Sentry中,有一个称为“隧道”的功能,官方文档中的这张图片完美地解释了为什么需要隧道:
如果没有隧道,发送到 Sentry 的请求将直接通过前端的浏览器发送。但是,这些直接发送到 Sentry 的请求可能会被广告拦截器拦截,从而导致 Sentry 无法接收数据。如果启用了隧道,请求首先发送到用户自己的服务器,然后转发到Sentry。这样,该请求就成为同源请求,不会被广告拦截器拦截。
在专门为 Next.js 设计的 Sentry SDK 中,使用了一个称为重写的功能。这是官方文档中的一个示例:
module.exports = {async rewrites() {return [{source: '/blog',destination: 'https://example.com/blog',},{source: '/blog/:slug',destination: 'https://example.com/blog/:slug', // Matched parameters can be used in the destination},]},}
Next.js重写可以分为两种:内部重写和外部重写。后者更像是一个代理,因为它可以直接将请求重定向到外部网站并显示响应。
Next.js Sentry SDK 的实现位于sentry-javascript/packages/nextjs/src/config/withSentryConfig.ts中:
/*** Injects rewrite rules into the Next.js config provided by the user to tunnel* requests from the `tunnelPath` to Sentry.** See https://nextjs.org/docs/api-reference/next.config.js/rewrites.*/function setUpTunnelRewriteRules(userNextConfig: NextConfigObject, tunnelPath: string): void {const originalRewrites = userNextConfig.rewrites;// This function doesn't take any arguments at the time of writing but we future-proof// here in case Next.js ever decides to pass someuserNextConfig.rewrites = async (...args: unknown[]) => {const injectedRewrite = {// Matched rewrite routes will look like the following: `[tunnelPath]?o=[orgid]&p=[projectid]`// Nextjs will automatically convert `source` into a regex for ussource: `${tunnelPath}(/?)`,has: [{type: 'query',key: 'o', // short for orgId - we keep it short so matching is harder for ad-blockersvalue: '(?<orgid>.*)',},{type: 'query',key: 'p', // short for projectId - we keep it short so matching is harder for ad-blockersvalue: '(?<projectid>.*)',},],destination: 'https://o:orgid.ingest.sentry.io/api/:projectid/envelope/?hsts=0',};if (typeof originalRewrites !== 'function') {return [injectedRewrite];}// @ts-expect-error Expected 0 arguments but got 1 - this is from the future-proofing mentioned above, so we don't care about itconst originalRewritesResult = await originalRewrites(...args);if (Array.isArray(originalRewritesResult)) {return [injectedRewrite, ...originalRewritesResult];} else {return {...originalRewritesResult,beforeFiles: [injectedRewrite, ...(originalRewritesResult.beforeFiles || [])],};}};}
关键部分是这一部分:
const injectedRewrite = {// Matched rewrite routes will look like the following: `[tunnelPath]?o=[orgid]&p=[projectid]`// Nextjs will automatically convert `source` into a regex for ussource: `${tunnelPath}(/?)`,has: [{type: 'query',key: 'o', // short for orgId - we keep it short so matching is harder for ad-blockersvalue: '(?<orgid>.*)',},{type: 'query',key: 'p', // short for projectId - we keep it short so matching is harder for ad-blockersvalue: '(?<projectid>.*)',},],destination: 'https://o:orgid.ingest.sentry.io/api/:projectid/envelope/?hsts=0',};
它根据o和p查询字符串参数确定要重定向到的最终 URL。
这里的问题是这两个参数都使用.*正则表达式,它匹配任何字符。换句话说,对于以下 URL:
https://huli.tw/tunnel?o=abc&p=def它将代理:
https://oabc.ingest.sentry.io/api/def/envelope/?hsts=0看起来不错,但是如果是这样呢?
https://huli.tw/tunnel?o=example.com%23&p=def%23是 的 URL 编码结果#。它将被代理至:
https://oexample.com#.ingest.sentry.io/api/def/envelope/?hsts=0我们使用#将原始主机名作为哈希的一部分包含在内,并成功更改代理的目的地。然而,领先o有点烦人。@让我们通过在开头添加来摆脱它:
https://huli.tw/[email protected]%23&p=def它成为了:
https://[email protected]#.ingest.sentry.io/api/def/envelope/?hsts=0这样,攻击者就可以使用该o参数来更改代理的目的地,并将请求重定向到任何地方。如前所述,此重写功能直接返回响应。因此,当用户访问时https://huli.tw/tunnel?o=@example.com%23&p=def,他们会看到 的响应example.com。
换句话说,如果攻击者将请求重定向到自己的网站,他们就可以输出<script>alert(document.cookie)</script>,从而将其变成 XSS 漏洞。
如果攻击者将请求重定向到类似 的其他内部网页https://localhost:3001,则成为 SSRF 漏洞(但目标必须支持 HTTPS)。
至于修复,很简单。只需向正则表达式添加一些限制即可。最后,Sentry 将其调整为仅允许数字:
{type: 'query',key: 'o', // short for orgId - we keep it short so matching is harder for ad-blockersvalue: '(?<orgid>\\d*)',},
此问题已在 7.77.0 及更高版本中修复。
结论
该漏洞非常简单且易于重现。只需找到修复提交并查看代码即可了解如何利用它。
总而言之,在进行 URL 重写时,您确实需要谨慎,因为很容易遇到问题(尤其是当您不仅重写路径而是整个 URL 时)。
感谢您抽出
.
.
来阅读本文
点它,分享点赞在看都在这里