Application Security

GraphQL'i Güvence Altına Alma: Sorgu Derinliği ve Maliyet Analizini Ustalaşma

GraphQL, modern uygulamaların veri çekme şeklini kökten değiştirdi ve istemcilere istekleri üzerinde hassas kontrol sağlıyor. Ancak bu esneklik, aşırı karmaşık sorgular aracılığıyla hizmet reddi (DoS) saldırılarına yol açabilecek önemli bir güvenlik ödemesiyle gelir. REST API'lerinde uç noktalar önceden tanımlanmışken, GraphQL tek bir uç nokta neredeyse her sorgu yapısını çalıştırmaya olanak tanır ve bu da sunucu kaynaklarını tüketmeye çalışan kötü niyetli aktörler için birincil bir hedef haline getirir.

Bu yazıda, bu riskleri azaltmak için iki sağlam stratejiyi inceleyeceğiz: sorgu derinliği sınırlarını zorlamak ve değişken maliyet analizini uygulamak. Bu teknikler, API'nizin yük altında yanıt vermesini ve güvenli kalmasını sağlar.

Sınırsız Sorguların Tehlikesi

Kullanıcı verilerini alan basit bir sorguyu düşünün. Şema özyinelemeli ilişkilere izin veriyorsa (örneğin, bir Kullanıcının birçok Arkadaşı vardır ve her Arkadaş da bir Kullanıcıdır), bir saldırgan derinlemesine iç içe geçmiş bir sorgu hazırlayabilir. On kullanıcı çekmek yerine, on arkadaş isteyebilir, ardından her birinin on arkadaşını isteyebilir; bu da veritabanı vuruşlarında ve bellek kullanımında üssel bir büyümeye yol açar. Buna genellikle "derin sorgu" saldırısı denir.

Ayrıca, tek bir istekte büyük miktarda veri çekmek ağ bant genişliğini ve veritabanı bağlantı havuzlarını doyurabilir. Buna karşı koymak için, sorgu iş mantığınıza temas etmeden önce gelen sorgu yapısını analiz eden güvenlik önlemleri uygulamamız gerekir.

Strateji 1: Sorgu Derinliği Sınırlarını Zorlamak

Sorgu derinliği sınırlama, bir işlemin ne kadar derin iç içe olabileceğini kısıtlar. Örneğin, uygulamanız için maksimum 5 derinliğin kabul edilebilir olduğunu belirleyebilirsiniz. Bu derinliği aşan her sorgu, net bir hata mesajıyla reddedilecektir.

Çoğu GraphQL sunucusu bunu işlemek için ara katman yazılımları veya eklentiler sağlar. Aşağıda, Express sunucusu ile popüler graphql-depth-limit paketini kullanan bir örnek bulunmaktadır.

const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const depthLimit = require('graphql-depth-limit');
const schema = require('./schema');

const app = express();

app.use('/graphql', graphqlHTTP((req) => ({
  schema: schema,
  graphiql: true,
  validationRules: [
    depthLimit(5, 3, ['*'], (depth, paths) => {
      // İsteğe bağlı: İzleme için en derin yolları günlüğe kaydedin
      console.log('Sorgu derinliği aşıldı:', depth);
    }),
  ],
})));

app.listen(4000, () => console.log('Sunucu http://localhost:4000 adresinde çalışıyor'));

Bu yapılandırmada, validationRules dizisi, her sorgunun yürütülmeden önce derinlik sınırına karşı kontrol edildiğini sağlar. Üçüncü argüman ['*'], kuralı tüm sorgu türlerine uygularken, geri çağırma güvenlik izleme için anormallikleri günlüğe kaydetmenize olanak tanır.

Strateji 2: Değişken Maliyet Analizi

Derinlik sınırlama, derin iç içe geçmeyi önlese de, bireysel alanların çözümleme maliyetini hesaba katmaz. Tek bir alan, ağır bir veritabanı birleştirmesini veya harici bir API çağrısını tetikleyebilir. Maliyet analizi, her alana hesaplama karmaşıklığına dayanarak bir "maliyet" atar. Bir sorgu geldiğinde sunucu toplam maliyeti hesaplar ve tanımlanmış bir bütçeyi aşarsa reddeder.

Bu yaklaşım, derinlik sınırlamadan daha ince detaylıdır. Örneğin, 10.000 kullanıcı kimliği alan düz bir sorgunun derinliği 1 olabilir ancak devasa bir maliyeti vardır; buna karşılık hafif alanlardan oluşan derinliği 3 olan iç içe geçmiş bir sorgu ise ihmal edilebilir düzeyde olabilir.

Node.js'de basit bir maliyet analizi ara katman yazılımını nasıl uygulayabileceğinize dair bir örnek:

const { convertToCost } = require('graphql-query-complexity');

// Çözücüleriniz için maliyetleri tanımlayın
const fieldConfig = {
  User: {
    friends: { cost: 10 },
    posts: { cost: 5 },
  },
  Post: {
    comments: { cost: 15 },
  },
};

app.use('/graphql', graphqlHTTP((req) => ({
  schema: schema,
  validationRules: [
    convertToCost({
      schema,
      maximumComplexity: 1000,
      variables: {},
      onComplete: (complexity) => {
        console.log('Sorgu karmaşıklığı:', complexity);
      },
      createError(max, actual) {
        return new Error(`Sorgu çok karmaşık: ${actual}. İzin verilen maksimum karmaşıklık: ${max}`);
      },
    }),
  ],
})));

Sonuç

GraphQL için hız sınırlama uygulamak tek bir çözüm herkese uymaz. Derinlik sınırlama, özyinelemeli saldırılara karşı hızlı bir savunma sağlarken, maliyet analizi kaynak tüketimini yönetmek için granüler bir yaklaşım sunar. Her iki stratejiyi birleştirerek, GraphQL'i güçlü kılan esnekliği korurken altyapınızı koruyan dayanıklı bir savunma katmanı oluşturursunuz.

API günlüklerinizi düzenli olarak izlemeyi unutmayın. Şemanız geliştiği gibi sınırlarınız da gelişmelidir. Maliyet atamalarınızın ve derinlik eşiklerinizin düzenli denetimleri, uygulamanızın güvenli, performanslı ve ölçeklenebilir kalmasını sağlayacaktır.

Share: