HeatMapXHeatMapX
價格登入

安全預覽外部網站——代理伺服器+內容淨化+iframe sandbox 的實作

HeatMapX Engineering Team5 min read
  • engineering
  • security
  • nextjs
  • iframe

本文重點

  • 若將外部網站載入到自家網域的 iframe 中,外部 JS 有機會碰觸到自家的使用者連線,存在風險
  • 對策是「具備 SSRF 防護的代理伺服器+移除外部 JS+限制型 CSP+iframe sandbox」的多層防禦
  • sandbox 設定過於嚴格時,連自己的腳本也會被擋下。忘記加上 allow-scripts 導致功能失效,就是一個典型的坑

HeatMapX 的 A/B 測試中有一項功能,可以「在套用到目標頁面的狀態下,預覽變更後(Variant B)的畫面」。從技術角度來看,這其實是「在自家網域的 iframe 中,安全地顯示使用者的外部網站」,是意外地棘手的處理。以下分享其設計思路,以及實際踩過的坑。

為什麼單純的實作方式很危險

如果直接取得外部網站的 HTML,並用自家網域(例如 heatmapx.com)原封不動地提供給使用者,那麼該 HTML 中所含的 <script>onclick= 等內容,就會以自家網域的權限執行。如此一來,就有 Cookie 或登入連線資訊被讀取的風險。這是必須避免的情況。

多層防禦架構

因此,我們疊加了以下幾層防護。

1. 具備 SSRF 防護的代理伺服器

驗證要取得的目標網址,拒絕 http/https 以外的協定。此外,也會阻擋 localhost、私有 IP,以及雲端的中繼資料端點(例如 169.254.169.254)。對於重新導向也不自動跟隨,而是以 redirect: 'manual' 逐段重新驗證,避免被導向內部網路。

2. 移除外部 JS(內容淨化)

從取得的 HTML 中移除 <script> 標籤、on*= 內嵌事件處理器,以及 javascript: 協定。因為預覽功能並不需要執行客戶網站的 JS(無法完整重現 SPA 是已知的取捨)。

3. 限制型 CSP 與 base href

<head> 之後立即插入 <base href>,藉此解析相對路徑的圖片與 CSS,同時在回應中附加「不執行外部 JS」的限制型 CSP。圖片與 CSS 則為了顯示效果而予以允許。

4. iframe sandbox

最後,在顯示端的 iframe 上加上 sandbox 屬性以進行隔離。

踩過的坑:sandbox 設定得太嚴格

預覽功能的做法,是在取得的 HTML 中注入一段「套用變更內容(changeSet)的自製小腳本」,並在 iframe 內執行,藉此改變畫面外觀。然而,預覽用 iframe 的 sandbox 屬性只有 allow-same-origin沒有加上 allow-scripts

結果,自製腳本無法執行,導致變更未被套用、顯示的是原始頁面這樣的問題。而編輯用的編輯器端 iframe 則是設定為 allow-scripts allow-same-origin 並正常運作,可見問題根源在於兩者設定不一致。

修正方式很簡單,只要讓預覽端也統一改成 allow-scripts allow-same-origin 即可。同時,我們也新增了驗證 iframe sandbox 屬性的回歸測試。

sandbox="allow-same-origin"  →  sandbox="allow-scripts allow-same-origin"

學到的教訓

  • 嵌入外部內容時,要以「多層防禦」的思維來考量,不要只依賴單一對策。
  • sandbox 並非「加上去就安全」,重點在於只精準允許必要的權限。設定過嚴,連自己的功能都會被擋下。
  • 對於「做相同處理的兩條路徑」(編輯器與預覽),務必確認雙方的安全性屬性是否一致。只修好其中一邊,另一邊就會壞掉。

總結

外部網站的安全預覽,可以透過 SSRF 防護、移除外部 JS、限制型 CSP、iframe sandbox 的多層防禦來實現。而 sandbox 的權限設定,不論是不足還是過多都會出問題。這次忘記加上 allow-scripts 的情況,正是一個典型的例子。

從 Claude Code 執行的熱圖,免費開始。

貼上一行追蹤標籤,從 CLI 取得分析與改善建議。