防止 ASP.NET MVC 應用程式中的跨網站偽造要求 (CSRF) 攻擊
跨網站偽造要求 (CSRF) 是攻擊,惡意網站會將要求傳送至使用者目前登入的易受攻擊網站
以下是 CSRF 攻擊的範例:
使用者使用表單驗證登入
www.example.com
。伺服器會驗證使用者。 來自伺服器的回應包含驗證 Cookie。
若未登出,使用者就會流覽惡意網站。 此惡意網站包含下列 HTML 格式:
<h1>You Are a Winner!</h1> <form action="http://example.com/api/account" method="post"> <input type="hidden" name="Transaction" value="withdraw" /> <input type="hidden" name="Amount" value="1000000" /> <input type="submit" value="Click Me"/> </form>
請注意,表單動作張貼到易受攻擊的網站,而不是惡意網站。 這是 CSRF 的「跨網站」部分。
使用者按一下 [提交] 按鈕。 瀏覽器包含具有要求的驗證 Cookie。
要求會以使用者的驗證內容在伺服器上執行,而且可以執行已驗證使用者允許執行的任何動作。
雖然此範例需要使用者按一下表單按鈕,但惡意頁面可能就像輕鬆地執行自動提交表單的腳本一樣。 此外,使用 SSL 不會防止 CSRF 攻擊,因為惡意網站可以傳送「HTTPs://」要求。
一般而言,CSRF 攻擊可能會針對使用 Cookie 進行驗證的網站,因為瀏覽器會將所有相關 Cookie 傳送至目的地網站。 不過,CSRF 攻擊不限於惡意探索 Cookie。 例如,基本和摘要式驗證也很容易遭受攻擊。 使用者以基本或摘要式驗證登入之後。 瀏覽器會自動傳送認證,直到會話結束為止。
防偽造權杖
為了協助防止 CSRF 攻擊,ASP.NET MVC 使用反偽造權杖,也稱為 要求驗證權杖。
- 用戶端會要求包含表單的 HTML 頁面。
- 伺服器在回應中包含兩個權杖。 一個權杖會以 Cookie 的形式傳送。 另一個會放在隱藏的表單欄位。 系統會隨機產生權杖,讓敵人無法猜測這些值。
- 當用戶端提交表單時,必須將這兩個權杖傳回伺服器。 用戶端會將 Cookie 權杖當作 Cookie 傳送,並在表單資料內傳送表單權杖。 (當使用者提交 form.) 時,瀏覽器用戶端會自動執行此動作
- 如果要求不包含這兩個權杖,則伺服器不允許要求。
以下是具有隱藏表單標記的 HTML 表單範例:
<form action="/Home/Test" method="post">
<input name="__RequestVerificationToken" type="hidden"
value="6fGBtLZmVBZ59oUad1Fr33BuPxANKY9q3Srr5y[...]" />
<input type="submit" value="Submit" />
</form>
防偽造權杖的運作方式是惡意頁面無法讀取使用者的權杖,因為相同的原始來源原則。 (相同原始來源原則 可防止裝載于兩個不同網站上的檔存取彼此的內容。因此,在先前的範例中,惡意頁面可以將要求傳送至 example.com,但無法讀取 response.)
若要防止 CSRF 攻擊,請使用反偽造權杖搭配任何驗證通訊協定,其中瀏覽器會在使用者登入之後以無訊息方式傳送認證。 這包括 Cookie 型驗證通訊協定,例如表單驗證,以及基本驗證和摘要式驗證等通訊協定。
您應該針對 POST、PUT、DELETE) (任何非安全方法要求防偽權杖。 此外,請確定安全的方法 (GET、HEAD) 沒有任何副作用。 此外,如果您啟用跨網域支援,例如 CORS 或 JSONP,則甚至 GET 之類的安全方法可能容易受到 CSRF 攻擊,讓攻擊者能夠讀取潛在的敏感性資料。
ASP.NET MVC 中的防偽造權杖
若要將防偽造權杖新增至 Razor 頁面,請使用 HtmlHelper.AntiForgeryToken 協助程式方法:
@using (Html.BeginForm("Manage", "Account")) {
@Html.AntiForgeryToken()
}
這個方法會新增隱藏的表單欄位,也會設定 Cookie 權杖。
反 CSRF 和 AJAX
表單權杖可能是 AJAX 要求的問題,因為 AJAX 要求可能會傳送 JSON 資料,而不是 HTML 表單資料。 有一個解決方案是在自訂 HTTP 標頭中傳送權杖。 下列程式碼使用 Razor 語法來產生權杖,然後將權杖新增至 AJAX 要求。 權杖是藉由呼叫 AntiForgery.GetTokens在伺服器上產生。
<script>
@functions{
public string TokenHeaderValue()
{
string cookieToken, formToken;
AntiForgery.GetTokens(null, out cookieToken, out formToken);
return cookieToken + ":" + formToken;
}
}
$.ajax("api/values", {
type: "post",
contentType: "application/json",
data: { }, // JSON data goes here
dataType: "json",
headers: {
'RequestVerificationToken': '@TokenHeaderValue()'
}
});
</script>
當您處理要求時,請從要求標頭擷取權杖。 然後呼叫 AntiForgery.Validate 方法來驗證權杖。 如果權杖無效, Validate 方法會擲回例外狀況。
void ValidateRequestHeader(HttpRequestMessage request)
{
string cookieToken = "";
string formToken = "";
IEnumerable<string> tokenHeaders;
if (request.Headers.TryGetValues("RequestVerificationToken", out tokenHeaders))
{
string[] tokens = tokenHeaders.First().Split(':');
if (tokens.Length == 2)
{
cookieToken = tokens[0].Trim();
formToken = tokens[1].Trim();
}
}
AntiForgery.Validate(cookieToken, formToken);
}