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作為相容性備案。
只要善用這兩種機制,就能有效防範點擊劫持,保障網站與使用者的安全。
