Open redirect, sitenizde ziyaretçinin tarayıcısını kullanıcı girdisinden alınan bir URL'ye, bu URL'yi doğrulamadan yönlendiren her sayfadır. Önemsiz görünür. Pratikte ise bir saldırganın sizin gerçek alan adınızı gösteren bir phishing bağlantısı göndermesine olanak tanır. OAuth kurulumlarında da yetkilendirme kodlarının ve access token'ların sızmasına yol açabilir.
Yönlendirme neden sadece kozmetik bir hata değildir
CWE-601, bu zafiyeti şöyle tanımlar: web uygulaması, harici bir siteye giden bir bağlantıyı belirten kullanıcı kontrollü girdiyi kabul eder ve ardından bu bağlantıyı bir yönlendirmede kullanır. Kurban, güvendiği sitenize giden bir bağlantıyı izler ve saldırganın kontrol ettiği bir sayfaya düşer. OWASP cheat sheet'i bunun biçimini şöyle gösterir:
http://example.com/example.php?url=http://malicious.example.com
Değiştirilmiş bağlantıdaki sunucu adı orijinal siteyle aynı olduğu için phishing girişimi daha güvenilir görünür. Kullanıcı tarafı için bağlantı denetleyicimize göz atabilirsiniz.
OAuth ile aynı zafiyetin etkisi daha da genişler. RFC 9700, client'ların ve yetkilendirme sunucularının tarayıcıyı bir query parametresinden alınan rastgele URI'lere yönlendiren URL'ler sunmaması gerektiğini (MUST NOT) belirtir, çünkü open redirector'lar yetkilendirme kodlarının ve access token'ların dışarı sızmasını mümkün kılabilir.
Forward'lar ilgili bir başka durumdur. Kullanıcı girdisi dahili bir hedefi belirliyorsa, örneğin function.jsp?fwd=admin.jsp gibi, özel hazırlanmış bir URL erişim kontrolünü aşıp yönetim işlevine ulaşabilir. Uygulama, kullanıcının hedef için yetkili olup olmadığını mutlaka kontrol etmelidir.
Parametreleri bulmak
İsimler değişir: OWASP örneklerinde url, fwd ve returnUrl geçer, sizin kodunuzda ise başkaları olacaktır. İsme değil, kalıba bakın.
| Teknoloji | Tehlikeli kalıp |
|---|---|
| Java | response.sendRedirect(request.getParameter("url")) |
| PHP | header("Location: " . $redirect_url) with $_GET['url'] |
| C# .NET | Response.Redirect(url) with request.QueryString["url"] |
| Rails | redirect_to params[:url] |
| Java forward | getRequestDispatcher(fwd).forward(...) |
grep ile yapılan ilk tarama, okuyacağınız bir liste çıkarır. Yolu ve dosya türlerini kendi repository'nize göre ayarlayın.
grep -rnE 'sendRedirect\(|Redirect\(|redirect_to params|header::LOCATION' src/
grep -rn -A3 'header(.Location' src/
İkinci komut PHP içindir. Her eşleşmeden sonraki satırları okuyun: header("Location... çağrısından sonra exit yoksa betiğin geri kalanı çalışmaya devam eder. Yönlendirmeyi yok sayacak şekilde ayarlanmış bir tarayıcı, bu durumda sayfanın geri kalanını gösterebilir.
.NET ekipleri önce login action'larına bakmalıdır. Cheat sheet'e göre ASP.NET MVC 1 ve 2 siteleri özellikle savunmasızdır ve MVC 2 LogOn örneği hiçbir doğrulama yapmadan returnUrl adresine yönlendirir. Önerilen çözüm MVC 3'e geçmektir.
Eşleşmelerin çoğu zararsız çıkacaktır. Argümanı string literal veya sunucu tarafında bir lookup olmayan her yönlendirmeye tek bir soru sorulur: değer nereden geldi? Cookie'ler, gizli form alanları, header'lar ve veritabanı içeriği de buna dahildir. Pek çok open redirect, geliştiricinin bir cookie'nin ya da gizli alanın değiştirilemeyeceğini varsaymasından doğar.
Bir bulguyu doğrulamak için tek bir sade harici adresle yetinmeyin. Aşağıdakilerin her birini parametreye gönderin ve Location header'ını izleyin:
http://malicious.example.comhttps://yoursite.example@malicious.example(userinfo:yoursite.examplekullanıcı adı olur)https://yoursite.example.malicious.example(sizinkiyle başlayan bir hostname)https://malicious-yoursite.example(sizinkiyle biten bir hostname)
Cheat sheet, userinfo ve yanıltıcı hostname'leri önek ve sonek kontrollerini aşmanın yolları olarak sayar. Sade URL engellenirken bunlardan biri geçiyorsa, kontrol büyük olasılıkla string eşleştirmesine dayanıyordur. Bu bir çıkarımdır, kanıt değildir, ancak her durumda kontrolü değiştirin.
Tarayıcılar (scanner) bunların bir kısmını yakalar. OWASP Web Security Testing Guide, bu tür araçların genel amaçlı olduğunu ve uygulamaya özgü bağlamdan yoksun olduğunu söyler, bu yüzden her bulguyu elle doğrulayın.
Tercih sırasına göre düzeltme
- Kullanıcı girdisine göre yönlendirme yapmayın. Hedef kodda sabitse saldırgan onu değiştiremez.
- ID'leri sunucu tarafında hedeflere eşleyin. Kullanıcı kısa bir ad veya ID gönderir, sunucu tam URL'yi bulur ve geri kalan her şeyi reddeder. ID'lerin sırayla denenememesine dikkat edin, aksi halde bir ziyaretçi tüm hedefleri listeleyebilir.
- Yerel dönüşler için framework yardımcısını kullanın. ASP.NET Core'da bu, yerel olmayan hedefleri reddeden
LocalRedirectveyaIsLocalUrldemektir. - Harici hedefler için allowlist kullanın. Bakımı yapılan bir URL parser kullanın ve ayrıştırılmış scheme, canonical host ve etkin port değerlerini açık bir listeyle karşılaştırın. Yalnızca belirli endpoint'lere izin veriliyorsa path'i de kısıtlayın.
- İsteğe bağlı olarak nonce zorunlu tutun. CWE, her yönlendirme isteğinde tahmin edilemez, uygulama tarafından üretilmiş bir nonce kullanılmasını ve bunu taşımayan isteklerin reddedilmesini tarif eder. XSS bunu aşabilir, bu yüzden nonce bir düzeltmeyi tamamlar, onun yerine geçmez.
Allowlist kontrolü için kurallar:
- Userinfo içeren ve belirsiz girdileri reddedin.
- Yönlendirmeyi, doğruladığınız URL ile yapın; sonradan dönüştürdüğünüz bir sürümle değil.
- Ham önek veya sonek eşleştirmesini asla kullanmayın. Cheat sheet, userinfo ve yanıltıcı hostname'lerin bu tür kontrolleri aştığı bir örnek olarak GitLab'ı gösterir.
Kötü değerlerden oluşan bir denylist zayıf bir birincil savunmadır, ancak CWE onu saldırıları tespit etmek için kabul edilebilir bulur. Bunun yerine "bilinen iyiyi kabul et" yaklaşımını kullanın.
İnsanların sitenizden ayrılması gerekiyorsa, hedefi adıyla gösteren ve tıklama isteyen bir ara sayfa (interstitial) ek bir katman sağlar. Bu sayfayı hazırlarken XSS'ten kaçının.
OAuth tarafı
RFC 9700, yetkilendirme sunucularının client yönlendirme URI'lerini önceden kayıtlı olanlarla birebir string eşleştirmesiyle karşılaştırmasını şart koşar. Tek istisna, native uygulamaların localhost yönlendirme URI'lerindeki port numaralarıdır. Aşağıdaki benim yorumumdur, RFC metni değildir: birebir eşleştirme callback'in kayıtlı olan olduğunu kanıtlar, dolayısıyla kayıtlı host aynı zamanda bir open redirector sunuyorsa kod yoluna devam edebilir. RFC ayrıntı için Bölüm 4.11'e işaret eder. Özetime güvenmeden önce o bölümü okuyun.
Nereden başlamalı
- Yukarıdaki grep kalıplarını kodunuzda çalıştırın ve hedefi bir istekten, cookie'den veya saklanan bir değerden gelen her yönlendirmeyi ve forward'ı listeleyin. Önce login ve OAuth callback handler'larını kontrol edin.
- PHP eşleşmelerinde eksik
exitolup olmadığına, .NET projelerinde ise MVC 1 veya 2 login action'larına bakın. - Her eşleşmeyi yukarıdaki dört girdiyle, userinfo biçimi dahil, test edin ve hangilerinin tarayıcıyı alan adınızın dışına gönderdiğini kaydedin.
- Mümkün olduğunda kullanıcının verdiği URL'leri sunucu tarafı ID eşlemesiyle, dönüş adresleri için yalnızca yerel kabul eden framework yardımcılarıyla, ihtiyaç duyduğunuz az sayıdaki harici hedef için ise ayrıştırılmış URL allowlist'iyle değiştirin.
- OAuth client kayıtlarınızı birebir yönlendirme URI'leri açısından gözden geçirin ve host'larınızdan hiçbirinin open redirector sunmadığını doğrulayın.
- Hatanın geri dönmemesi için kod incelemesine ve sızma testi kapsamınıza bir yönlendirme testi ekleyin.
