Merhaba! Önceki dersimizde, .NET ekosistemindeki Web.config dosyasının ve machineKey gibi kritik konfigürasyonel sırların, ViewState deserializasyonu gibi zafiyetleri nasıl tetikleyebildiğini inceledik. Bir uygulamanın güvenlik duruşunun, kodun kendisi kadar, onu yönlendiren konfigürasyonun doğruluğuna da ne kadar bağlı olduğunu gördük.
Bu dersimizde, konfigürasyon kaynaklı zafiyetler temasını sürdürerek Java ekosisteminin en yaygın güvenlik framework'ü olan Spring Security'ye odaklanacağız. Bu dersin amacı, Spring Security konfigürasyonlarında yanlış yapılandırılmış filtre zincirleri üzerinden kimlik doğrulama ve yetkilendirme atlatmalarını analiz etmektir. ViewState'te olduğu gibi, burada da geliştiricinin tanımladığı kuralların sırası, kapsamı ve mantığı, tüm uygulamanın güvenlik modelini belirler. Yanlış bir sıralama veya fazla geniş tanımlanmış bir kural, en karmaşık kimlik doğrulama mekanizmalarını bile etkisiz hale getirebilir.
Spring Security'nin Özü: Filtre Zinciri (Filter Chain)
Spring Security, özünde, uygulamanıza gelen her HTTP isteğini karşılayan ve bir dizi güvenlik kontrolünden geçiren bir filtreler zinciridir. Bu zincirdeki her bir filtre, belirli bir görevden sorumludur: oturum yönetimi, CSRF koruması, kimlik doğrulama, yetkilendirme vb. Bir isteğin, uygulamanızdaki asıl controller metoduna ulaşabilmesi için bu zincirden başarıyla geçmesi gerekir.
Bu yapının temellerini ve kimlik doğrulama (authentication) ile yetkilendirme (authorization) arasındaki ayrımı netleştirmek için aşağıdaki kaynağı inceleyerek başlayalım.
Bu blog yazısı, Spring Security'nin temel felsefesini ve mimarisini anlaşılır bir dilde özetliyor. Özellikle "güvenlik konfigürasyonunu geliştiricinin sorumluluğuna bırakma" prensibini anlamak, bu dersteki zafiyetleri tespit edebilmek için kritik öneme sahip.
Yazıda öncelikle giriş bölümünü okuyarak Authentication ve Authorization kavramları arasındaki farkı ve sırayı netleştirin. Ardından, "Spring Boot Entegrasyonu" bölümünü atlayıp doğrudan "Çekirdek Mekanizmalar" bölümüne geçin. Bu bölüm, SecurityFilterChain mimarisinin neden bir bean olarak tanımlandığını ve bir geliştirici kendi zincirini tanımladığında Spring'in varsayılan güvenliği neden devre dışı bıraktığını açıklıyor. Bu davranışın doğurduğu sorumluluğu anlamak, konumuzun temelini oluşturmaktadır.
Okuduğunuz gibi, Spring Security'nin en temel tasarım kararlarından biri, geliştirici özel bir SecurityFilterChain bean'i tanımladığında, tüm güvenlik sorumluluğunu ona devretmesidir. Spring, "Artık kuralları sen koyuyorsun, varsayılan korumalarıma güvenme" der. Bu esneklik, aynı zamanda, dikkatsiz bir konfigürasyonla tüm güvenlik mekanizmalarını bypass etme riskini de beraberinde getirir.
Yetkilendirme Kurallarını Analiz Etmek: authorizeHttpRequests
Güvenlik kurallarının tanımlandığı yer, genellikle authorizeHttpRequests (eski versiyonlarda authorizeRequests) ile başlayan metot zinciridir. Bu blok içinde, hangi URL yollarının (path) hangi koşullarla erişilebilir olacağı belirlenir.
İşte tipik bir SecurityFilterChain yapılandırması:

Bu kod bloğunu analiz ederken anlaşılması gereken en önemli prensip, "ilk eşleşen kazanır" (first match wins) mantığıdır. Spring Security, gelen isteğin URL'sini, yukarıdan aşağıya doğru tanımlanan requestMatchers (veya antMatchers) kurallarıyla karşılaştırır ve ilk eşleşen kuralı uygular. Sonraki kurallara bakmaz. İşte bu sıralama mantığı, yetkilendirme atlatmalarının ana kaynağıdır.
Yaygın Yanlış Yapılandırma Desenleri
Bir kod denetçisi olarak, SecurityFilterChain tanımında aşağıdaki tehlikeli desenleri aramalısınız.
1. Fazla Geniş Kapsamlı permitAll() ve Kural Sıralaması
En sık rastlanan hata, genel bir kuralın, daha spesifik ve kısıtlayıcı bir kuraldan önce gelmesidir.
Hatalı Örnek:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/api/**").permitAll() // Tehlikeli: Bu kural, sonraki kuralı etkisiz kılar.
.requestMatchers("/api/admin/**").hasRole("ADMIN") // Bu kurala sıra asla gelmez.
.anyRequest().authenticated()
);
return http.build();
}
Bu konfigürasyonda, /api/admin/users adresine yapılan bir istek, ilk kural olan .requestMatchers("/api/**").permitAll() ile eşleşir ve yetkilendirme gerektirmeden işleme alınır. .requestMatchers("/api/admin/**").hasRole("ADMIN") kuralı daha spesifik olmasına rağmen, sıralamada altta kaldığı için hiçbir zaman uygulanmaz.
Doğru Sıralama: En kısıtlayıcı kurallar daima en üstte olmalıdır.
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/api/admin/**").hasRole("ADMIN") // Doğru: Önce spesifik olan.
.requestMatchers("/api/**").permitAll() // Sonra daha genel olan.
.anyRequest().authenticated()
);
return http.build();
}
Kod denetimi yaparken, permitAll() içeren tüm requestMatchers kurallarını dikkatle incelemelisiniz. Bu kuralların, yanlışlıkla /actuator, /management, /internal-api gibi hassas olabilecek yolları kapsayıp kapsamadığını kontrol edin.
2. URL Normalizasyon ve Eşleştirici (Matcher) Zafiyetleri
Bu daha ileri seviye bir problemdir ve özellikle antMatchers gibi eski eşleştiricileri kullanan kod tabanlarında karşınıza çıkabilir. Farklı uygulama sunucuları (Tomcat, Jetty, vb.) ve Spring'in kendisi, URL yollarını farklı şekillerde normalleştirebilir.
Örneğin, antMatchers'ın bazı eski sürümleri, path traversal karakterleri (/../) veya URL kodlamalı karakterler içeren isteklere karşı savunmasız olabilir. Bir kural /admin/** yolunu korumak için yazılmışsa, bir saldırgan /public/../admin/dashboard gibi bir yolla bu kuralı atlatmayı deneyebilir.
Daha da önemlisi, eşleştiricinin kendi mantığındaki bir hata bile zafiyete yol açabilir. Aşağıdaki güvenlik bülteni, bunun somut bir örneğidir.
Bu bültende açıklanan CVE-2022-22978 zafiyeti, RegexRequestMatcher kullanılırken, bir düzenli ifadede yer alan . karakterinin, Pattern.DOTALL bayrağı aktif değilken bile yeni satır karakterleriyle (%0A veya %0D) eşleşmesi nedeniyle ortaya çıkmıştır. Örneğin, /admin/.+ gibi bir regex, /admin/v1%0A/public gibi bir isteği yakalaması gerekirken yakalayamayabilir ve bu da kuralın atlatılmasına yol açabilir.
Bu tür zafiyetler, bir kod denetçisi olarak sadece kural sıralamasına değil, kullanılan eşleştiricinin türüne (requestMatchers, antMatchers, regexMatchers) ve sürümüne de dikkat etmeniz gerektiğini gösterir.
3. "Hepsini Yakala" (Catch-All) Kuralının Eksikliği
Güvenli bir konfigürasyonun sonunda mutlaka bir "catch-all" kuralı bulunmalıdır. Bu genellikle .anyRequest().authenticated() veya daha katı bir politika için .anyRequest().denyAll() şeklindedir.
Bu kuralın olmaması, geliştiricinin gelecekte ekleyeceği ancak güvenlik kuralı tanımlamayı unuttuğu yeni endpoint'lerin varsayılan olarak herkese açık hale gelmesi riskini doğurur.
Tehlikeli Örnek (Catch-all yok):
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/login", "/register").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
// .anyRequest().authenticated() unutulmuş!
);
return http.build();
}
Bu durumda, eğer bir geliştirici /api/v1/health-check adında yeni bir endpoint eklerse ve bu endpoint yukarıdaki kurallardan hiçbiriyle eşleşmezse, Spring Security varsayılan olarak erişime izin verecektir. Bu endpoint hassas veriler içeriyorsa (örneğin veritabanı bağlantı durumu), bir bilgi sızıntısı zafiyeti ortaya çıkar.
Güvenli Kod Denetimi Stratejisi
Spring Security konfigürasyonunu denetlerken izlemeniz gereken adımlar şunlardır:
SecurityFilterChainBean'lerini Bulun: Projede@Beananotasyonu ileSecurityFilterChaindöndüren tüm metotları tespit edin.- Kural Sıralamasını Analiz Edin:
authorizeHttpRequestsbloğundakirequestMatcherstanımlarını yukarıdan aşağıya inceleyin. Daha spesifik kuralların (ör./admin/users) her zaman daha genel kurallardan (ör./admin/**) önce geldiğinden emin olun.permitAll()içeren kurallara özellikle şüpheyle yaklaşın. - Eşleştirici Tipini Kontrol Edin:
antMatchersmı yoksa modern ve daha güvenli olanrequestMatchersmı kullanılıyor? EğerregexMatcherskullanılıyorsa, düzenli ifadenin kendisi bir zafiyet barındırıyor mu? (.karakterinin kaçış (escape) karakteri olmadan kullanımı vb.) - "Catch-All" Kuralını Doğrulayın: Zincirin sonunda
.anyRequest().authenticated()veya.anyRequest().denyAll()gibi bir kuralın varlığını teyit edin. Yokluğu, ciddi bir tasarım hatasıdır. - Çoklu Filtre Zincirlerini İnceleyin: Karmaşık uygulamalarda
@Orderanotasyonu ile sıralanmış birden fazlaSecurityFilterChainbean'i olabilir. Düşük sıralı (@Order(1)) bir zincirin, yüksek sıralı (@Order(2)) bir zincirin koruması gereken bir URL desenini "çalıp" çalmadığını analiz edin.
Özet ve Sonraki Adımlar
Bu dersimizde, Spring Security'nin gücünün, geliştiricinin eline bıraktığı konfigürasyon sorumluluğundan kaynaklandığını gördük. Bir filtre zincirindeki kuralların sırası, kapsamı ve mantığı, uygulamanın tüm erişim kontrol modelini tanımlar ve küçük bir hata bile tam bir yetkilendirme atlatmasına yol açabilir.
Anahtar Çıkarımlar:
- Sorumluluk Geliştiricidedir: Özel bir
SecurityFilterChaintanımlandığında, Spring'in varsayılan güvenliği devre dışı kalır. - Sıralama Her Şeydir: "İlk eşleşen kazanır" mantığı nedeniyle, en spesifik kurallar her zaman en üstte olmalıdır.
permitAll()Tehlikelidir: Geniş kapsamlıpermitAll()kuralları, hassas endpoint'leri yanlışlıkla korumasız bırakmanın en yaygın yoludur.- Eksik Kural, Açık Kapıdır: Sonunda bir "catch-all" (
anyRequest()) kuralının olmaması, gelecekte eklenecek endpoint'ler için bir güvenlik boşluğu yaratır. - Eşleştiricinin Mantığı Önemlidir:
antMatchers,regexMatchersgibi eşleştiricilerin kendi içsel çalışma mantığı veya hataları da bypass'lara neden olabilir.
Statik dillerden Java ve .NET'in güvenlik konfigürasyonlarına ve zafiyet desenlerine odaklandığımız bu modülü burada tamamlıyoruz. Bir sonraki dersimizde, modern web geliştirmenin diğer popüler platformlarına yöneleceğiz. Express.js middleware sıralamasını ve Django/Flask güvenlik middleware'lerini değerlendirerek güvenlik boşluklarını tespit etmeyi öğreneceğiz. Bu konu, Spring'deki filtre zinciri mantığının JavaScript ve Python dünyasındaki karşılıklarını analiz ederek, konfigürasyon ve yürütme sırası kaynaklı zafiyetler konusundaki bakış açınızı daha da genişletecek.