Autor del tema
#0
1) “Blind” GraphQL sızdırma ne demek?
Introspection kapalıyken saldırgan şemayı doğrudan listeleyemez; ama yine de şu yollarla ne var ne yok anlayıp veri çekebilir:
- Meşru istemci trafiğinden şema çıkarma: Web/Mobile uygulamanın attığı sorgular zaten alan adlarını/type’ları ifşa eder.
- Hata mesajı/validasyon farklarından çıkarım: “Böyle bir alan yok” / “Bu argüman beklenmiyor” gibi detaylı hatalar, alan isimlerini tahmin etmeyi kolaylaştırır.
- Persisted queries / operation registry: Prod’da allowlist varsa bile, registry yanlış yönetilirse “fazla geniş” operasyonlar veri sızdırır.
- Asıl kritik nokta: Broken access control
GraphQL’de sızdırmaların büyük kısmı field-level / object-level authorization eksikliğidir:
- “Ben görebiliyorum” varsayımıyla resolver’da kontrol yoksa,
- Bir alan (ör. email, roles, billing) sadece UI’da gizlenmiş ama API’da serbestse,
introspection kapalı olsa bile veri çıkar.
- “Ben görebiliyorum” varsayımıyla resolver’da kontrol yoksa,
Özet: Introspection kapatmak “keşfi” zorlaştırır; ama yetkilendirme ve veri minimizasyonu zayıfsa sızıntıyı engellemez.
2) Uygulamada (yasal) nasıl test edilir?
Bu bölüm “kendi sistemin / izinli pentest / staging” için savunmacı test planı.
A) Şema keşfi yerine “meşru kaynaklardan envanter çıkar”
- Frontend repo’da *.graphql, Apollo/Relay query’leri, codegen çıktıları
- Mobil uygulama network log’ları (izinli cihazınızda)
- API gateway log’larında görünen operationName’ler
Amaç: “Zaten uygulamanın kullandığı alanlar” listesini çıkarıp risk analizi yapmak.
B) “Kör sızıntı” açısından en riskli 6 kontrol
- Object-level auth (BOLA): Bir objeyi isteme hakkı var mı? (ör. başka kullanıcı profili)
- Field-level auth: Objeyi görse bile her alanı görebilir mi? (email, telefon, invoice, token vb.)
- Tenant izolasyonu: org_id / tenant_id her resolver’da enforced mı?
- Error mesajı sızıntısı: Prod’da detaylı şema/stack trace dönüyor mu?
- Query complexity / depth: Aşırı nested sorgularla veri “toplu” çekilebiliyor mu?
- Batching / alias abuse: Tek istekle çok sayıda entity çekilebiliyor mu? (Limit/Rate yoksa)
C) Negatif test yaklaşımı (en güvenlisi)
“Payload” değil, rol ve yetki senaryosu test edilir:
- Rol A kullanıcı → Rol B alanlarına erişebiliyor mu?
- Kendi tenant’ı dışındaki objeleri görebiliyor mu?
- UI’da görünmeyen alanlar API’dan geliyor mu?
- Aynı endpoint farklı “operation” ile fazla veri döndürüyor mu?
3) Introspection kapalıyken sızıntıyı büyüten yaygın hatalar
- Tek endpoint, tek auth kontrol: “Giriş yaptıysa her şeye erişir” tarzı.
- Resolver’da kontrol yerine UI’da gizleme: “Buton yok ki erişemesin” yanılgısı.
- Aşırı geniş search/filter argümanları: Sunucu tarafı validation yok.
- Union/interface alanlarında “default resolver” ile beklenmedik alanlar açılması
- Debug mod / tracing açık (Apollo tracing, stack trace, error extensions)
- Persisted query allowlist var ama operasyonlar fazla kapsamlı (tek operasyon her şeyi getiriyor)
4) Koruma: “Next-Gen” GraphQL hardening paketi
Aşağıdaki önlemler birlikte uygulanınca “blind sızıntı” sınıfı ciddi düşer.
4.1 Yetkilendirme: her resolver’da zorunlu
- Object-level: Bu objeye erişim hakkı var mı?
- Field-level: Bu alanı görme hakkı var mı?
- Default deny: Belirsiz durumda kapat.
Kural: “GraphQL auth middleware var” yetmez; resolver içi kontrol şart.
4.2 Veri minimizasyonu ve şema hijyeni
- Hassas alanları (email/phone/billing) ayrı type/field yap ve role bağlı aç.
- Admin alanları ayrı endpoint veya ayrı schema (mümkünse).
- Public type’larda gereksiz ilişki zincirlerini kısalt.
4.3 Hata ve gözlemlenebilirlik
- Prod’da detaylı hata mesajı kapalı, stack trace yok.
- extensions içinde şema ipuçları dönmesin.
- X-Request-ID + operationName + kullanıcı/tenant + complexity logla.
4.4 Query kontrolü (DoS + toplu sızıntı önleme)
- Depth limit (örn. max depth)
- Query complexity limit
- Rate limit (user/IP)
- Timeout / circuit breaker
- Batching kontrolü (gerek yoksa kapat)
Aşağıda savunma amaçlı örnek (Node/Apollo mantığıyla) – saldırı değil, limit koyma:
CODE
12345678910111213141516171819// Savunma odaklı: depth + complexity + hata sanitizasyonu (örnek yaklaşım)
import depthLimit from "graphql-depth-limit";
import { createComplexityLimitRule } from "graphql-validation-complexity";
const validationRules = [
depthLimit(8),
createComplexityLimitRule(1000, {
onCost: cost => console.log("GraphQL cost:", cost),
formatErrorMessage: cost => `Query too complex: ${cost}`,
}),
];
const formatError = (err) => {
// prod: detay sızdırma
return { message: "Request failed.", code: "GRAPHQL_ERROR" };
};
// Apollo Server gibi bir yerde:
// new ApolloServer({ schema, validationRules, formatError })
