Voer deze vijf tests uit op een staging-omgeving van uw GraphQL API voordat het endpoint openbaar wordt. Configureer staging zoals productie straks wordt ingericht, anders zeggen de resultaten weinig.
De tests volgen de vier gebieden uit de OWASP GraphQL Cheat Sheet: inputvalidatie, dure queries, toegangscontrole en onveilige standaardconfiguratie.
1. Controleer de defaults
Te uitgebreide foutmeldingen, introspection en GraphiQL zijn de gebruikelijke onveilige defaults. Test ze alle drie. Begin met de kleinst mogelijke introspection-query:
{__schema{types{name}}}
Kijk of de server antwoordt met schemadetails of de query weigert. Zoek daarna in een browser naar GraphiQL. Stuur ten slotte een foutieve query en een query met een ongeldig argument, en lees de foutmelding. Namen van resolvers, stack traces of databasemeldingen in het antwoord vormen een routekaart voor een aanvaller.
De oplossing zit in de configuratie. Schakel introspection en GraphiQL uit waar de API niet bedoeld is om verkend te worden, geef generieke foutmeldingen terug en bewaar de details in de logs aan de serverkant.
2. Stuur vijandige input door elk argument
De gebruiker levert identifiers aan, en data fetchers gebruiken die vervolgens in HTTP-requests, databasequeries en andere aanroepen. Daar ontstaat injection. Test elk argument van elke query en mutation met dezelfde payloads die u bij elke andere API zou gebruiken: SQL- en NoSQL-injection, OS command injection, SSRF en CRLF-injection.
Om dit scriptbaar te maken, genereert u de argumentlijsten uit het schema zolang introspection op staging nog aanstaat, laat u uw payloads over die lijsten lopen en schakelt u introspection daarna uit.
Wat u moet afdwingen:
- Specifieke GraphQL-types zoals scalars en enums, en gedefinieerde schema's voor de input van mutations.
- Een allowlist van tekens in plaats van een denylist. Alfanumerieke, niet-unicode input is een goed uitgangspunt, omdat daarmee veel aanvallen worden uitgesloten.
- Geparameteriseerde statements voor alles wat naar een interpreter gaat. Gebruikt u een ORM of ODM, gebruik die dan zoals gedocumenteerd, want ORM-injection blijft mogelijk.
- Het afwijzen van ongeldige input zonder prijs te geven hoe de validatie werkt.
3. Zoek de dure query
Een API is kwetsbaar als een resourcelimiet ontbreekt of verkeerd is ingesteld. Het OWASP-onderdeel over unrestricted resource consumption noemt onder meer execution timeouts, maximaal toewijsbaar geheugen, file descriptors, processen, maximale uploadgrootte, records per pagina, operaties per client-request en bestedingslimieten bij externe providers.
Bouw om te testen de diepste query die uw schema toestaat door in een lus relaties te volgen. Pas de namen aan op uw schema:
{ users { orders { customer { orders { customer { email } } } } } }
Voeg niveaus toe totdat de server weigert of trager wordt. Vraag daarna om een zeer grote paginagrootte en stuur een te grote payload.
Het advies uit de cheat sheet is om controles toe te voegen die te dure queries beperken of voorkomen. Combineer dat met een maximum voor de paginagrootte, een execution timeout en maximale groottes voor strings, arrays en uploads. Stel de waarden af op basis van een week aan logs met echt verkeer, zodat legitieme clients eronder blijven.
4. Batch u voorbij de rate limiter
Batching is brute force waarbij het aantal requests verborgen blijft. In het tweede scenario van het OWASP-onderdeel staat een upload-mutation achter een rate limiter en een groottecontrole. De aanvaller stuurt één POST naar /graphql met een array van bijna duizend operaties. De limiter ziet één request en de server loopt vol qua geheugen. Accepteert uw API uploads, lees dan ons artikel over het ontwerpen en testen van veilige bestandsuploads, dat die kant behandelt.
Probeer hetzelfde tegen een login- of OTP-mutation:
[
{"query": "mutation {login(user: \"alice\", pass: \"guess1\") {token}}"},
{"query": "mutation {login(user: \"alice\", pass: \"guess2\") {token}}"}
]
Controleer of uw limiet elke operatie in de array meetelt en of mislukte pogingen nog steeds uw lockoutlogica bereiken. Probeer daarna elke andere manier die uw server toestaat om meerdere operaties in één request te bundelen.
Beperk het aantal operaties per client-request en beperk hoe vaak één client dezelfde gevoelige operatie mag uitvoeren, zoals het valideren van een OTP. Begrens ook de kosten: in het eerste scenario van het onderdeel veroorzaakt een script tienduizenden betaalde sms-berichten via een wachtwoord-vergeten-flow. Stel bestedingslimieten in bij externe providers, en billing alerts waar limieten niet mogelijk zijn.
5. Vraag om het veld van een ander
Authenticatie vertelt u wie er aanroept. Ze bepaalt niet welke velden die persoon mag lezen. De GraphQL-autorisatiegids zegt dat de uitvoering pas moet beginnen nadat authenticatie-middleware de identiteit heeft bevestigd, en dat de server nog steeds per opgevraagd veld moet beslissen of de gebruiker het mag zien.
Test met twee gewone accounts. Vraag met de sessie van account A de objecten van account B op via hun ID, en bereik ze daarna via een gerelateerd object dat beide accounts kunnen zien. Pas de namen aan op uw 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 } } } }
Wordt de eerste query geweigerd en geeft de tweede het e-mailadres van B terug, dan zit de controle op de top-level lookup en is ze nooit toegepast op het geneste type. Herhaal de test zonder enige sessie. Wordt dezelfde data ook via REST of een mobiele API aangeboden, test dan ook die toegangspunten. Ons artikel over het testen van elk ID in uw app op IDOR en BOLA behandelt dezelfde aanpak buiten GraphQL.
Autorisatie binnen een field resolver is prima voor prototypes, maar u moet haar voor elk toegangspunt dupliceren. Als de kopieën uit elkaar gaan lopen, zien gebruikers andere data afhankelijk van de API die ze gebruiken. Verplaats de logica naar de business logic-laag, zodat er één bron van waarheid is. In het voorbeeld uit de gids ontvangt postRepository.getBody de gebruiker en de post, en geeft het de body alleen terug aan de auteur. Geef een volledig gevuld (hydrated) user-object door, geen ondoorzichtig token of API-sleutel.
Wat een scanner u niet vertelt
Geautomatiseerde tools bieden breedte, maar de OWASP Web Security Testing Guide wijst erop dat ze zijn gebouwd voor applicaties in het algemeen, niet voor uw eigen code en bedrijfsregels. De ernstigste fouten zijn vaak verweven met de business logic.
Test 1 tot en met 3 zijn grotendeels te scripten. Test 5 vraagt handmatig werk, omdat iemand moet begrijpen welke tenant welk object bezit. Daarom is dit de test die u het best aan een extern team kunt overdragen. Onze pentestdienst omvat dat.
Waar u kunt beginnen
- Stuur de introspection-query en open GraphiQL op de staging-omgeving. Schakel uit wat antwoordt en test opnieuw met een foutieve query om de foutmeldingen te controleren.
- Maak een lijst van elk argument van elke query en mutation. Controleer dat elk een specifiek type of enum heeft en een allowlist-validator.
- Stuur de diep geneste query, een request met een grote paginagrootte en een te grote payload. Stel de limieten in, stuur de queries opnieuw en controleer dat elk wordt geweigerd.
- Stuur een array-batch tegen uw login- en OTP-mutations. Dwing een limiet per request af, en op herhalingen van gevoelige operaties.
- Voer de tweeaccountstest, inclusief geneste relaties, uit op elk type dat persoonlijke of bedrijfsgegevens bevat. Verplaats elke controle die u in een resolver aantreft naar de business logic-laag.
