最近在整合其他系統的時候,需要把外部合作廠商的網頁給嵌入,卻遇到了無法嵌入的問題,問了較熟 HTML 的同事時,他問了我一句,iFrame 應該不能任意嵌入其他網頁吧?這樣叫Yahoo, Facebook 他們情何以堪,馬上被當成別人的子網頁...
有鑑於對 iFrame的不熟,所以決定以下做幾個實驗來研究一下
首先我們鎖定Yahoo 跟 Google 做個實驗,先準備一個簡單的 HTML 檔案並試著嵌入兩個外部網頁
<html>
<head>
</head>
<body>
<button onClick="testIframe();">load iframe</button>
this is yahoo frame
<iframe id="yahoo-frame" name="yahoo-frame" scrolling="auto" frameborder="0"></iframe>
this is google frame
<iframe id="google-frame" name="google-frame" scrolling="auto" frameborder="0"></iframe>
<script type="text/javascript">
function testIframe() {
document.getElementById("yahoo-frame").src = 'http://www.yahoo.com';
document.getElementById("google-frame").src = 'http://www.google.com.tw';
}
</script>
</body>
</html>
結果 Yahoo 可以嵌入,但 Google 卻不能
先說結論,每個網頁都應當有權利決定要不要被別人嵌入,或是被特定的 domain 嵌入
而這個結論就是Google 不希望被非 www.google.com 的 domain 的系統所嵌入
而這一切都是 response header 的 X-Frame-Options 所造成的,在最新的網頁規格是使用了 Content Security Policy
甚至在 chrome 的 console 介面會看到這個錯誤
Refused to display 'https://www.google.com.tw/' in a frame because it set 'X-Frame-Options' to 'sameorigin'.
若是設定 Content Security Policy 的話,可以設定至某些允許的 domain 是可以嵌入的
若response header 裡所指定的 特別 domain, 則會出現
Refused to display 'http://domain/xxx.aspx' in a frame because an ancestor violates the following Content Security Policy directive: "frame-ancestors *.someDomain.com
若我把這個 HTML (用 iFrame 包了 Yahoo 與 Google 的 網頁放至某台 Server 的 IIS 站台,並設定 X-Frame-Options 即可
如此一來該 IIS 站台的回應都會在 response header 裡加上 X-Frame-Options
若 A 站台 嵌入了 Yahoo, Google ,且這兩個站台是同時授權能被 A 站台的 domain 嵌入,那麼 A 站台就能正常呈現 iFrame裡的網頁資料
但是 A 站台若沒有在 response header 設定是否能給其他人嵌入的話
B 站台若嵌入了 A站台的網頁,那麼 B 是否也能正常顯示 Yahoo, Google 的網頁,造成了安全性的連鎖效應呢!?
接著來做個簡單的驗證
A': 角色為 facebook, google,在 response header 設定了只要是 *.contoso.com 的 domain 都能嵌入
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Content-Security-Policy" value="frame-ancestors *.contoso.com" />
</customHeaders>
</httpProtocol>
</system.webServer>
</configuration>
並置入 display 網頁
<html>
<head>
</head>
<body>
this is A' site, only authorized to *.test.contoso.com
</script>
</body>
</html>
A: 角色為中繼網頁,用來呈現嵌入 A' ,domain 為 A.contoso.com
iFrame.html
<html>
<head>
</head>
<body>
this is a frame contain A' frame
<iframe id="main-frame" name="main-frame" style="height:600px;width:100%;border:none" scrolling="auto" frameborder="0" src="http://172.17.0.35/display.html" ></iframe>
<script type="text/javascript">
function testIframe() {
document.getElementById("main-frame").src = "http://A'/display.html";
}
</script>
</body>
</html>
從 A 嵌入 A' 的網頁則不會出現問題,因為 Content-Security-Policy 有設定了 *.contoso.com
B: 角色為最終端嵌入者,將 A 的網頁嵌入
若要從 B 直接載入 A' 的網頁,則沒問題,但如果要嵌入 iframe 則會出現問題
果然就出現了Refused to display 'http://A'.Server/display.html' in a frame because an ancestor violates the following Content Security Policy directive: "frame-ancestors *.contoso.com".
但如果直接跳過 A',直接從沒有設定安全性的 A 來嵌入網頁的話呢?
這種偷吃步看起來難不倒 Content-Security-Policy 的設定
參考資料
http://blog.darkthread.net/post-2016-06-12-iframe-clickjacking.aspx