跨網站要求偽造 (也稱為 XSRF 或 CSRF),是一種挾制用戶在當前已登入的Web應用程式上執行非本意的操作的攻擊方法。這些攻擊之所以可能是因為Web瀏覽器會在每次向網站發送請求時自動發送某些類型的身份驗證Token,也被稱為 one-click attack 或者 session riding。簡單說,是攻擊者通過一些技術手段欺騙用戶的瀏覽器去存取一個自己曾經認證過的網站並執行一些操作(如發郵件,發訊息,甚至財產操作如轉帳和購買商品)。由於瀏覽器曾經認證過,所以被存取的網站會認為是真正的用戶操作而去執行。
跟跨網站腳本(XSS)相比,XSS 利用的是用戶對指定網站的信任,CSRF 利用的是網站對用戶網頁瀏覽器的信任。
以下範例說明CSRF的攻擊方式:
假設今天使用者操作某間銀行轉帳操作的URL位址如下: https://sample.bank.com/transfer?account=AccountName&amount=1000&to=PayeeName
接著使用者訪問了惡意網站 https://bad-crook-site.com/
此網站埋入了一段代碼 <img src="https://sample.bank.com/transfer?account=ATai&amount=1000&to=Badman" />
如果有帳戶名為ATai的用戶存取了此惡意網站,而他之前剛存取過銀行不久,登入資訊尚未過期,那麼他就會損失1000元。
某些攻擊會以使用HTTP GET Request的端點為目標,在這種情況下,可以使用來執行動作。這種形式的攻擊常見於允許image但封鎖 JavaScript 的論壇網站。使用GET作為敏感資訊傳遞的網站應用程式很容易受到惡意攻擊。取得變更狀態的Request不安全。最佳做法是永遠不要變更 GET 要求的狀態。
這種惡意的攻擊方式並不侷限範例GET的方式,也可能在網站內放入隱藏表單:
<form action="https://sample.bank.com/transfer" method="post">
<input type="hidden" name="account" value="ATai">
<input type="hidden" name="amount" value="1000">
<input type="hidden" name="to" value="Badman">
</form>
這種惡意的網址可以有很多種形式,藏身於網頁中的許多地方。此外,攻擊者也不需要控制放置惡意網址的網站。例如他可以將這種位址藏在論壇,部落格等任何用戶生成內容的網站中。這意味著如果伺服器端沒有合適的防禦措施的話,用戶即使存取熟悉的可信網站也有受攻擊的危險。
透過例子能夠看出,攻擊者並不能通過CSRF攻擊來直接獲取用戶的帳戶控制權,也不能直接竊取用戶的任何資訊。他們能做到的,是欺騙用戶的瀏覽器,讓其以用戶的名義執行操作。
ASP.NET Core 使用 ASP.NET Core Data Protection 來實行 antiforgery。 資料保護堆疊必須設定為可在伺服器陣列中運作。
在呼叫下列其中一個API時,Antiforgery Middleware 會新增至DI Container中:
ASP.NET Core 3.0(2.0以上) 中, FormTagHelper 會將 antiforgery token 插入至 HTML 表單元素。
下方範例的 Razor 會自動產生 antiforgery token:
<form method="post">
</form>
產生出的HTML:
<form method="post">
<input name="__RequestVerificationToken" type="hidden" value="CfDJ....UZA">
</form>
當 標籤包含 method="post" 屬性,且 action屬性是空的(action="")或是沒有action屬性,就會自動產生 HTML form 元素的 antiforgery token。
如果想要指定action卻又想加入antiforgery token,可以參考以下做法:
使用HTML helper加入antiforgery token
<form action="CSRFAttack" method="post">
@Html.AntiForgeryToken()
</form>
或是使用HTML helper產生表單的tag
@using (Html.BeginForm("CSRFAttack", "Sercurity"))
{
...
}
都會在表單內加入隱藏欄位
<input name="__RequestVerificationToken" type="hidden" value="CfDJ....UZA">
接著我們需要在後端對Token進行驗證
ASP.NET Core 包含使用 antiforgery token的三個Filters:
以下範例介紹三種Filters的使用方式
將ValidateAntiForgeryToken
套在Action上
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult CSRFAttack(string data)
{
...
}
或是套用在Controller上
[ValidateAntiForgeryToken]
public class SercurityController : Controller
{
...
}
將IgnoreAntiforgeryToken
套在Action上
[IgnoreAntiforgeryToken]
public IActionResult CSRFAttack()
{
...
}
將AutoValidateAntiforgeryToken
套用在Controller上
[AutoValidateAntiforgeryToken]
public class SercurityController : Controller
{
...
}
或是在Startup.ConfigureServices
中進行全域的設定
services.AddControllersWithViews(options =>
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()));
在傳統的 Web 應用程式中,antiforgery token會使用隱藏的表單欄位傳遞到伺服器。在新式以JavaScript 為基礎的應用程式和 Spa 中,許多Request都會透過程式進行發出。 這些 AJAX 要求可能會使用其他技術 (例如要求headers或 cookies) 傳送Token。
如果 cookie 使用來儲存驗證token,以及驗證Server上的 API 要求,CSRF 就是潛在的問題。如果使用本機儲存體來儲存token,可能會減緩 CSRF 弱點,因為來自本機儲存體的值不會在每次發出Request時自動傳送至Server。 因此,建議使用本地存儲在客戶端上存儲將 antiforgery token 儲存在用戶端上,並以Request Header的形式傳送token。
以下範例為使用WebAPI時,建議設定antiforgery token的方式
在Startup.Configure
中新增一個Middleware:
public void Configure(IApplicationBuilder app,
IWebHostEnvironment env,
IAntiforgery antiforgery)
{
...
app.Use(next => context =>
{
string path = context.Request.Path.Value;
if (
string.Equals(path, "/", StringComparison.OrdinalIgnoreCase) ||
string.Equals(path, "/index.html", StringComparison.OrdinalIgnoreCase))
{
var tokens = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken,
new CookieOptions() { HttpOnly = false });
}
return next(context);
});
...
}
並在Startup.ConfigureServices
注入服務
services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
在用戶端,透過JS取得名為XSRF-TOKEN
的cookie內容,並在發出Request時將內容放入名為X-XSRF-TOKEN
的Header中,而後端在接收到要求時透過Antiforgery服務尋找名為X-XSRF-TOKEN
的Header。
參考資料
ASP.NET MVC 防範 CSRF 攻擊 - 在 AJAX 裡使用 AntiForgeryToken 的處理
AntiForgeryToken生成过程解析
防止跨網站偽造要求 (XSRF/CSRF) 攻擊 ASP.NET Core