安全預覽外部網站——代理伺服器+內容淨化+iframe sandbox 的實作
- 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 的情況,正是一個典型的例子。