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>
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.
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.
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.
{:en}In today’s technological age, we typically build our application solutions on event-driven architecture in order…
{:tr} Makalenin ilk bölümünde, Software Supply Chain güvenliğinin öneminden ve containerized uygulamaların güvenlik risklerini azaltabilmek…
{:tr}Bildiğimiz gibi modern yazılım geliştirme ortamında containerization'ın benimsenmesi, uygulamaların oluşturulma ve dağıtılma şekillerini oldukça değiştirdi.…
{:tr}Bildiğimiz gibi bir ürün geliştirirken olabildiğince farklı cloud çözümlerinden faydalanmak, harcanacak zaman ve karmaşıklığın yanı…
{:tr}Bazen bazı senaryolar vardır karmaşıklığını veya eksi yanlarını bildiğimiz halde implemente etmekten kaçamadığımız veya implemente…
{:tr}Bildiğimiz gibi microservice architecture'ına adapte olmanın bir çok artı noktası olduğu gibi, maalesef getirdiği bazı…
View Comments
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.
Teşekkür ederim öncelikle. 1. si bu tarz method'larınız öncelikle neden authentication gerektirmiyor diye sorabilir miyim? 2. olarak da seri bir şekilde aynı method'a request'ler geliyorsa, Web API'da Throttling konusuna bir bakmanızı tavsiye ederim.
Merhabalar kullanıcı login olduktan sonra diğer kullanıcılar için istek gönderemesin istiyorum.
Örneğin login olan kullanıcı 150 id numarasına sahip, aşağıdaki adreste kullanıcı bilgilerini Update ediyorum . localhost:25399/User/Update/150 fakat 151 olarak değiştirdiğimde güncelleme işlemini yinede gerçekleştiriyor. yani 150 nolu idle login olsamda 151 id nolu kullanıcının bilgilerini güncellemiş oluyorum.bunun güvenliğini nasıl sağlayabilirim? kolay gelsin.
Merhaba, buna benzer bir konuyu daha önce cevapladım token based authentication başlığı altında. Kullanıcı token'ı aldıktan sonra claims'e kullanıcının id'sini de eklemelisiniz. Diğer tüm işlemlerde update edilmek istenilen kullanıcı id si ile claims üzerinden gelen id'leri check edebilirsiniz. iyi günler dilerim.
Bu işime çok iyi yaradı gerçekten teşekkür ederim.Projemde json oluştururken classlar üzerinden değilde newtonsoft.json kullanarak string json oluşturuyorum.bunun mimari üzerinde dez avantajı varmıdır.birde classlar üzerinden json oluşturulurken header içerisine contenttype application/json charset=utf-8 otomatik ekleniyor fakat string olarak json oluşturduğumda
contenttype text/plain olarak gözüküyor.her metoda aşağıdaki kodu ekleyerek sorunumu çözüyorum bunu global olarak değiştirebileceğim bi yöntem varmıdır.kolay gelsin.
resp.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
Merhaba class üzerinden json'ı nerede oluşturuyorsunuz da content type text/plain oluyor? call yaptığınız client nedir?
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, ilk olarak @Html.AntiForgeryToken() kodunu form içerisine eklediniz mi? Birde [ValidateAntiForgeryToken] attribute'ünü decorate ettiniz mi acaba?
Merhaba,
Tek sayfada iki tane form düşünürsek
@Html.AntiForgeryToken() tagını iki formun arasınada mı yerleştirlemiliyiz?
Merhaba, evet Asp.NET MVC ilgili token'ı kendisi reuse edecektir.
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
Merhaba çözümün bir kaç farklı yolu var. En basit haliyle API'ınız üzerinden bir token generate ettirip, request'lerde parametre olarak gönderebilirsiniz. Burada detaylı bir link bulunuyor: https://docs.microsoft.com/en-us/aspnet/core/security/anti-request-forgery?view=aspnetcore-2.1