GRAPHQL API’NIN GÜVENLİĞİ NASIL SAĞLANIR
Tam olarak ne istediğinizi, istediğiniz zaman sorgulamanın karışık güvenlik sonuçları vardır. Bu yazımda GraphQL API’nin client tarafından kötüye kullanılan sorgulardan nasıl korunduğunu anlatacağım.
Bir GraphQL API’sini uygun bir şekilde güvenli hale getirmek için, sunucuların zaman aşımına uğraması, derinlik sınırlaması veya DoS saldırılarını azaltmak için sorguların karmaşıklığını sınırlaması gerekmektedir. Sunucular, uygun olduğunda her zaman sorguları beyaz listeye almalıdır. Ayrıca tüm client tarafındaki sorguların maliyetlerini takip etmelidir. API geliştiricileri, SQL Injection, Langsec vb sorunları hafifletmek için sorgu bağlamıyla bir authentice’yi dikkatlice incelemeli şemaların gizli kalmasını sağlamalıdır.
Yukarıda bahsettiklerimin her birini şimdi adım adım basit bir şekilde GraphQL API’de uygulayalım.
Query Timeouts
Bu, esasen her bir sorgu için maksimum zaman sınırları belirlemektir. 3 saniyelik bir zaman aşımı ile yapılandırılmış bir sunucu, 3 saniyelik zaman geçtikten hemen sonra herhangi bir sorgunun yürütülmesini durdurur. Zaman aşımlarının uygulanması kolay olsa da, zaman aşımı başladığında saldırgan zaten erişim kazanmış olabilir. Zaman aşımları genellikle son çare olarak büyük sorgulara karşı koruma sağlar.
Query Depth
Saldırganlar yararlı sorgular göndermek yerine, genellikle maliyetli, iç içe geçmiş sorgular göndererek hizmetinizi, veritabanlarınızı ve ağınızı diğer uygulamalara yönelik web hizmetinizi aşırı yükler.
Sınırsız GraphQL sorguları, saldırganların sorgu derinliğini kötüye kullanmasına izin verir. Bu olası bir DoS güvenlik açığıdır. Yeterli derinlikle bu çok büyük zararlar verebilir.
Cyclical Queries
Paylaşımlarınızın bulunduğu ve Yazar bilgilerini aldığınız bir sorgu düşünün. GraphQL ile bunu karmaşık döngüsel sorgulara kolaylıkla alabilirsiniz:
# cyclical query
# derinlik: 8+
query cyclical {
author(id: "xyz") {
posts {
author {
posts {
author {
posts {
author {
... {
...
}
}
}
}
}
}
}
}
}
Inline and Named Fragments
GraphQL’de, satır içleri veya adlandırdığınız kısımlar derinliği artırmaz.
# inline fragment
# derinlik: 1
query Satir_ici{
authors
... on Query {
posts
}
}
# named fragment
# derlinlik: 1
query Test{
... adlandırılmis_Test
}
fragment adlandırılmis_Test on Query {
posts
}
Maksimum sorgu derinliği, sorgu derinliğini sınırlamak için kullanılır. Bir GraphQl sunucusu daha sonra derinliğe bağlı olarak sorguyu reddebilir.
Limiting Query Depth with
graphql-depth-limit
Derinlik limitini belirlemek için bir örnek yapalım. graphql_utilites kütüphanesi derinliği sınırlayabilir.
from graphql_utilities import ExtendedExecutionContext
query = """
{
posts(first: 5) {
postId
author {
authorId
posts(first: 5) {
postId
author {
authorId
posts(first: 5) {
postId
author {
authorId
posts(first: 5) {
postId
// Depth: 7
}
}
}
}
}
}
}
}
"""
results = graphql_sync(schema=schema,
source=query,
execution_context_class=ExtendedExecutionContext,
context_value={
"depth_analysis": {
"max_depth": 5
}
})
results
# ExecutionResult(data=None, errors=[GraphQLError("Reached max depth of 5")])
Query Complexity
Şemamızda diğerlerinden daha maliyetli olan alanlar vardır mutlaka.GraphQL sunucumuza ek yük getirdikleri ve DoS saldırıları için de kullanabilecekleri en maliyetli sorgular sınırlandırılmalıdır. Sorgu karmaşıklığı, karmaşık alanı tamsayılar ile tanımlayıp kısıtlar. Her alanın karmaşıklığını bir tam sayı ile gösterelim.
query simple {
author(id: "xyz") { # karmaşıklık: 1
posts(first: 5) { # karmaşıklık: 5
title # karmaşıklık: 1
}
}
}
Sorgunun karmaşıklığı 7 olacaktır. Şemamızda maksimum karmaşıklığı 5 olarak ayarlasaydık, bu sorgu başarısız olur.
Limiting Query Complexity with
graphql-validation-complexity
Maliyet analizini gerçekleştirmek için graphql-utilites kütüphanesinden yararlanalım tekrardan.
from graphql_utilities import t ExtendedExecutionContext, build_schema_with_costschema = build_schema_with_cost(source="""
type Post
@cost(complexity: 5) {
postId: ID!
title: String
@cost(complexity: 20)
}
type Query {
post(postId: ID!): Post
}
""")query = """
{
{ post(postId: "XXXXX") { postId title } }
}
"""
results = graphql_sync(schema=schema, source=query,
execution_context_class=ExtendedExecutionContext, context_value={
"cost_analysis": { "max_complexity": 8 }
})
results
# ExecutionResult(data=None, errors=[GraphQLError("25 exceeded max complexity ˓→of 8")])
Yukarıdaki örnekten de anlaşıldığı gibi maximum karmaşıklık 8 belirlenmiş iken sorgudaki toplam karmaşıklık maliyeti 25 olduğundan sorgu başarısız olmuştur.
Whitelisting Queries
White List kapsamı biraz sınırlıdır çünkü onaylanan sorgular listesinin manuel olarak tutulması gerekir. Ancak, Sorgu Derinliği ve Karmaşıklık ile karşılaştırıldığında, daha kısıtlayıcı bir yaklaşımdır.
Bununla birlikte, bir listeye indirgendiğinden, özellikle devasa genel API’larla pek kullanışlı olmayabilir.
Kötü amaçlı veya istenmeyen sorguları White List’e almadığından emin olmak için listenin iyice incelenmesi gerekir.
White List’de yapılması gereken bir diğer önemli nokta ise, meşru sorguların veya iç içe geçmiş parçaların engellenmediğinden emin olmak için dikkatlice incelenmelidir.
Throttling (Rate Limiting) Clients
Yukarıda tartışılan diğer adımların aksine, Rate Limit genellikle çok sayıda orta ölçekli sorgu yapan client’ı hedefler. Rate Limit’in ardındaki kavram oldukça basittir. Bir sorgunun maliyeti ne kadar az olursa olsun, onu önemli sayıda göndermek kaçınılmaz olarak pahalı hale gelecektir.
Rate Limit, bir client’in belirli bir zaman aralığında kaç istek gönderebileceğini sınırlar. Çoğu GraphQL API’sinde, client’ın kaynakları çok sık istemesini sınırlamak için kullanılır.
GraphQL API’de istek miktarını sınırlamak, REST kadar etkili değildir. GraphQL sorgularının doğası gereği, yalnızca birkaç pahalı istek önemli ek yüklere neden olabilir.
Protecting the GraphQL
Güvenlik açısından API’nizde SSL’i zorunlu kılmak ve günlük access log, error logları incelemek API’nizin güvenliği açısından çok önemlidir.