API Güvenliği

GraphQL Güvenliği: Saldırganın Gözünden Zafiyetler

GraphQL, istemciye sorguyu şekillendirme özgürlüğü vererek geliştirme tarafında büyük kolaylık sağlıyor. Aynı özgürlük, güvenlik tarafında REST'te alışık olmadığımız bir yüzey açıyor: tek uç nokta, kendini anlatan bir şema ve sunucunun önceden bilmediği sorgu şekilleri.

9 dakika Zafiyet sınıfları

GraphQL'in güvenlik profili neden farklı

REST'te her uç nokta ayrı bir yoldur; erişim kontrolü, hız sınırı ve izleme büyük ölçüde yol ve yöntem üzerinden kurulur. GraphQL'de ise neredeyse tüm trafik tek bir yola, çoğunlukla tek bir gövde tipiyle akar. Bunun üç doğrudan sonucu var:

  • Yol tabanlı kontroller körleşir. "Şu uç noktaya dakikada on istek" kuralı, tek istekte yüz iş yapan bir sorguyu göremez.
  • Sunucu, dönecek veri şeklini önceden bilmez. Erişim kontrolünün uç nokta düzeyinden alan ve nesne düzeyine inmesi gerekir.
  • Şema kendini anlatır. Saldırgan için keşif maliyeti çok düşüktür; tipler, ilişkiler ve mutasyonlar bir arada durur.

Keşif: introspection ve şema sızıntısı

Üretimde açık bırakılmış introspection, tüm tip ve alan haritasını tek istekte verir. Bu tek başına bir zafiyet değildir — asıl sorunlar yetkilendirmede — ama saldırganın işini gözle görülür şekilde kolaylaştırır ve genellikle diğer bulguların başlangıç noktasıdır.

Introspection kapatıldığında şema tamamen gizlenmiş olmaz. Sunucunun ürettiği yardımcı hata mesajları (yazım hatası durumunda "şunu mu demek istediniz" önerileri), alan adlarını tek tek doğrulamaya imkân verir. Aynı şekilde istemci paketleri içindeki sorgu metinleri, geliştirici araçlarındaki ağ trafiği ve kaynak haritaları da şemayı büyük ölçüde açık eder.

Şemayı gizlemek bir gecikme önlemidir, savunma değil. Introspection kapalıyken güvenli görünen bir API, açıkken de güvenli olmalıdır. Aksi hâlde elinizdeki şey belirsizlikle sağlanan güvenliktir.

Asıl risk: alan ve nesne bazlı yetkilendirme

GraphQL'de bulunan yüksek etkili bulguların büyük çoğunluğu yetkilendirme başlığında toplanıyor. Tekrar eden desenler:

  • Yetkilendirmenin üst seviyede kalması. Kök sorgu yetkilendirilir, ama iç içe alanlar aynı denetimden geçmez. Kullanıcı kendi erişebildiği bir nesneden yola çıkıp ilişkiler üzerinden erişmemesi gereken nesnelere ulaşabilir. Örneğin kendi ekibindeki bir kaydın ilişkili "sahip" alanı üzerinden başka bir kullanıcının özel alanlarına geçmek.
  • Nesne kimliğiyle doğrudan erişim. Global kimliklerle tek tip bir nesne getirme alanı sunan şemalarda, kimliği tahmin edilebilir veya sızmış bir nesne için sahiplik kontrolü yapılmıyorsa klasik bozuk nesne yetkilendirmesi ortaya çıkar.
  • Mutasyonların daha az denetlenmesi. Okuma tarafına özen gösterilirken yazma tarafında rol kontrolünün atlanması sık görülür. Özellikle "admin" işlevlerinin aynı şemada yer alması durumunda.
  • Hassas alanların şemada bulunması. Parola özeti, dahili notlar, e-posta gibi alanların tipte tanımlı olması ve yalnızca istemci arayüzünde gösterilmemesi. Şema, arayüzün değil sunucunun sözleşmesidir.

Doğru yer, yetkilendirmeyi resolver seviyesinde ve mümkünse veri erişim katmanında kurmak: her alan, çağıran bağlamı bilerek kendi kararını versin. Şema düzeyinde yönergelerle yapılan kontroller okunabilir olduğu için değerli, ama iş mantığına dayalı sahiplik kararlarını tek başına taşıyamaz.

Sorgu maliyeti ve kaynak tüketimi

Şemada döngüsel ilişkiler varsa — ki neredeyse her zaman vardır — istemci, küçük bir metinle sunucuya çok büyük iş yaptırabilir. İç içe geçmiş ilişkiler, her katmanda çarpan etkisi yaratır; birkaç yüz baytlık bir sorgu veri tabanında binlerce sorguya dönüşebilir.

Bu tarafta ölçülmesi gereken şey istek sayısı değil, maliyet:

  • Derinlik sınırı: belirli bir iç içe geçme seviyesinden sonrasını reddet.
  • Karmaşıklık/maliyet analizi: her alana bir maliyet ata, sorgunun toplam maliyetini çalıştırmadan hesapla ve bütçeyi aş reddet.
  • Sayfalama zorunluluğu: liste dönen alanlarda üst sınırlı sayfa boyutu; sınırsız liste alanı bırakma.
  • Zaman aşımı ve eşzamanlılık sınırı: tek bir sorgunun havuzu tüketmesini engelle.
  • Kalıcı sorgular (persisted queries): üretimde yalnızca önceden onaylanmış sorgu kimliklerini kabul et. Serbest sorgu yüzeyini tamamen kapatan en etkili önlem.

Alias ve batching ile hız sınırı atlatma

GraphQL'e özgü ve gözden kaçmaya en müsait sınıf bu. İstemci, aynı alanı farklı takma adlarla (alias) tek bir sorgu içinde defalarca çağırabilir; birçok sunucu ayrıca tek HTTP isteğinde birden fazla operasyonu dizi hâlinde kabul eder.

Sonuç: HTTP isteği sayısını sayan bir hız sınırı, tek istekte yüzlerce denemeyi göremez. Kimlik doğrulama, tek kullanımlık kod doğrulama, kupon kodu deneme, kullanıcı adı sayımı gibi işlemler bu yolla etkili biçimde kaba kuvvete açılır.

Savunma tarafı:

  • Hız sınırını HTTP isteği değil, çözümleyici çağrısı veya maliyet birimi üzerinden say.
  • Sorgu başına alias sayısını ve aynı alanın tekrar sayısını sınırla.
  • Operasyon dizisi (batching) desteğini üretimde kapat ya da dizi uzunluğunu sınırla.
  • Hassas mutasyonlarda hesap/IP bazlı ayrı ve katı sayaçlar kullan; başarısız denemeleri ayrıca izle.

Resolver'ın arkası: enjeksiyon ve SSRF

GraphQL bir sorgu dili, ama arkasındaki veri katmanı için bir güvenlik katmanı değil. Argümanlar doğrudan bir veri tabanı sorgusuna, bir dosya yoluna veya bir dış isteğe akıyorsa, klasik enjeksiyon sınıflarının hepsi aynen geçerlidir.

  • Veri tabanı enjeksiyonu: filtre veya sıralama argümanlarının sorgu metnine birleştirilmesi. Tip sistemi, dize içeriğini denetlemez.
  • Sunucu taraflı istek sahteciliği: URL alan mutasyonlar (dosya içe aktarma, web kancası kaydı, önizleme üretme) iç ağa yönlendirilebilir.
  • Dosya yükleme: çok parçalı yükleme uzantısı kullanan şemalarda tip, boyut ve depolama konumu denetimleri.
  • Özel skaler tipler: doğrulama mantığı burada yazılır ve sıklıkla eksiktir; tip adının varlığı doğrulandığı anlamına gelmez.

Taşıma katmanı: CSRF, abonelikler, hata mesajları

  • CSRF. Sunucu sorguları GET üzerinden ya da basit içerik tipleriyle kabul ediyorsa ve kimlik doğrulama çerezle yapılıyorsa, siteler arası istek sahteciliği mümkün hale gelir. Yazma işlemleri yalnızca POST ve application/json ile kabul edilmeli, çerezlerde uygun SameSite ayarı bulunmalı.
  • Abonelikler. WebSocket bağlantılarında kimlik doğrulama çoğu zaman yalnızca bağlantı kurulurken yapılır. Oturum iptal edildiğinde açık bağlantının da sonlanması gerekir; ayrıca köken (origin) kontrolü atlanmamalı.
  • Hata mesajları. Üretimde yığın izleri, iç alan adları ve veri tabanı hataları istemciye dönmemeli. Bu mesajlar hem şema keşfini hem de enjeksiyon denemelerinin geri bildirim döngüsünü besler.
  • Geliştirici arayüzleri. Üretimde açık bırakılan sorgu oynatıcıları, keşfi ve denemeyi ücretsiz hale getirir.

Savunma listesi

Önlem Kapattığı sınıf
Resolver seviyesinde alan ve nesne yetkilendirmesiBozuk nesne/alan yetkilendirmesi
Sorgu derinliği ve maliyet bütçesiKaynak tüketimi, hizmet dışı bırakma
Kalıcı sorgu listesi (üretimde serbest sorgu yok)Keşif + maliyet + beklenmeyen şekiller
Alias ve batch sınırı, maliyet bazlı hız sınırıHız sınırı atlatma, kaba kuvvet
Zorunlu ve üst sınırlı sayfalamaToplu veri çekme
Üretimde introspection ve öneri mesajları kapalıKeşif kolaylığı (tek başına savunma değil)
Sadeleştirilmiş hata mesajlarıBilgi sızıntısı
POST + JSON zorunluluğu, SameSite çerezlerCSRF
Argümanların parametreli sorgularla kullanılmasıEnjeksiyon
Çıkış isteklerinde adres allowlistSSRF

Özet

GraphQL'de test ederken benim izlediğim sıra şu: önce şemayı çıkar, sonra yetkilendirmeyi zorla — çünkü yüksek etkili bulguların neredeyse tamamı orada. Ardından maliyet ve hız sınırı davranışını ölç, en sonda resolver'ların arkasındaki klasik sınıflara bak.

Savunma tarafında ise tek bir cümlelik özet mümkün: GraphQL'in esnekliği istemciye ait olmalı, sunucuya değil. Sunucu tarafında ne dönebileceği, ne kadar maliyet üretebileceği ve kimin neyi görebileceği önceden ve açıkça sınırlanmadıkça, şemanın kendisi saldırganın en iyi belgesidir.

Kaynaklar

  1. OWASP GraphQL Cheat Sheet: cheatsheetseries.owasp.org
  2. OWASP API Security Top 10: owasp.org/API-Security
  3. GraphQL Specification — GraphQL Foundation.
  4. PortSwigger Web Security Academy — GraphQL API vulnerabilities: portswigger.net/web-security/graphql