1. 為什麼需要防止網頁被惡意嵌入?
在網路世界中,常見一種名為「點擊劫持(Clickjacking)」的攻擊手法:
– 攻擊者建立一個惡意網站。
– 透過 <iframe>
或 <object>
暗中 載入你的網頁,把它藏在惡意網站的某處。
– 然後在網頁上疊加其它元素,誘導使用者 誤點擊。
這樣一來,就可能造成:
– 帳號被盜用
– 信用卡等個資外洩
– 或其他安全相關的嚴重問題
所以,我們想做的事情很簡單:
1. 完全不允許 其他網站把我的網頁用框架(<iframe>
)嵌入。
2. 或者只允許「特定且信任的來源」來嵌入。
常見的做法有兩個:
– 透過 X-Frame-Options
(較早的規範),或
– 透過 CSP (Content-Security-Policy)
裡的 frame-ancestors
。
2. X-Frame-Options
2.1 這是什麼?
X-Frame-Options
是一個 HTTP 響應標頭。一旦瀏覽器接收到這個標頭,就會決定該網頁「能否被他人用框架 (<iframe>
) 方式嵌入」。
– 主流瀏覽器大多支援 X-Frame-Options
。
– 不過設定值相對簡單,只有幾種可用選項。
2.2 常見設定
- DENY
X-Frame-Options: DENY
- 任何網頁都無法把你這個網頁放在
<iframe>
裡。
- 任何網頁都無法把你這個網頁放在
- SAMEORIGIN
X-Frame-Options: SAMEORIGIN
- 只有「同源(同協定 + 同網域 + 同埠號)」的網頁才能用框架嵌入。
- ALLOW-FROM
X-Frame-Options: ALLOW-FROM https://partner.example.com
- 只允許特定網域來嵌入。
- 但是 Chrome 等瀏覽器對此的支援不佳。
2.3 實際專案中怎麼做?
2.3.1 Node.js/Express
const express = require('express');
const app = express();
// 設置 X-Frame-Options: DENY
app.use((req, res, next) => {
res.setHeader('X-Frame-Options', 'DENY');
next();
});
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
- 只要有此設定,瀏覽器就會拒絕其他網頁使用
<iframe>
來載入此頁。
2.3.2 Apache
在 .htaccess
或 Apache 設定檔中加入:
<IfModule mod_headers.c>
Header set X-Frame-Options "DENY"
</IfModule>
- 設定完成後,重啟/重新載入 Apache 即可生效。
3. Content-Security-Policy (CSP) 的 frame-ancestors
3.1 這是什麼?
CSP
是一套更完整、更強大的安全機制,通常可以用來防禦像是「XSS」等攻擊。在它的一系列指令中,frame-ancestors
能做的事就是:
– 控制「哪些網域」可以使用 <iframe>
或 <object>
嵌入我這個頁面。
3.2 常見設定方式
- 全部禁止
Content-Security-Policy: frame-ancestors 'none';
- 任何人都不能用
<iframe>
載入你的頁面(類似X-Frame-Options: DENY
)。
- 任何人都不能用
- 只允許同源
Content-Security-Policy: frame-ancestors 'self';
- 只有「相同網域(含同協定、同埠號)」可以嵌入。
- 允許多個指定網域
Content-Security-Policy: frame-ancestors 'self' https://example.com https://partner.example.com;
- 同源 +
example.com
+partner.example.com
都可以嵌入。
- 同源 +
- 允許任何 HTTPS 網域
Content-Security-Policy: frame-ancestors https:;
- 只要是透過 HTTPS 的網域都能嵌入(比較危險,不建議隨意使用)。
3.3 實際專案中怎麼做?
3.3.1 Node.js/Express
const express = require('express');
const app = express();
// 設定 CSP:只允許同源 + https://example.com
app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy',
"frame-ancestors 'self' https://example.com"
);
next();
});
app.get('/', (req, res) => {
res.send('Hello World! This page cannot be framed by unauthorized sites.');
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
'self'
代表同源https://example.com
是另外指定可嵌入的網域
3.3.2 Nginx
server {
listen 80;
server_name mysite.com;
location / {
add_header Content-Security-Policy "frame-ancestors 'self' https://example.com" always;
try_files uriuri/ =404;
}
}
- 重新載入 Nginx 後,非
'self'
或example.com
的網域想<iframe>
載入mysite.com
,都會被擋下。
4. X-Frame-Options vs. frame-ancestors
4.1 可控性
X-Frame-Options
只能設定DENY
、SAMEORIGIN
、ALLOW-FROM
三種。frame-ancestors
不只可指定多個網域,還可以使用其它更複雜的條件,彈性更高。
4.2 支援度
- 舊版瀏覽器可能對
X-Frame-Options
的相容性較好。 ALLOW-FROM
一直以來在 Chrome 等瀏覽器支援度不佳。- 現代瀏覽器多建議使用
frame-ancestors
。
4.3 實務建議
- 現在的最佳做法:以
CSP frame-ancestors
為主。 - 若需要相容舊環境,則同時也加上
X-Frame-Options
,提供「退回機制」。
5. 進階:同時使用 X-Frame-Options 與 CSP
範例(Node.js/Express):
const express = require('express');
const app = express();
// 同時設定 X-Frame-Options 與 CSP
app.use((req, res, next) => {
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
// 設置完之後,frame-ancestors 仍然是主要依據
res.setHeader('Content-Security-Policy', "frame-ancestors 'self' https://example.com");
next();
});
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
- 新版瀏覽器看到
Content-Security-Policy
會用frame-ancestors
來判斷。 - 舊版瀏覽器或不支援 CSP 的,則用
X-Frame-Options
來判斷。
6. 總結
- 點擊劫持(Clickjacking)透過
<iframe>
的暗中嵌入,可能導致嚴重的帳戶或資安問題。 X-Frame-Options
與CSP frame-ancestors
都能控制「誰能使用<iframe>
嵌入網頁」。X-Frame-Options
功能簡單、但相容度相對較早,frame-ancestors
則更彈性、更現代化。- 建議:
- 以 CSP(frame-ancestors)為主,
- 同時保留
X-Frame-Options
作為相容性備案。
只要善用這兩種機制,就能有效防範點擊劫持,保障網站與使用者的安全。