Esegua questi cinque test su un deployment di staging della Sua API GraphQL prima che l'endpoint diventi pubblico. Configuri lo staging come sarà la produzione, altrimenti i risultati valgono poco.
I test seguono le quattro aree della GraphQL Cheat Sheet di OWASP: validazione dell'input, query costose, controllo degli accessi e configurazione predefinita insicura.
1. Verifichi le impostazioni predefinite
Errori troppo verbosi, introspection e GraphiQL sono le tipiche impostazioni predefinite insicure. Li provi tutti e tre. Parta dalla più piccola query di introspection:
{__schema{types{name}}}
Verifichi se il server risponde con i dettagli dello schema o rifiuta la richiesta. Poi cerchi GraphiQL nel browser. Infine invii una query malformata e una query con un argomento errato, e legga l'errore. Nomi dei resolver, stack trace o messaggi del database nella risposta sono una mappa per un attaccante.
La correzione è di configurazione. Disattivi introspection e GraphiQL dove l'API non è pensata per essere esplorata, restituisca errori generici e conservi i dettagli nei log lato server.
2. Faccia passare input ostile da ogni argomento
L'utente fornisce degli identificatori, e i data fetcher li usano poi in richieste HTTP, query al database e altre chiamate. È lì che avviene l'injection. Provi ogni argomento di ogni query e mutation con i payload che userebbe su qualsiasi altra API: SQL e NoSQL injection, OS command injection, SSRF e CRLF injection.
Per renderlo automatizzabile, generi gli elenchi degli argomenti a partire dallo schema mentre l'introspection è ancora attiva in staging, faccia scorrere i Suoi payload su di essi e disattivi l'introspection al termine.
Che cosa imporre:
- Tipi GraphQL specifici, come scalari ed enum, e schemi definiti per l'input delle mutation.
- Una allowlist di caratteri, non una denylist. Un input alfanumerico e non unicode è un buon punto di partenza perché esclude molti attacchi.
- Statement parametrizzati per tutto ciò che viene passato a un interprete. Se usa un ORM o un ODM, lo usi come da documentazione, perché l'ORM injection resta possibile.
- Rifiuto dell'input non valido senza rivelare come funziona la validazione.
3. Trovi la query costosa
Un'API è vulnerabile se manca un qualsiasi limite alle risorse o se è impostato male. La voce OWASP sul consumo illimitato di risorse elenca timeout di esecuzione, memoria massima allocabile, file descriptor, processi, dimensione massima dei file caricati, record per pagina, operazioni per richiesta del client e limiti di spesa presso fornitori terzi.
Per il test, costruisca la query più profonda consentita dal Suo schema seguendo le relazioni in un ciclo. Adatti i nomi al Suo schema:
{ users { orders { customer { orders { customer { email } } } } } }
Continui ad aggiungere livelli finché il server non rifiuta la richiesta o rallenta. Poi richieda una dimensione di pagina molto grande e invii un payload di dimensioni eccessive.
Il consiglio della cheat sheet è di aggiungere controlli che limitino o impediscano le query troppo costose. Lo combini con un tetto alla dimensione di pagina, un timeout di esecuzione e dimensioni massime per stringhe, array e upload. Calibri i valori su una settimana di log del traffico reale, in modo che i client legittimi restino sotto le soglie.
4. Aggiri il rate limiter con il batching
Il batching è un attacco brute force che nasconde il numero di richieste. Nel secondo scenario della voce OWASP, una mutation di upload è protetta da un rate limiter e da un controllo sulla dimensione. L'attaccante invia una sola POST a /graphql contenente un array di quasi mille operazioni. Il limiter vede una sola richiesta e il server esaurisce la memoria. Se la Sua API accetta upload, il nostro articolo su come progettare e testare upload di file sicuri tratta questo aspetto.
Provi lo stesso approccio su una mutation di login o OTP:
[
{"query": "mutation {login(user: \"alice\", pass: \"guess1\") {token}}"},
{"query": "mutation {login(user: \"alice\", pass: \"guess2\") {token}}"}
]
Verifichi se il Suo limite conta ogni operazione dell'array e se i tentativi falliti raggiungono comunque la logica di lockout. Poi provi qualsiasi altro modo che il Suo server consenta di racchiudere più operazioni in una sola richiesta.
Limiti il numero di operazioni per richiesta del client e regoli la frequenza con cui un singolo client può eseguire una stessa operazione sensibile, come la validazione di un OTP. Ponga un tetto anche alla spesa: nel primo scenario della voce, uno script fa partire decine di migliaia di SMS a pagamento tramite un flusso di password dimenticata. Imposti limiti di spesa presso i fornitori terzi e avvisi di fatturazione dove i limiti non sono possibili.
5. Richieda il campo di un altro utente
L'autenticazione Le dice chi sta chiamando. Non decide quali campi può leggere. La guida all'autorizzazione di GraphQL afferma che l'esecuzione dovrebbe iniziare dopo che il middleware di autenticazione ha confermato l'identità, e che il server deve comunque decidere, per ogni campo richiesto, se l'utente può vederlo.
Esegua il test con due account ordinari. Con la sessione dell'account A, richieda gli oggetti dell'account B tramite ID, poi vi acceda attraverso un oggetto correlato visibile a entrambi gli account. Adatti i nomi al Suo schema:
# Direct lookup: should be refused for account A
{ customer(id: "CUSTOMER_ID_OF_B") { email } }
# Same data through a shared parent: must also be refused
{ product(id: "SHARED_PRODUCT_ID") { orders { customer { email } } } }
Se la prima query viene rifiutata e la seconda restituisce l'email di B, il controllo si trova sul lookup di primo livello e non è mai stato applicato al tipo annidato. Ripeta il test senza alcuna sessione. Se gli stessi dati sono serviti tramite REST o un'API mobile, provi anche quei punti di ingresso. Il nostro articolo su come testare ogni ID della Sua app per IDOR e BOLA tratta lo stesso approccio al di là di GraphQL.
L'autorizzazione dentro il resolver di un campo va bene per i prototipi, ma va duplicata per ogni punto di ingresso. Se le copie divergono, gli utenti vedono dati diversi a seconda dell'API che usano. Sposti la logica nel livello di business logic, così da avere un'unica fonte di verità. Nell'esempio della guida, postRepository.getBody riceve l'utente e il post e restituisce il corpo solo all'autore. Passi un oggetto utente completamente idratato, non un token opaco o una chiave API.
Che cosa non Le dirà uno scanner
Gli strumenti automatici offrono ampiezza, ma la OWASP Web Security Testing Guide sottolinea che sono costruiti per le applicazioni in generale, non per il Suo codice personalizzato e le Sue regole di business. Le falle più gravi sono spesso intrecciate con la business logic.
I test da 1 a 3 sono in gran parte automatizzabili. Il test 5 richiede lavoro manuale, perché qualcuno deve capire quale tenant possiede quale oggetto, ed è quindi quello che conviene di più affidare a un team esterno. Il nostro servizio di penetration test copre proprio questo.
Da dove iniziare
- Invii la query di introspection e apra GraphiQL sul deployment di staging. Disattivi ciò che risponde e riprovi con una query malformata per controllare gli errori.
- Elenchi ogni argomento di query e mutation. Confermi che ciascuno abbia un tipo specifico o un enum e un validatore basato su allowlist.
- Invii la query profondamente annidata, una richiesta con dimensione di pagina elevata e un payload sovradimensionato. Imposti i limiti, poi rinvii le query e confermi che ciascuna venga rifiutata.
- Invii un batch in forma di array contro le mutation di login e OTP. Imponga un limite per richiesta e sulle ripetizioni delle operazioni sensibili.
- Esegua il test con due account, comprese le relazioni annidate, su ogni tipo che contiene dati personali o commerciali. Sposti nel livello di business logic ogni controllo trovato in un resolver.
