iT邦幫忙

2026 iThome 鐵人賽

DAY 28
0
佛心分享-IT 人職涯歷練

從 UI/UX 麻瓜到工程魔法師|從讀懂程式開始系列 第 28 篇

Day 28|只是顯示 HTML,為什麼還要加 iframe sandbox?

  • 分享至 

  • xImage
  •  

前一篇講到:

Frontend 把按鈕藏起來,不代表 Backend 就真的安全。

今天換另一種很容易讓人低估的情況:

我只是把一段 HTML 顯示出來而已,為什麼還要特別防?

例如做 Email Preview:

Backend 回一段 HTML
↓
Frontend 顯示預覽

第一眼看起來很單純。

但只要這段內容是:

<html>
  ...
</html>

就不能只把它當成:

一段普通文字。

因為 HTML 本身可能具有:

Script
Link
Form
Navigation
其他瀏覽器行為

所以「怎麼顯示」本身就是一個安全問題。


先分清楚:文字和 HTML 不一樣

假設:

const content = "<h1>Hello</h1>";

如果當一般文字顯示:

<div>{content}</div>

畫面可能看到:

<h1>Hello</h1>

React 會把它當:

文字

而不是 HTML。

但如果真的想把它 Render 成:

<h1>Hello</h1>

事情就不一樣了。

因為現在 Browser 會開始:

解析這段 HTML。


HTML 不只是樣式

很多人第一次想到 HTML,可能只會想到:

<h1>
<p>
<table>
<img>

但 HTML 文件裡也可能存在:

<script>

或:

<form>

甚至可以包含會導向其他地方的:

<a href="...">

所以如果一段內容來源不完全可信,

直接把它當成頁面的一部分執行,

風險就會提高。


最容易想到的是 XSS

XSS:

Cross-Site Scripting

可以先理解成:

原本只是想顯示內容,結果裡面的惡意程式碼被瀏覽器執行了。

例如概念上:

<script>
  // 惡意程式碼
</script>

如果這種內容真的能在網站環境中執行,

就可能造成安全問題。

所以前端碰到:

HTML String

時,

不能只問:

顯示出來了嗎?

還要問:

Browser 會把裡面的哪些能力一起打開?


為什麼 Email Preview 很適合放進 iframe?

假設 Backend 回:

<html>
  <body>
    <h1>Invoice</h1>
    <p>Hello Customer</p>
  </body>
</html>

如果直接塞進目前頁面的 DOM,

這段 HTML 會跟:

目前 App

混在同一個頁面環境裡。

而 iframe 的概念比較像:

主頁面

┌─────────────────────────┐
│ React App               │
│                         │
│   ┌─────────────────┐   │
│   │ iframe          │   │
│   │                 │   │
│   │ Email HTML      │   │
│   │                 │   │
│   └─────────────────┘   │
│                         │
└─────────────────────────┘

也就是:

把預覽內容放在另一個文件環境裡。


但 iframe 本身不代表安全

這點很重要。

不能變成:

用了 iframe
=
安全

iframe 只提供了一個:

隔離的文件環境

但裡面的內容到底能做什麼,

還要看 iframe 的設定。

這時候就會看到:

sandbox

sandbox 可以先理解成「預設先把能力關掉」

例如:

<iframe sandbox />

可以先把它想成:

這個 iframe 裡面的內容可以顯示,但先限制很多原本可能擁有的能力。

也就是:

先限制
↓
真的需要什麼
↓
再明確開放

這種思路跟權限很像。

不是:

全部開著
↓
再想哪些要關

而是:

預設限制
↓
最小化允許

這其實就是 Least Privilege

資安裡有一個很重要的概念:

Principle of Least Privilege

最小權限原則。

可以先理解成:

只給完成這件事真正需要的能力。

例如:

Email Preview

真正需求可能只是:

顯示文字
顯示排版
顯示圖片

那就不一定需要:

執行 Script
送出 Form
控制主頁面
開新視窗

既然不需要,

就沒有理由全部開放。


sandbox 不是只有開或關

iframe 的 sandbox 可以搭配不同允許條件。

例如概念上可能看到:

<iframe
  sandbox="allow-same-origin"
/>

或其他:

allow-...

設定。

這代表:

我不是把所有限制全部拿掉,而是只針對某些能力開例外。

所以理解 sandbox 時,

不要只記:

有 sandbox = 安全

更重要的是:

到底開放了哪些能力?


為什麼 allow-scripts 要特別小心?

假設:

sandbox="allow-scripts"

代表 iframe 裡的 Script 可以執行。

如果這段 HTML 本來就是:

不完全可信任的內容

那就等於又打開了一個重要能力。

所以真正評估時要問:

這個 Preview 真的需要 JavaScript 嗎?

如果答案是:

不需要

那通常就沒有必要開。


allow-same-origin 也不是一句「圖片需要就加」

這類設定的真正影響要看:

iframe 的來源
內容怎麼載入
頁面 Origin
Cookie / Storage
其他安全設定

所以我現在看到:

allow-same-origin

不會把它當成固定模板。

而會先問:

為什麼這裡需要?

資安設定如果只是:

別的地方有,所以我複製

其實很危險。


iframe 裡的圖片又是另一個問題

Email HTML 很常有:

<img src="...">

有些 Email 裡的圖片不是一般公開網址,

而是:

cid:

例如:

<img src="cid:logo">

這種圖片原本是在 Email MIME 結構裡,

不是普通 Web URL。

所以如果 Backend 回:

HTML
+
CID Image Data

Frontend 可能還需要把:

cid:xxx

轉成 Browser 真正能顯示的來源。


這時候就不能把「HTML 預覽」想得太單純

完整流程可能是:

Backend
↓
Email HTML
+
CID Images
↓
Frontend
↓
處理圖片來源
↓
iframe
↓
sandbox
↓
Preview

也就是:

顯示一封 Email Preview,本身其實跨了好幾個層次。


為什麼不能直接用 dangerouslySetInnerHTML?

React 有:

dangerouslySetInnerHTML

它可以把 HTML String 直接塞進 DOM。

例如:

<div
  dangerouslySetInnerHTML={{
    __html: html,
  }}
/>

名字其實已經很直接了:

dangerously

因為它會繞過 React 原本把內容當純文字處理的保護。

如果:

HTML 來源不可信

而且又沒有正確做:

Sanitization

就可能帶來 XSS 風險。


iframe sandbox 和 Sanitization 不是完全同一件事

這裡也不能混在一起。

Sanitization

比較像:

把不允許的 HTML / attribute / script 移除或清理。

而:

iframe sandbox

比較像:

限制這份文件執行時能做什麼。

所以:

一個處理內容
一個限制執行環境

兩者解決的角度不同。

實際系統要不要兩個都用,

要看內容來源、需求和安全模型。


「Backend 回的 HTML」也不代表一定可信

這個觀念很重要。

很容易產生一種直覺:

這是我們自己的 Backend 回來的
↓
所以一定安全

但 Backend 裡的內容可能又來自:

使用者輸入
外部 Email
第三方資料
資料庫舊資料
Template + Dynamic Content

所以真正要問的是:

這段 HTML 最原始的來源是什麼?

而不是只看:

最後是哪支 API 回來的。


這又回到「資料來源」的問題

前面 Debug 我們一直問:

這份資料從哪來?

到了 Security,

一樣要問。

例如:

HTML
↓
API Response
↓
Backend
↓
Database
↓
User-generated Content

如果最上游其實是不可信資料,

那中間經過:

Backend

並不會自動把它變安全。


我現在看到 HTML Preview,會問這些問題

① HTML 從哪裡來?

② 內容可以被誰修改?

③ 裡面需要 Script 嗎?

④ 需要 Form 嗎?

⑤ 需要 Navigation 嗎?

⑥ 圖片來源怎麼處理?

⑦ 是否需要 Sanitization?

⑧ iframe sandbox 開了哪些能力?

這些問題比:

iframe 有沒有成功顯示?

更完整。


資安很多時候是在處理「邊界」

前一天:

Frontend
↓
Backend

我們說:

Client 不能被當成可信任邊界。

今天:

App
↓
External / Dynamic HTML

也是一個邊界。

所以:

來源 A 的內容
↓
進入來源 B 的執行環境

就應該多想一次:

它到底被允許做什麼?


「能 Render」只是第一步

例如 Email Preview 完成時:

HTML 看得到
圖片看得到
排版正常

功能面已經成功。

但工程上還要再多一層:

這份內容會不會執行不需要的能力?

這就是:

Feature Correctness

和:

Security

的差異。


今天真正想記的是

只是顯示 HTML
≠
HTML 沒有風險

以及:

iframe
≠
自動安全

真正重要的是:

內容來源
+
執行環境
+
允許能力

而:

sandbox

背後的核心不是:

加一個 attribute 就安全了。

而是:

預設限制能力,只開真正需要的部分。

這跟昨天的 Backend Authorization 其實是同一個思路:

不要因為東西「看起來只能這樣用」
就相信它一定只會這樣被用。

Frontend Button:

看不到
≠
不能呼叫 API

HTML Preview:

只是拿來看
≠
內容只能被動顯示

所以 Security 很多時候不是:

「壞人會怎麼攻擊?」

而是先從一個更基本的問題開始:

我現在到底給了這份資料多少能力?

下一篇就可以正式把前 28 天全部收進一套方法:

看到 Bug 時,我現在不再第一時間改程式碼了。


上一篇
Day 27|把按鈕藏起來就算有權限控管嗎?Frontend Permission 和 Backend Authorization 到底差在哪?
系列文
從 UI/UX 麻瓜到工程魔法師|從讀懂程式開始 共 28 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言