60 saniyede OAuth güvenlik incelemesi
Bir OAuth güvenlik incelemesi neredeyse her zaman aynı bulgu yanılsamasıyla başlar: kimse akışın nerede bozulduğunu gösteremez, çünkü akış teknik olarak bozulmamıştır. Standart şartname (RFC) sağlamdır. Kimlik sağlayıcı (IdP) yapılandırılmıştır. Değiş tokuş başarıyla tamamlanır. Ancak saldırgan yine de hedef hesabı ele geçirebilir; çünkü zafiyet adımlardan birinde değil, hiç inşa edilmemiş bir adımdadır.
OAuth bir yetki devretme (delegation) protokolüdür ve protokolün en yaygın başarısızlıkları, yalnızca amaçlanan yetkiyi devretmeyi başaramamaktan kaynaklanır. Hata, uygulayıcının “ihtiyacım yok” diyerek dışarıda bıraktığı şartname yarısındadır.
Herkesin kontrol ettiği üç şey ve atlanan diğer üçü
Her OAuth değerlendirmesi aynı üç özelliği doğrular; çünkü dokümantasyonlar sadece bunlara karşı uyarır. Ve neredeyse her OAuth bulgusu, bu üçünün hemen yanında uygulamasız bekleyen diğer üç özellikten çıkar — çünkü hiç üretilmemiş bir değer için kimse hata kaydı (ticket) açmaz.
Kontrol edilen: redirect_uri parametresi. Atlanan: Birebir (tam) eşleşip eşleşmediği.
Kontrol edilen: Geriye bir token dönüp dönmediği. Atlanan: Token’ın doğru hedef kitle (aud - audience) için doğrulanıp doğrulanmadığı.
Kontrol edilen: Kullanıcının oturum açıp açmadığı. Atlanan: Giriş yapılan oturumun gerçekten bu kullanıcıya mı ait olduğu, yoksa kurbanın bir saldırgan oturumuna mı düşürüldüğü.
Bu üç senaryoda da örüntü aynıdır: Akışın tamamlanmış gibi görünmesini sağlayan parçalar mevcuttur; akışın doğru ve güvenli çalışmasını sağlayan parçalar ise eksiktir.
Redirect her şeydir ve “içerir” (contains) asla “eşittir” (equals) demek değildir
Authorization code kayıtlı olan URI’ye teslim edilir; bu da redirect doğrulamasını tüm akıştaki tek taşıyıcı güvenlik kontrolü haline getirir. Hata kontrolün hiç olmaması değildir; gevşek eşleştirilmesidir.
https://app.example.com/callback olarak kaydedilmiş bir client, şaşırtıcı sayıda gerçek dünya uygulamasında aşağıdakilerin herhangi birini kabul edebilir:
https://app.example.com/callback.attacker.com
https://app.example.com/callback/../evil
https://app.example.com.attacker.com/callback
https://app.example.com/callback%40attacker.com
Bir substring (alt dize) eşleşmesi, sondaki eğik çizgi (/) kontrolünün unutulması veya browser ile URL parser’ın host tanımında anlaşmazlığa düşmesi durumunda authorization code doğrudan saldırganın endpoint’ine akar. Bu durum, path normalisation notumuzda detaylandırdığımız ayrışmanın aynısıdır: sağlayıcı bir string’i yetkilendirir, tarayıcı başka bir string’e istek atar ve zafiyet, her ikisi de dokümante edildiği gibi davranan iki parser arasındaki boşlukta doğar.
State bir lüks değildir. Login ile login-CSRF arasındaki tek farktır
state parametresi, uygulamaların en sık terk ettiği alandır; çünkü sorunsuz bir test (happy-path) senaryosunda akış bu parametre olmadan da kusursuz çalışır. Oysa state’in yegane görevi, oturum açma isteğini başlatan tarayıcı oturumu ile akışı tamamlayan oturumu birbirine kriptografik olarak bağlamaktır.
state olmadığında; saldırgan kendi hesabıyla bir OAuth akışı başlatır, kendisi adına geçerli bir authorization code elde eder ve ardından kurbana bu kod ile oturumu tamamlayan bir bağlantı gönderir. Kurban oturum açmış olur — ancak saldırganın hesabında. Eğer uygulama kurbanın bu oturuma kredi kartı, API anahtarı veya adres eklemesine izin veriyorsa, saldırgan daha sonra kendi hesabına girerek tüm bu hassas verileri okur.
Akışta hiçbir şey bozulmamıştır. Giriş işlemi, uygulamanın tasarladığı şekilde başarıyla tamamlanmıştır. Ancak uygulamanın bu kodu reddetmesini sağlayacak parametre hiç geliştirilmediği için sistem zafiyete tamamen açıktır.
PKCE kimsenin incelemediği public client’lar içindir
Authorization Code + PKCE standardının ortaya çıkış sebebi, eski implicit flow’un token’ları URL fragment’larında ve log dosyalarında sızdırması ve public client mimarisinde (mobil uygulamalar, SPA) client secret’ı güvenle saklayacak bir alanın olmamasıdır. Kodun mülkiyet kanıtı böylece code_verifier ve code_challenge çiftine aktarılır.
Denetimlerde karşılaşılan tipik hata şablonu bellidir: Implicit flow’dan authorization code flow’a taşınmış ancak PKCE eklenmemiş bir mobil veya SPA client. Yalnızca code flow’a geçmenin tek başına güvenliği artıracağı varsayılmıştır. Ancak PKCE olmayan bir public client, implicit flow’un taşıdığı kod yakalama (code interception) risklerinin tamamını taşır; yalnızca modern bir ambalaja sarılmıştır.
Kimsenin kontrol etmediği token confusion riski
Atlanan son adım en karmaşık olanıdır: Hangi token’ı aldığınız ve bu token’ın gerçekten bu uygulama için geçerli olup olmadığı.
Bir ID Token kullanıcının kim olduğunu kanıtlar. Bir Access Token ise kullanıcının ne yapmaya yetkili olduğunu kanıtlar. Bunlar farklı hedef kitleler (aud) için üretilmiş farklı token’lardır ve asla birbirinin yerine geçmemelidir. Bu ayrımın yapılmaması iki kritik hataya yol açar:
Access token’ı kimlik kanıtı olarak kabul etmek. İstemci elde edebildiği herhangi bir token’ı doğrulanmış bir oturum olarak kabul ederse; sağlayıcıdan farklı bir servis veya client için token alabilen bir saldırgan, hedef uygulamada yetkisiz oturum açabilir.
aud (Audience) claim’ini doğrulamamak. Client A için üretilen token Client B tarafından kabul edilir, çünkü B yalnızca imzanın geçerli olup olmadığına bakar; token’ın kime tahsis edildiğini denetlemez. İmza geçerlidir. Token gerçektir. Ancak B için üretilmemiştir. Bunu kabul etmek, tek bir istemcinin ele geçirilmesini tüm ekosistemin ele geçirilmesine dönüştürür.
Düzeltme tek bir kontrolden ibarettir: İmza kontrolü geçtiği için atlanan audience doğrulaması.
Bir akış olarak değil, bir montaj hattı olarak test etmek
Faydalı bir sızma testi “giriş çalışıyor mu” sorusunu sormaz. Akışı, kasıtlı olarak atlanan yarısına odaklanarak adımlamaktır:
# 1. Redirect tam eşleşiyor mu, yoksa bir alt alan adı / parser hilesi host'u değiştiriyor mu?
GET /authorize?response_type=code&client_id=...&redirect_uri=https://app.example.com/callback.attacker.com
# 2. State zorunlu mu? Parametreyi çıkarıp akışı tamamlayın ve ilerleyip ilerlemediğini görün.
GET /authorize?response_type=code&client_id=...&redirect_uri=... # state yok
# 3. Token'ın audience doğrulaması yapılıyor mu, yoksa sadece imza mı kontrol ediliyor?
# Farklı bir client_id için üretilmiş geçerli bir token'ı bu client'ın endpoint'ine gönderin.
Bu adımların her biri tek bir HTTP isteğidir ve her biri demo akışının asla sormadığı kritik soruları yanıtlar.
Evidence matrix
| Sinyal | Neyi kanıtlar | Negatif kontrol | Savunmacı doğrulaması |
|---|---|---|---|
| Birebir uymayan bir redirect varyantının authorization code alması | Redirect doğrulaması ile tarayıcı URL yorumlamasının uyuşmadığını | Kayıtsız bir host kullanarak tam bir ret yanıtı (400 Bad Request) bekleyin | Kayıtlı URI’ye karşı scheme, host, port, path ve normalizasyonu unit test’e bağlayın |
Girişin eksik, tekrar kullanılan veya saldırgan kaynaklı state ile tamamlanması | Callback’in oturumu başlatan tarayıcı session’ına bağlı olmadığını | İki farklı oturum başlatın ve her birinin diğerinin state değerini reddettiğini doğrulayın | State üretimini, tek kullanımlık tüketimini, süre aşımını ve session binding’i loglayın |
| Public client’ın çalınmış bir kodu orijinal verifier olmadan takas edebilmesi | PKCE’nin token endpoint’inde bulunmadığını veya zorunlu kılınmadığını | Yanlış bir verifier ile isteği tekrarlayın ve reddedildiğini görün | Tüm public client kayıtları için S256 challenge zorunluluğunu doğrulayın |
| Başka bir client/audience için üretilen token’ın oturum oluşturması | İmza doğrulamasının var olduğunu ancak audience ve token amacı kontrollerinin yapılmadığını | Aynı issuer’dan farklı bir aud içeren token gönderin ve erişimin engellendiğini görün | Tüketici serviste issuer, audience, token type, nonce ve yetkili taraf (azp) doğrulaması yapın |
Sürekli karşılaştığım örüntü
OAuth genellikle kimlik katmanına sonradan eklenir; ekleyen ekip RFC dokümanını yüzeysel okumuştur ve atlanan adımlar sorunsuz akışta (happy-path) zorunlu görünmediği için atlanmıştır. Ardından uygulama büyür, giriş çalışmaya devam eder ve yapılan ihmal sonraki tüm servislere miras kalır — çünkü hiçbir şey hata vermez, kimse dönüp geriye bakmaz.
Bu durum, sertifika şablonu analizimizde anlattığımız tortunun aynısıdır: zamanında çalışan bir sistem olarak kurulmuş, ancak hiçbir ekip sistemin tamamına uçtan uca sahip çıkmadığı için denetlenmeden kalmıştır. Kimlik ekibi IdP’yi yönetir, uygulama ekibi client’ı yönetir; ikisi arasındaki güvenlik boşluğuna ise kimse sahip çıkmaz.
Savunmacılara teslim edilmesi gerekenler
“OAuth zayıf” demek yerine atlanan spesifik kontrolü adlandırın. “Redirect URI bir substring karşılaştırmasıyla doğrulanıyor, bu da open redirect üzerinden authorization code hırsızlığına yol açıyor” somut bir bulgudur. “OAuth uygulamanızı gözden geçirin” ise takvime asla alınmayacak soyut bir projedir.
State parametresini katı bir gereklilik haline getirin. Tek bir değer ve tek bir karşılaştırmadır; login-CSRF sınıfını tamamen ortadan kaldırır. Etkileşimli bir web login akışının state olmadan çalışması için meşru hiçbir teknik gerekçe yoktur.
Her public client için PKCE, her token için audience doğrulaması. Her ikisi de çoğu kütüphanede tek satırlık bir yapılandırmadır ve standart bir akış ile bir saldırgan tarafından istismar edilebilen bir akış arasındaki yegane farktır.
Problem hiçbir zaman akışta değildi. Problem, “standart” kelimesinin implementasyonu değil, yalnızca protokol şartnamesini tanımlıyor olmasıdır.
Bu teknik not ne kadar güncel?
En son kaynak incelemesi, içerik güncellemesi veya yayın tarihi gösterilmektedir.
Yazar teknik incelemeyi tamamladı. Bu etiket tek başına laboratuvar yeniden üretimi iddiası taşımaz.
