GraphQL

Naučite hakovanje AWS-a od nule do heroja sa htARTE (HackTricks AWS Red Team Expert)!

Drugi načini podrške HackTricks-u:

Uvod

GraphQL je istican kao efikasna alternativa REST API-ju, nudeći pojednostavljen pristup za upitivanje podataka sa backend-a. Za razliku od REST-a, koji često zahteva brojne zahteve na različitim endpoint-ima za prikupljanje podataka, GraphQL omogućava dobijanje svih potrebnih informacija putem jednog zahteva. Ovo pojednostavljenje značajno koristi developerima smanjujući složenost njihovih procesa dobijanja podataka.

GraphQL i Bezbednost

Sa pojavom novih tehnologija, uključujući GraphQL, pojavljuju se i nove bezbednosne ranjivosti. Ključna stvar koju treba imati na umu je da GraphQL ne uključuje mehanizme autentifikacije po default-u. Odgovornost je developera da implementiraju takve bezbednosne mere. Bez odgovarajuće autentifikacije, GraphQL endpoint-i mogu otkriti osetljive informacije neautentifikovanim korisnicima, predstavljajući značajan bezbednosni rizik.

Napadi Brute Force na Direktorijume i GraphQL

Da bi se identifikovali izloženi GraphQL instance, preporučuje se uključivanje određenih putanja u napade brute force na direktorijume. Ove putanje su:

  • /graphql

  • /graphiql

  • /graphql.php

  • /graphql/console

  • /api

  • /api/graphql

  • /graphql/api

  • /graphql/graphql

Identifikacija otvorenih GraphQL instanci omogućava pregled podržanih upita. To je ključno za razumevanje podataka dostupnih putem endpoint-a. GraphQL-ov sistem introspekcije olakšava ovo detaljisanjem upita koje šema podržava. Za više informacija o ovome, pogledajte GraphQL dokumentaciju o introspekciji: GraphQL: A query language for APIs.

Fingerprint

Alat graphw00f je sposoban da otkrije koji GraphQL engine se koristi na serveru i zatim ispisuje neke korisne informacije za bezbednosnog revizora.

Univerzalni upiti

Da biste proverili da li je URL GraphQL servis, može se poslati univerzalni upit, query{__typename}. Ako odgovor uključuje {"data": {"__typename": "Query"}}, potvrđuje da URL hostuje GraphQL endpoint. Ovaj metod se oslanja na GraphQL-ovo polje __typename, koje otkriva tip upitanog objekta.

query{__typename}

Osnovna enumeracija

GraphQL obično podržava GET, POST (x-www-form-urlencoded) i POST(json). Iako je radi sigurnosti preporučljivo dozvoliti samo json kako bi se sprečili CSRF napadi.

Introspekcija

Da biste koristili introspekciju za otkrivanje informacija o šemi, upitajte polje __schema. Ovo polje je dostupno na korenskom tipu svih upita.

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

Sa ovim upitom pronaći ćete ime svih tipova koji se koriste:

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

Sa ovim upitom možete izvući sve tipove, njihova polja i argumente (i tip argumenata). Ovo će biti vrlo korisno da biste znali kako da upitate bazu podataka.

Greške

Interesantno je znati da li će greške biti prikazane jer će doprineti korisnim informacijama.

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

Nabrajanje šeme baze podataka putem introspekcije

Ako je omogućena introspekcija, ali gornji upit ne uspe, pokušajte ukloniti direktive onOperation, onFragment i onField iz 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
}
}
}
}

Inline 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 upit koji će izbaciti sve metainformacije iz graphql-a (imena objekata, parametri, tipovi...)

Ako je omogućena introspekcija, možete koristiti GraphQL Voyager da biste videli u GUI-u sve opcije.

Upitovanje

Sada kada znamo koje vrste informacija su sačuvane u bazi podataka, pokušajmo izvući neke vrednosti.

U introspekciji možete pronaći koji objekat možete direktno upitati (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", koji je takođe tip objekta. Stoga možete upitati objekat zastave.

Imajte na umu da je tip upita "flags" "Flags", a ovaj objekat je definisan na sledeći način:

Možete videti da su objekti "Flags" sastavljeni od imena i vrednosti. Zatim možete dobiti sva imena i vrednosti zastava upitom:

query={flags{name, value}}

Imajte na umu da u slučaju da je objekat za upit primitivan tip poput stringa kao u sledećem primeru

Možete ga jednostavno upitati sa:

query={hiddenFlags}

U još jednom primeru gde su postojala 2 objekta unutar "Query" tipa objekta: "user" i "users". Ako ovi objekti ne zahtevaju nikakav argument za pretragu, mogli biste dobiti sve informacije iz njih samo tražeći podatke koje želite. U ovom primeru sa Interneta mogli biste izvući sačuvane korisničke imene i lozinke:

Međutim, u ovom primeru, ako pokušate to uraditi, dobićete ovu grešku:

Izgleda da će pretraga koristiti "uid" argument tipa Int. U svakom slučaju, već smo znali da je u odeljku Osnovna enumeracija predložen upit koji nam je pokazivao sve potrebne informacije: query={__schema{types{name,fields{name, args{name,description,type{name, kind, ofType{name, kind}}}}}}}

Ako pročitate priloženu sliku kada pokrenete taj upit, videćete da je "user" imao arg "uid" tipa Int.

Dakle, vršeći neki lagani uid brute force, otkrio sam da je u uid=1 dobijeno korisničko ime i lozinka: query={user(uid:1){user,password}}

Imajte na umu da sam otkrio da mogu tražiti parametre "user" i "password" jer ako pokušam da pronađem nešto što ne postoji (query={user(uid:1){noExists}}) dobijam ovu grešku:

I tokom faze enumeracije otkrio sam da je objekat "dbuser" imao polja "user" i "password.

Triks za ispuštanje upita niske (zahvaljujući @BinaryShadow_)

Ako možete pretraživati po tipu niske, kao što je: query={theusers(description: ""){username,password}} i tražite prazan string, ispuštaće sve podatke. (Napomena: ovaj primer nije povezan sa primerom iz tutorijala, za ovaj primer pretpostavite da možete pretraživati koristeći "theusers" po polju tipa String nazvanom "description").

Pretraga

U ovom okruženju, baza podataka sadrži osobe i filmove. Osobe su identifikovane svojim emailom i imenom; filmovi po njihovom imenom i ocenom. Osobe mogu biti prijatelji jedni sa drugima i takođe imati filmove, što ukazuje na odnose unutar baze podataka.

Možete pretraživati osobe po imenu i dobiti njihove emaile:

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

Primetite kako je naznačeno da se dobije name subscribedMovies 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 odnosi nekoliko različitih objekata korišćenjem 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 introspekciji možete pronaći deklarisane mutacije. Na sledećoj slici "MutationType" se naziva "Mutation" i objekat "Mutation" sadrži imena mutacija (kao što je "addPerson" u ovom slučaju):

U ovom postavci, baza podataka sadrži osobe i filmove. Osobe su identifikovane njihovim emailom i imenom; filmovi po njihovom imenom i ocenom. Osobe mogu biti prijatelji jedni sa drugima i takođe imati filmove, što ukazuje na odnose unutar baze podataka.

Mutacija za kreiranje novih filmova 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 tipovi podataka naznačeni u upitu.

Dodatno, baza podataka podržava operaciju mutacije, nazvanu addPerson, koja omogućava kreiranje osoba zajedno sa njihovim povezivanjem sa postojećim prijateljima i filmovima. Važno je napomenuti da prijatelji i filmovi moraju već postojati u bazi podataka pre nego što budu povezani sa novostvorenom 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
}
}
}
}
}
}

Preopterećenje direktive

Kao što je objašnjeno u jednoj od ranjivosti opisanih u ovom izveštaju, preopterećenje direktive podrazumeva poziv direktive čak i milione puta kako bi server trošio operacije dok nije moguće izvršiti DoS napad.

Grubo silovanje u 1 API zahtevu

Ove informacije su preuzete sa https://lab.wallarm.com/graphql-batching-attack/. Autentifikacija putem GraphQL API-a sa istovremenim slanjem mnogo upita sa različitim pristupnim podacima radi provere. To je klasičan napad grubom silom, ali sada je moguće poslati više od jednog parova korisničko ime/lozinka po HTTP zahtevu zbog mogućnosti grupisanja u GraphQL-u. Ovaj pristup bi prevario spoljne aplikacije za praćenje stope uveravajući ih da je sve u redu i da nema bota koji pokušava da pogodi lozinke.

Ispod možete pronaći najjednostavniju demonstraciju zahteva za autentifikaciju aplikacije, sa 3 različita para email/lozinka istovremeno. Očigledno je moguće poslati hiljade u jednom zahtevu na isti način:

Kao što možemo videti sa snimka odgovora, prvi i treći zahtevi vratili su null i odražavali odgovarajuće informacije u odeljku error. Druga mutacija je imala tačne autentifikacione podatke i odgovor je imao tačan autentifikacioni token sesije.

GraphQL Bez introspekcije

Sve više graphql endpointa onemogućava introspekciju. Međutim, greške koje graphql baca kada primi neočekivan zahtev su dovoljne za alate poput clairvoyance da rekonstruišu veći deo šeme.

Osim toga, Burp Suite ekstenzija GraphQuail prati GraphQL API zahteve koji prolaze kroz Burp i gradi internu GraphQL šemu sa svakim novim upitom koji vidi. Takođe može otkriti šemu za GraphiQL i Voyager. Ekstenzija vraća lažni odgovor kada primi introspekcijski upit. Kao rezultat, GraphQuail prikazuje sve upite, argumente i polja dostupna za korišćenje unutar API-ja. Za više informacija proverite ovo.

Lep wordlist za otkrivanje GraphQL entiteta može se pronaći ovde.

Zaobilaženje odbrana introspekcije GraphQL-a

Zaobilaženje Odbrana Introspekcije GraphQL-a

Da bi se zaobišle restrikcije na introspekcijske upite u API-ima, ubacivanje specijalnog karaktera nakon ključne reči __schema pokazuje se efikasnim. Ovaj metod iskorišćava uobičajene propuste programera u regex obrascima koji ciljaju blokiranje introspekcije fokusirajući se na ključnu reč __schema. Dodavanjem karaktera poput razmaka, novih linija i zareza, koje GraphQL ignoriše ali možda nisu uzete u obzir u regex-u, restrikcije mogu biti zaobiđene. Na primer, introspekcijski upit sa novom linijom nakon __schema može zaobići takve odbrane:

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

Ako ne uspete, razmotrite alternativne metode zahteva, poput GET zahteva ili POST sa x-www-form-urlencoded, jer se ograničenja mogu odnositi samo na POST zahteve.

Otkrivanje izloženih GraphQL struktura

Kada je introspekcija onemogućena, ispitivanje izvornog koda veb sajta radi unapred učitanih upita u JavaScript bibliotekama korisna je strategija. Ovi upiti mogu se pronaći koristeći karticu Sources u alatkama za razvoj, pružajući uvide u šemu API-ja i otkrivajući potencijalno izložene osetljive upite. Komande za pretragu unutar alatki za razvoj su:

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

CSRF u GraphQL-u

Ako ne znate šta je CSRF, pročitajte sledeću stranicu:

pageCSRF (Cross Site Request Forgery)

Možete pronaći nekoliko GraphQL endpointa konfigurisanih 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 krajnjih tačaka takođe podržava form-urlencoded POST zahteve:

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

Dakle, pošto se CSRF zahtevi poput prethodnih šalju bez prethodnih zahteva za pretpostavljanje, moguće je izvršiti promene u GraphQL-u zloupotrebom CSRF-a.

Međutim, imajte na umu da je nova podrazumevana vrednost kolačića za samesite zastavicu u Chrome-u Lax. To znači da će kolačić biti poslat samo sa veb strane treće strane u GET zahtevima.

Imajte na umu da je obično moguće poslati upit zahtev takođe kao GET zahtev i CSRF token možda neće biti validiran u GET zahtevu.

Takođe, zloupotrebom XS-Search napada moguće je eksfiltrirati sadržaj sa GraphQL endpointa zloupotrebom korisničkih podataka.

Za više informacija proverite originalni post ovde.

Autorizacija u GraphQL-u

Mnoge GraphQL funkcije definisane na endpointu mogu proveravati samo autentifikaciju zahtevaoca, ali ne i autorizaciju.

Izmena ulaznih promenljivih upita može dovesti do otkrivanja osetljivih detalja naloga leaked.

Mutacija čak može dovesti do preuzimanja naloga pokušavajući da se izmene podaci drugog naloga.

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

Bypass autorizacije u GraphQL-u

Povezivanje upita zajedno može zaobići slab sistem autentikacije.

U donjem primeru možete videti da je operacija "forgotPassword" i da bi trebalo da izvrši samo upit forgotPassword koji je s njim povezan. Ovo se može zaobići dodavanjem upita na kraju, u ovom slučaju dodajemo "register" i korisničku promenljivu da se sistem registruje kao novi korisnik.

Zaobilaženje ograničenja brzine korišćenjem aliasa u GraphQL-u

U GraphQL-u, aliasi su moćna funkcija koja omogućava eksplicitno imenovanje svojstava prilikom slanja zahteva API-ju. Ova mogućnost je posebno korisna za dobijanje više instanci istog tipa objekta u jednom zahtevu. Aliasima se može prevazići ograničenje koje sprečava GraphQL objekte da imaju više svojstava sa istim imenom.

Za detaljnije razumevanje GraphQL aliasa, preporučuje se sledeći resurs: Alias.

Iako je primarni cilj aliasa smanjenje potrebe za brojnim API pozivima, identifikovan je neplanirani slučaj upotrebe gde se aliasi mogu iskoristiti za izvođenje napada grubom silom na GraphQL endpoint. Ovo je moguće jer su neki endpointovi zaštićeni limitatorima brzine dizajniranim da spreče napade grubom silom ograničavanjem broja HTTP zahteva. Međutim, ovi limitatori brzine možda ne uzimaju u obzir broj operacija unutar svakog zahteva. S obzirom na to da aliasi omogućavaju uključivanje više upita u jedan HTTP zahtev, mogu zaobići takve mere ograničenja brzine.

Razmotrite donji primer, koji ilustruje kako se aliasovani upiti mogu koristiti za proveru validnosti kodova popusta u prodavnici. Ovaj metod bi mogao zaobići ograničenje brzine jer kompajlira nekoliko upita u jedan HTTP zahtev, što potencijalno omogućava proveru brojnih kodova popusta 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
}
}

Alati

Skeneri ranjivosti

Klijenti

Automatski testovi

Reference

Naučite hakovanje AWS-a od nule do heroja sa htARTE (HackTricks AWS Red Team Expert)!

Drugi načini podrške HackTricks-u:

Last updated