You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
module.exports={asyncrewrites(){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},]},}
/** * 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. */functionsetUpTunnelRewriteRules(userNextConfig: NextConfigObject,tunnelPath: string): void{constoriginalRewrites=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[])=>{constinjectedRewrite={// 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(typeoforiginalRewrites!=='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 itconstoriginalRewritesResult=awaitoriginalRewrites(...args);if(Array.isArray(originalRewritesResult)){return[injectedRewrite, ...originalRewritesResult];}else{return{
...originalRewritesResult,beforeFiles: [injectedRewrite, ...(originalRewritesResult.beforeFiles||[])],};}};}
其中的重中之重就是這一段:
constinjectedRewrite={// 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',};
Sentry 在 2023 年 11 月 9 號時,在部落格上發布了這篇文章:Next.js SDK Security Advisory - CVE-2023-46729,內容主要在講述 CVE-2023-46729 這個漏洞的一些細節,包含漏洞成因、發現時間以及修補時間等等。
雖然說是在 11/9 正式對外發布漏洞公告,但漏洞其實在 10/31 發佈的 7.77.0 版本已經修復了,有預留一些時間給開發者們來修補漏洞。
接著就來簡單講一下這個漏洞的成因以及攻擊方式。
漏洞分析
在 GitHub 上面也有一個比較偏技術的說明:CVE-2023-46729: SSRF via Next.js SDK tunnel endpoint
可以看到這一段:
在 Sentry 裡面,有一個叫做 tunnel 的功能,這張來自於官方文件的圖完美地解釋了為什麼需要 tunnel:
如果沒有 tunnel 的話,送給 Sentry 的請求會在前端直接透過瀏覽器發送,而這些直接發給 Sentry 的請求可能會被一些 ad blocker 擋住,Sentry 就沒辦法接收到數據。若是有開啟 tunnel,就會變成先將請求發送給自己的 server,再從自己的 server 轉發給 Sentry,這樣就變成了 same-origin 的請求,便不會被 ad blocker 擋住。
在專門提供給 Next.js 使用的 Sentry SDK 中,是用了一個叫做 rewrite 的功能,官方範例如下:
Next.js 的 rewrite 基本上可以分為兩種,internal 跟 external,後者的話其實更像是 proxy 的感覺,可以直接把請求導向到外部網站,然後顯示出 response。
Next.js Sentry SDK 的實作在 sentry-javascript/packages/nextjs/src/config/withSentryConfig.ts:
其中的重中之重就是這一段:
會根據 query string 中的
o
跟p
,決定最後要重新導向的 URL。而這邊的問題是這兩個參數的 regexp 都用了
.*
,也就是說會配對到任何字元,換句話說,底下這個網址:會 proxy 到:
看起來沒什麼問題,但如果是這樣呢?
%23
是#
URL encode 後的結果,最後就會 proxy 到:我們利用了
#
來把原本的 hostname 都當成 hash 的一部分,並且成功更改了 proxy 的目的地。但最前面那個 o 還是有點煩人,不如把它一起消除掉吧!只要在最前面加個@
就行了:會變成:
如此一來,攻擊者就可以利用 o 這個參數更改 proxy 的目的地,將 request 導向至任何地方。剛剛有說過這個 rewrite 功能會將 response 直接回傳,所以當使用者瀏覽:
https://huli.tw/tunnel?o=@example.com%23&p=def
的時候,看到的 response 會是example.com
的結果。也就是說,如果攻擊者把請求導向至自己的網站,就可以輸出
<script>alert(document.cookie)</script>
,就變成了一個 XSS 漏洞。若是攻擊者不是導到自己的網站,而是導向到其他內部的網頁如
https://localhost:3001
之類的,就是一個 SSRF 的漏洞(但目標必須支援 HTTPS 就是了)。至於修復方式的話也很簡單,只要對 regexp 做出一些限制即可,最後 Sentry 是調整成只允許數字:
7.77.0 版本以及之後的版本都已經修復了這個問題。
總結
這個漏洞真的滿簡單而且滿好重現的,只需要找到修復的 commit,看兩眼程式碼大概就能知道怎麼攻擊。
總之呢,在做 URL rewrite 的時候真的必須謹慎一點,不然還滿容易出問題的(尤其是你不只是 rewrite path,而是 rewrite 整個 URL)。
The text was updated successfully, but these errors were encountered: