跨網站腳本 (Cross-Site Scripting, XSS) 攻擊是常見的攻擊手法,有效的阻擋方式是透過網頁內容安全政策 (Content Security Policy, CSP) 規範,告知瀏覽器發出的 Request 位置是否受信任,阻擋非預期的對外連線,加強網站安全性。
本篇將介紹 ASP.NET Core 自製 CSP Middleware 防止 XSS 攻擊。
另外,做範例的過程中,剛好發現 iT 邦幫忙 沒有擋 Clickjacking,所以就順便補充。
iT 邦幫忙 2018 鐵人賽 - Modern Web 組參賽文章:
[Day27] ASP.NET Core 2 系列 - 網頁內容安全政策 (Content Security Policy)
XSS 介紹
攻擊者可能透過任何形式的漏洞,在網站中安插惡意的程式碼,例如:
1 | <script> |
當使用者開啟頁面,Cookie 就被送走了。情境如下:
CSP 介紹
CSP 是瀏覽器提供網站設定白名單的機制,網站可以告知瀏覽器,該網頁有哪些位置可以連、哪些位置不能連。現行大部分的瀏覽器都有支援 CSP,可以從 Can I use Content Security Policy 查看支援的瀏覽器及版本。
CSP 的設定方式有兩種:
- HTTP Header 加入
Content-Security-Policy: {Policy}
當有不符合安全政策的情況,瀏覽器就會提報錯誤, 並終止該行為執行。 - HTTP Header 加入
Content-Security-Policy-Report-Only: {Policy}
當有不符合安全政策的情況,瀏覽器就會提報錯誤, 但會繼續執行 。主要用於測試用,怕網站直接套上 CSP 導致功能不正常。
- HTML 加入
<meta>
在 HTML<head>
區塊加入<meta http-equiv="Content-Security-Policy" content="{Policy}">
。
當有不符合安全政策的情況,瀏覽器就會提報錯誤, 並終止該行為執行。<meta>
的方式不支援 Report-Only 的方式。
CSP 範例
建立一個簡單的範例 HTML,分別載入內外部資源,如下:
Views/Home/Index.cshtml
1 |
|
在未使用 CSP 前,內容都是可以正常顯示,輸出畫面如下:
在 Startup.Configure
註冊一個 Pipeline,把每個 Requset 都加上 CSP 的 HTTP Header,如下:
Startup.cs
1 | using Microsoft.AspNetCore.Builder; |
套用 CSP 後,輸出畫面如下:
CSP 指令 (Directives)
上圖套用 CSP 後,連內部的 IFrame 都不顯示,主要是因為 CSP 指令的關係。
CSP 指令可以限制發出 Request 獲取資源的類型以及位置,指令的使用格式如下:
1 | Response Headers |
以
;
區分多個指令,以空格區分多個白名單位置。
常用的 CSP 指令如下:
default-src
預設所有類型的載入都使用這個規則。connect-src
載入 Ajax、Web Socket 套用的規則。font-src
載入字型套用的規則。frame-src
載入 IFrame 套用的規則。img-src
載入圖片套用的規則。media-src
載入影音標籤套用的規則。如:<audio>
、<video>
等。object-src
載入非影音標籤物件套用的規則。如:<object>
、<embed>
及<applet>
等。script-src
載入 JavaScript 套用的規則。style-src
載入 Stylesheets (CSS) 套用的規則。report-uri
當瀏覽器發現 CSP 安全性問題時,就會提報錯誤給report-uri
指定的網址。
若使用Content-Security-Policy-Report-Only
就需要搭配report-uri
。強烈建議使用回報功能,當被 XSS 攻擊時才會知道。
其他 CSP 指令可以參考 W3C 的 CSP 規範。
每個 CSP 指令可以限制一個或多個能發出 Request 的位置,設定參數如下:
*
允許對任何位置發出 Request。
如:default-src *;
,允許載入來自任何地方、任何類型的資源。'none'
不允許對任何位置發出 Request。
如:media-src 'none';
,不允許載入影音標籤。'self'
只允許同網域的位置發出 Request。
如:script-src 'self';
,只允許載入同網域的*.js
。- URL
指定允許發出 Request 的位置,可搭配*
使用。
如:img-src http://cdn.johnwu.cc https:;
,只允許從http://cdn.johnwu.cc
或其他 HTTPS 的位置載入*.css
。
建立 CSP Middleware
上述 CSP 套用在 Header 的格式實在很容易打錯字,而且又是弱型別,日後實在不易維護。
所以可以自製一個 CSP Middleware 來包裝這 CSP,方便日後使用。
把 CSP 指令都變成強強型,如下:
- CspDirective.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class CspDirective
{
private readonly string _directive;
internal CspDirective(string directive)
{
_directive = directive;
}
private List<string> _sources { get; set; } = new List<string>();
public virtual CspDirective AllowAny() => Allow("*");
public virtual CspDirective Disallow() => Allow("'none'");
public virtual CspDirective AllowSelf() => Allow("'self'");
public virtual CspDirective Allow(string source)
{
_sources.Add(source);
return this;
}
public override string ToString() => _sources.Count > 0
? $"{_directive} {string.Join(" ", _sources)}; " : "";
} - CspOptions.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14public class CspOptions
{
public bool ReadOnly { get; set; }
public CspDirective Defaults { get; set; } = new CspDirective("default-src");
public CspDirective Connects { get; set; } = new CspDirective("connect-src");
public CspDirective Fonts { get; set; } = new CspDirective("font-src");
public CspDirective Frames { get; set; } = new CspDirective("frame-src");
public CspDirective Images { get; set; } = new CspDirective("img-src");
public CspDirective Media { get; set; } = new CspDirective("media-src");
public CspDirective Objects { get; set; } = new CspDirective("object-src");
public CspDirective Scripts { get; set; } = new CspDirective("script-src");
public CspDirective Styles { get; set; } = new CspDirective("style-src");
public string ReportURL { get; set; }
}
然後建立 CSP 的 Middleware,如下:
CspMiddleware.cs
1 | public class CspMiddleware |
再用一個靜態方法包 CSP Middleware,方便註冊使用,如下:
CspMiddlewareExtensions.cs
1 | public static class CspMiddlewareExtensions |
把原本註冊在 Startup.Configure
的 Pipeline 改成用 UseCsp
註冊,如下:
Startup.cs
1 | using Microsoft.AspNetCore.Builder; |
一樣的 CSP 規則,強型別的註冊方式看起來感覺清爽多了。
Clickjacking 攻擊
Clickjacking 是一種透過 IFrame 的偽裝攻擊方式。
攻擊者可以透過嵌入被攻擊目標網頁,偽裝成目標網頁,進而攔截使用者的資料。如下圖:
紅色框現內的 IFrame 用 iT 邦幫忙 的頁面,然後在 Main Frame 透過 JavaScript 攔截使用者的操作事件,範例程式碼:
1 | <head> |
當使用者以為點擊到被攻擊目標,實際上點到的是偽裝的網站,如圖:
X-Frame-Options
Clickjacking 攻擊可以透過 CSP 的 frame-ancestors
防範,但似乎還不是所有瀏覽器都支援 frame-ancestors
,較通用的方式是在 HTTP Header 加上 X-Frame-Options
,通知瀏覽器該頁面是否能被當作 IFrame 使用。
延伸上面 CSP Middleware 的範例,建立一個 FrameOptionsDirective.cs 繼承 CspDirective,如下:
FrameOptionsDirective.cs
1 | public class FrameOptionsDirective : CspDirective |
CspOptions.cs
1 | public class CspOptions |
CspMiddleware.cs
1 | public class CspMiddleware |
Startup.cs
1 | public class Startup |
X-Frame-Options
不支援多個網域,如果要設定多個網域,建議搭配著 CSP 的frame-ancestors
使用。
設定完成後,當被未允許的 Domain 嵌入為 IFrame 頁面時,瀏覽器就提報錯誤。
把上面範例程式碼的 IFrame URL 改為 https://www.google.com.tw/
。
Google 有設定 X-Frame-Options
為 sameorigin
,所以會產生錯誤訊息,如下:
Refused to display ‘
https://www.google.com.tw/
‘ in a frame because it set ‘X-Frame-Options’ to ‘sameorigin’.
參考
USING CSP HEADER IN ASP.NET CORE 2.0
Content Security Policy Level 3
Content-Security-Policy - HTTP Headers 的資安議題 (2)
[翻譯] 我是這樣拿走大家網站上的信用卡號跟密碼的(推薦閱讀)