WAF loglarınızda veya tarayıcı konsolunuzda Refused to execute inline script because it violates the following Content Security Policy directive gibi bir şey gördüyseniz ve aynı zamanda kullanıcılar "giriş yaptıktan sonra kendi kendine giriş yapıyor" diye bildiriyorsa, muhtemelen birini diğeriyle karıştırdığınız iki ayrı sorunla karşı karşıyasınız. XSS ve CSRF her ikisi de kurbanın tarayıcısını kötüye kullanır, ancak biri sayfanızda kod çalıştırır, diğeri yalnızca istek gönderir. Bu farkı zihninizde ayırmadığınız sürece, her iki savunmayı da yarım uygularsınız.
XSS nedir ve neden CSRF token'ı onu engellemez
XSS'te saldırgan, kurbanın tarayıcısının kendi JavaScript'ini sizin alan adınızda çalıştırmasını sağlar. O andan itibaren, o script sizin origin'inizin içindedir. Yani document.cookie'ye erişimi vardır, fetch atabilir, form oluşturabilir ve hepsinden önemlisi, CSRF token'ını sayfanın kendisinden okuyup aynı istekte kullanabilir.
İşte burada hata yapılıyor: ekipler CSRF token'ını forma koyar, sızma testini geçer ve içleri rahatlar. Sonra bir yorum alanı escape edilmeden kalır ve o token saldırganın script'i tarafından okunup kullanılır. İşareti de şudur: logda istekler geçerli token'a sahiptir, Referer doğrudur, her şey sağlıklı görünür, ancak işlem gerçek kullanıcının IP'sinden yapılmıştır. CSRF token'ı, sizin origin'inizde çalışan kodu engellemek için tasarlanmamıştır. O iş CSP ve çıktıyı escape etmektir.
Pratikte gördüğünüz üç XSS türü
- Reflected: Girdi URL'den gelir ve escape edilmeden HTML'e döner. En klasik biçimi
?q=<script>alert(1)</script>'tir. - Stored: Girdi veritabanında kalır ve tüm ziyaretçilere sunulur. En tehlikeli tür, çünkü kurbanı tıklamaya ikna etmeye gerek yoktur.
- DOM-based: Ne sunucu işin içindedir ne de HTML yanıtı kirlenmiştir. İstemci tarafındaki kod,
location.hashdeğerini kendisiinnerHTMLiçine döker.
Üçüncü tür için, sunucudaki hiçbir filtre yardımcı olmaz. JavaScript kodunu okuyup innerHTML, document.write veya eval'in nerede kullanıldığını görmelisiniz. Doğru alternatif textContent'tir, "script"i silen bir regex değil.
CSRF nedir ve neden yalnızca SameSite yeterli değildir
CSRF'te saldırgan kod çalıştırmaz; yalnızca kurbanın tarayıcısının kendi oturum çereziyle sizin alan adınıza bir istek göndermesini sağlar. Saldırganın sayfası başka bir alan adındadır ve sizin çerezinizi görmez, ancak tarayıcı onu otomatik olarak ekler. method="POST" ve action'ı e-posta değiştirme endpoint'ine yönelik gizli bir form, sayfa yüklenir yüklenmez gönderilir.
SameSite=Lax çerezi bu durumların büyük bir kısmını kapatır, ancak iki yerde yetersiz kalır. Birincisi, bu özelliği yok sayan daha eski tarayıcılar. İkincisi, kendi alt alan adlarınız: bir alt alan adı herhangi bir nedenle saldırganın elindeyse veya XSS varsa, istek SameSite açısından aynı site sayılır. Yani SameSite'i koyun, ancak ikinci katman olarak, token'ın yerine geçen bir şey olarak değil.
Doğru CSRF token deseni
Token per-session ve rastgele olmalı, sunucuda saklanmalı ve durum değiştiren her istekte kontrol edilmelidir. Double-submit desenini yalnızca token çerezi HttpOnly değilse ve imzalanmışsa kabul edin; aksi halde alt alan adı onu yeniden yazabilir.
// Express + csurf-style
app.use(session({ cookie: { httpOnly: true, sameSite: 'lax', secure: true } }));
app.use(csrf({ cookie: false })); // token session'da, ayrı çerezde değil
app.use((req, res, next) => {
res.locals.csrfToken = req.csrfToken();
next();
});
Pratik nokta: token'ı meta etiketine koyup sonra JavaScript ile okumayın. Token'ın JS tarafından okunabilir hale geldiği anda, XSS için de okunabilir hale gelir. Onu sunucu tarafında HTML formunda render edin.
HttpOnly çerezi tam olarak neyi kapatır
Oturum çerezini HttpOnly ile gönderin ki document.cookie onu görmesin. Bu, XSS ile doğrudan oturum hırsızlığını kapatır, ancak CSRF'yi engellemez; çünkü tarayıcı çerezi yine otomatik olarak ekler. Bu iki mekanizma iki farklı eksende çalışır ve birbirinin yerini tutmaz.
| Önlem | XSS'e karşı | CSRF'ye karşı |
|---|---|---|
| Çıktıyı escape etmek | Evet | Hayır |
| HttpOnly çerezi | Kısmen (oturum hırsızlığı) | Hayır |
| CSRF token'ı | Hayır | Evet |
| SameSite=Lax/Strict | Hayır | Büyük ölçüde |
| CSP başlığı | Evet (savunma katmanı) | Hayır |
Content-Security-Policy'yi gerçekten nasıl devreye alırız
Sıkı bir CSP, XSS olsa bile saldırganın script'inin çalışmasını durdurur. Ancak yanlış CSP siteyi bozar ve ekip iki saat sonra onu kaldırır. Gerçekçi yol şudur:
- Önce
Content-Security-Policy-Report-Onlyile başlayın ve raporları bir endpoint'te toplayın. - Bir hafta sonra, gerçek alan adlarını
script-src'ye koyun ve'unsafe-inline''ı kaldırın. - Inline script'ler için hash değil nonce kullanın, çünkü nonce her yanıtta değişir.
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-8f3a...' https://cdn.example.ir;
object-src 'none';
base-uri 'self';
frame-ancestors 'self'
base-uri 'self' satırını hafife almayın. O olmadan, saldırgan bir <base> etiketi enjekte ederek sayfanın tüm göreli yollarını kendi alan adına gönderebilir ve CSRF token'ını sizin formunuzdan çalabilir. Bu, kontrol listelerinde atlanan durumlardan biridir.
Gerçek ödünleşim: neden her yerde sıkı olamayız
Sıfır 'unsafe-inline' ile sıkı CSP, onlarca inline script ve üçüncü taraf eklentisi olan eski sitelerde pratikte front-end'i yeniden yazmak anlamına gelir. Proje geçiş halindeyse, benim tercihim şudur: önce çıktıyı escape etmeyi ve CSRF token'ını tamamlayın, CSP'yi Report-Only modunda bırakın ve kademeli olarak sıkılaştırın. Site yeni yapılmışsa, ilk günden nonce koyun; sonradan eklemek kat kat daha pahalıdır.
Daha az söylenen bir başka kısıtlama da var: CSRF token'ı Bearer token ile çalışan stateless API'lerde anlam ifade etmez. Orada token'ı localStorage'da tutarsanız, bir XSS her şeyin kaybolması için yeterlidir. Bu nedenle API'lerde HttpOnly çerezi ile birlikte CSRF token'ı daha iyi bir seçimdir, yönetimi daha zor olsa bile.
Bu akşam için pratik kontrol listesi
- Tüm oturum çerezlerini
HttpOnly; Secure; SameSite=Laxile gönderin. - Durum değiştiren her endpoint'i CSRF token'ı ile koruyun, logout dahil.
- Çıktıları HTML formatında escape edin; JavaScript için
textContentkullanın. - CSP başlığını Report-Only modunda devreye alın ve logları inceleyin.
- Denetim logunu ayrı ve dokunulmamış tutun ki olaydan sonra yeniden oluşturabilesiniz; Denetim logu nedir ve onu dokunulmamış nasıl tutarız? tam olarak bunu açıklıyor.
WordPress üzerinde çalışıyorsanız, bu açıkların büyük bir kısmı eklentilerden ve REST API'den gelir. Her şeyden önce WordPress güvenliği; wp-config'ten kendileri açık olan eklentilere kadar ve WordPress REST API'sini güvenli hale getirme adlı iki yazıyı okuyun. Ağ katmanı için de hizmetiniz hacimsel saldırılar altındaysa, DDoS koruması bu işlerin tamamlayıcısıdır, ancak çıktıyı escape etmenin yerini tutmaz. Ekibiniz büyükse ve şifreleri sohbette paylaşıyorsanız, Ekiplerde şifre yönetimi yazısına da bakın; çalınan oturum çoğu zaman tam oradan başlar.
Bu katmanları sıfırdan inşa etmek yerine, yükün bir kısmını kendisi üstlenen bir altyapı üzerine koymak istiyorsanız, ServerNet güvenlik hizmetleri makul bir başlangıç noktasıdır. Ancak unutmayın: hiçbir hizmet, kodunuzda textContent yazmanın yerini tutmaz.
Sık sorulan sorular
SameSite=Lax, CSRF token'ının yerini tutar mı?
Tam olarak değil. SameSite=Lax, cross-site isteklerin çoğunu engeller, ancak top-level GET isteklerini reddetmez ve eski tarayıcılarda güvenilir değildir. Endpoint'iniz GET ile durumu değiştiriyorsa, SameSite hiç yardımcı olmaz. CSRF token'ını tutun ve SameSite'i ikinci katman olarak görün.
CSRF token'ı varken neden XSS saldırısı hâlâ başarılı oluyor?
Çünkü saldırganın script'i sizin origin'inizde çalışır ve gerçek kullanıcının sahip olduğu aynı DOM'a erişir. Token'ı formdan okuyup geçerli istek gönderebilir. Bu durumda doğru savunma token değil, çıktıyı escape etmek ve CSP'dir.
HttpOnly çerezi hangi saldırıyı engeller?
Oturum çerezinin JavaScript tarafından okunmasını engeller, böylece XSS ile doğrudan oturum hırsızlığını zorlaştırır. Ancak CSRF'yi engellemez, çünkü tarayıcı çerezi yine isteğe otomatik olarak ekler.
Bugün yapabileceğim minimum şey nedir?
Üç iş: oturum çerezini HttpOnly; Secure; SameSite=Lax yapın, durum değiştiren her endpoint'i CSRF token'ı ile kapatın ve tüm kullanıcı çıktılarını escape edin. Bu üçünden sonra CSP'ye geçin. Sıralama önemlidir; henüz escape etmeyen kod üzerindeki CSP yalnızca siteyi bozar.
Yorumlar 0
Henüz yorum yok — ilk siz olun!