Merhaba arkadaşlar.
Gelen sorular üzerine bu yazımda sizlerle Asp.Net Web API’da Cross-Site Request Forgery(CSRF) saldırılarına karşı güvenliği nasıl ele alabiliriz’i küçük çapta anlatmaya çalışacağım. Zaten bir çoğumuzun Asp.Net MVC‘den AntiForgeryToken ile aşina olduğu bir konu olabilir. AntiForgeryToken implementasyonunu Web API bacağında ise custom olarak kendimiz gerçekleştireceğiz.
Dilerseniz öncelikle CSRF hakkındaki bilgimizi biraz güncelleyelim.
CSRF atağı saldırgan tarafından son kullanıcının kullandığı uygulamada, isteği dışında işlemler yaptırılabilmesidir diyebiliriz.
Bir üstteki resme baktığımızda ise burada Attacker’ın Victim’a bir link gönderdiğini ve burada bir form submit işleminin yapıldığını (isteğinin dışında) görüyoruz. Sonrasında ise Attacker’ın isteği doğrultusunda ilgili Response alındıktan sonra Bank Server’a Victim’ın data+cookie gibi geçerli session bilgileri ile gönderilebildiğini görüyoruz. Biraz uç bir örnek olsada CSRF saldırılarının işleyiş biçimi bu şekildedir diyebiliriz. Denk gelinebilmesi biraz zor bir açık olsa da gerektiği durumlarda önlemini almak iyi olacaktır.
Yukarıdaki uç örneğimizde bulunan akışta, son kullanıcıya saldırgan tarafından tıklatılan en basitinden örnek form’a bakmak gerekirse de:
<h1>Fake Bank Form</h1> <form action="http://www.foobank.com/api/transfer" method="post"> <input type="hidden" name="SessionId" value="123456" /> <input type="hidden" name="Amount" value="100" /> <input type="submit" value="Transfer"/> </form>
1) AntiForgeryToken mantığı nasıl çalışır?
Client tarafında ilgili form’a özel encrypted bir AntiForgeryToken oluşturulur ve bir adette cookie içerisinde oluşturulur. Bu token MVC tarafında aşağıdaki kod satırı ile oluşturulmaktadır.
@Html.AntiForgeryToken()
İlgili form POST edildiğinde ise AntiForgeryToken server tarafında decrypt edilerek cookie’deki değer ile karşılaştırılır ve validation işlemlerinden geçirilir. Bu sayede Cross-Site istekleri engellenmiş olunur.
2) Asp.Net Web API tarafında AntiForgeryToken implementasyonu
Implementasyon işlemini Web API’ın filter’ları ile gerçekleştireceğiz. Bunun için IAuthorizationFilter interface’inden yararlanacağız. Implementasyona başlamadan önce Nuget Package Manager üzerinden “Microsoft.AspNet.WebPages” paketini yüklememiz gerekmektedir. Bu paket yardımı ile “System.Web.Helpers” namespace’i altında bulunan, AntiForgery sınıfını kullanabileceğiz.
ValidateHttpAntiForgeryTokenAttribute isminde bir class oluşturalım ve aşağıdaki gibi kodlayalım.
using System; using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using System.Web.Helpers; using System.Web.Http.Controllers; using System.Web.Http.Filters; namespace AspNetWebAPIAntiForgeryToken.Filters { [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] public sealed class ValidateHttpAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter { public Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation) { try { AntiForgery.Validate(); } catch(Exception ex) { // Loglama işlemleri yapılmalıdır actionContext.Response = new HttpResponseMessage { StatusCode = HttpStatusCode.Forbidden }; return FromResult(actionContext.Response); } return continuation(); } private Task<HttpResponseMessage> FromResult(HttpResponseMessage result) { var taskCompletionSource = new TaskCompletionSource<HttpResponseMessage>(); taskCompletionSource.SetResult(result); return taskCompletionSource.Task; } } }
Oluşturmuş olduğumuz attribute içerisinde ExecuteAuthorizationFilterAsync method’undan yararlanarak, içerisinde AntiForgery.Validate() method’unu çağırıyoruz. Bu method kendisine gönderilen AntiForgeryToken’ı decrypt işleminden geçirerek, validation işlemlerini gerçekleştirmektedir. Eğer validation işlemlerinden geçemezse bu noktada exception’a düşmektedir.
Şimdi bir TestController oluşturalım ve oluşturmuş olduğumuz attribute’ü aşağıdaki gibi kullanalım.
using System.Net; using System.Net.Http; using System.Web.Http; using AspNetWebAPIAntiForgeryToken.Filters; namespace AspNetWebAPIAntiForgeryToken.Controllers { public class TestController : ApiController { [ValidateHttpAntiForgeryToken] public HttpResponseMessage Post() { return Request.CreateResponse(HttpStatusCode.OK); } } }
Asp.Net Web API tarafı şuan CSRF için hazır durumda. Test işlemlerimizi gerçekleştirebilmemiz için, içerisinde POST işlemi gerçekleştirecek olan basit bir form’a sahip MVC uygulaması oluşturalım.
Form’u aşağıdaki gibi düzenleyelim.
<form action="http://localhost:57955/api/test" method="post"> @Html.AntiForgeryToken() <div class="form-horizontal"> <div class="form-group"> Email <div class="col-md-10"> <input type="text" class="form-control" /> </div> </div> <div class="form-group"> New Password <div class="col-md-10"> <input type="text" class="form-control" /> </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="submit" value="Complete" class="btn btn-default" /> </div> </div> </div> </form>
Burada bizim için önemli olan sadece AntiForgeryToken’ı oluşturacak olan “Html.AntiForgeryToken()” kısmıdır. MVC uygulamasını çalıştırıp kaynak kodu görüntülediğimizde ise, her seferinde farklı bir token ürettiğini de görebiliriz.
<input name="__RequestVerificationToken" type="hidden" value="EC5S9Ew_yO9NWtprV9MoDY9-FVsNnbeRXB7glpC3-8qeR7AbA_xvQ-AdsxnIga5an8Pda4eAgG8V9QaeZswvxbcxwhI1" />
Yukarıdaki formun POST edileceği adres, local’imdeki Web API’ı host ettiğim “http://localhost:57955/api/test” adresidir. Sizde bu kısmı kendi local adresiniz ile değiştirebilirsiniz.
İlk denememizi yapabilmek adına dilerseniz her iki projeyi de aynı anda çalıştıralım. Çalıştırdıktan sonra Web API kısmındaki geliştirmiş olduğumuz token’ın “AntiForgery.Validate()” satırına bir break point koyalım ve MVC uygulamasındaki form’da submit işlemini gerçekleştirelim. Bakalım token validation işlemlerinden geçebilecek mi?
Ups! validation işlemi gerçekleşemedi. Bunun sebebi ise oluşturulan AntiForgeryToken’lar “machineKey” içerisindeki “validation” ve “decryption” key bilgilerine göre oluşturulmaktadır tıpkı OAuth 2.0’da olduğu gibi. Bu işlemin iki taraf içinde ortak gerçekleştirilebilmesi yani aynı dilden konuşabilmeleri için aynı “machineKey” bilgilerine sahip olmaları gerekmektedir.
IIS Manager üzerinden hızlıca machineKey bilgilerini oluşturabilirsiniz. Ben her iki uygulamam için “web.config” içerisindeki “system.web” altına ilgili “machineKey” bilgilerini, aşağıdaki gibi tanımlıyorum.
<system.web> <compilation debug="true" targetFramework="4.5.2"/> <httpRuntime targetFramework="4.5.2"/> <machineKey validationKey="994FD669298C7B30E765A8E6118D4140746A83EF0A89F94EBEF02EF2991FA5C808F2DAAD91E8D649DEF9A1F31E398CA1D7C8D2B7A21EB3E81F7A824456BEA1BC" decryptionKey="B550B27413F1DBB46A37CC54620FCF4F78AAE1A22E4AD71DF185EDF4F9AC5726" validation="SHA1" decryption="AES" /> </system.web>
İki uygulamayı şimdi tekrar çalıştırıp, “AntiForgery.Validate()” satırındaki debug noktasına bir kez daha göz atalım.
Bu sefer başarılı bir şekilde validate işleminin gerçekleştiğini görebilmekteyiz.
AntiForgery sınıfının Validate method’unun arkaplanındaki kontrol işleminde ise form arayüzünden POST edilen AntiForgeryToken ile, cookie’ye yazılan token’lar server tarafında decrypt edilerek kontrol edilmektedir ve validation işlemleri gerçekleştirilmektedir.
3) Dikkat edilmesi gereken bir kaç nokta
- Bu işlem sadece POST request’leri için geçerli olmalıdır, GET request’leri için değil
- Eğer sisteminizde herhangi bir Cross Site Scripting(XSS) açığı var ise kolaylıkla token bypass edilebilmektedir
- Cookie aracılığı ile validation işlemleri gerçekleştirildiği için herhangi bir kullanıcının cookie’leri kapalı ise, valid olmayacaktır
Makalemizi burada sonlandırıyoruz ve umarım herkes için faydalı bir makale olmuştur. İlgili örnek projeye aşağıdaki linkten ulaşabilirsiniz.
Elinize sağlık güzel yazı olmuş.benim sorum güvenlikle ilgili. Bütün request’leri validate etmem lazım. kullanıcı sürekli olarak saçma sapan request’lerde bulunduğunda örnek olarak; userId 4608 olsun ve user bilglsini getiren metoda 4608 den başlayıp random sayılar üreterek ( /account/getUserInfo/4609) başka kullanıcıların bilgileri için request’te bulunduklarında güvenliği bir şekilde sağlamam lazım.
yardımcı olursanız sevinirim.
Merhaba machine key oluşturmama rağmen The required anti-forgery cookie “__RequestVerificationToken” is not present. hatası alıyorum. machine keylerde sorun var desem iki server arası authorization yaparken sorun çıkarmıyor. yardımcı olursanız sevinirim.
Merhaba,
Tek sayfada iki tane form düşünürsek
@Html.AntiForgeryToken() tagını iki formun arasınada mı yerleştirlemiliyiz?
Merhaba, eğer Web API kullanan consume eden php, java kullanıyorsa @Html.AntiForgeryToken() bu metodu nasil yapmalılar? Token nasil oluşturulmalı ki, web api kısmı validate etsin. Teşekkürler