Can I protect GraphQL requests?
Short answer
Yes; with JSON and signature checks, without a GraphQL-specific parser, query depth analysis or query cost analysis. Since a GraphQL request mostly comes with an application/json body ({"query": "…", "variables": {…}}), WAF parses the body as JSON: the query text and the variable values are scanned with JSON-scoped signatures; a query that comes with GET is scanned as a query parameter. The library has 34 rules for introspection (__schema) queries, batched and aliased queries, denial of service through deeply nested queries, unauthorized mutations, and SQL or NoSQL injection inside the variables; these rules are not in a single category but in different categories, and they are searched by name in the Signature & Rule Library. Three of the deeply nested query rules (Graphql Nested Query Dos, GraphQL Deep Nesting DoS, GraphQL fragment-cycle / entity-expansion DoS) work only in the query parameter in the URL; for a nested query in the JSON body, there is a 4-point rule. JSON Depth measures only the depth of the JSON envelope; it does not measure the nesting of the GraphQL query. On the positive side, only POST is allowed for the GraphQL endpoint, the query, operationName and variables paths are restricted with Allowed JSON Keys, and a JSON Value Length limit is set on the query field. Plain text queries that come with the application/graphql type are scanned as a raw body (RAW).
Problem: GraphQL Carries the Whole API on a Single Endpoint
GraphQL receives all operations on a single endpoint (usually /graphql); which data is requested is determined not by the path or the query parameters but by the query text in the body. Therefore, positive checks at the endpoint and method level are not enough on their own; the attacks are inside the query text and the variables (OWASP GraphQL Cheat Sheet).
WAF processes a GraphQL request as follows:
| Request format | How it is inspected |
|---|---|
POST, Content-Type: application/json | The body is parsed as JSON; the query text and every string value under variables are scanned separately with JSON-scoped signatures. |
GET, ?query=… | The query text is scanned as a query parameter value. |
POST, Content-Type: application/graphql | The body is scanned as raw content; the content type is determined from the body and checked against the Allowed Raw Body Mime Types list (What is Allowed Raw Body Mime Types used for?). |
| Subscription over WebSocket | Only the first HTTP request that opens the connection is inspected. |
Some of the GraphQL-specific rules and examples tested against the patterns in the library:
| Rule | Scope | Score | Example |
|---|---|---|---|
| GraphQL Schema Introspection | Query, JSON, form | 8 | {__schema{types{name}}} |
| GraphQL Introspection Type Query | Query, JSON, form | 8 | __type queries |
| GraphQL Dangerous Mutation Privilege Escalation | Query, JSON, form | 8 | mutation { deleteAllUsers } |
| GraphQL Batched Query · GraphQL Aliased Batch Queries | Query, JSON, form | 6 | Batched and aliased queries |
| SQL injection inside a GraphQL argument value | JSON | 8 | SQL injection in an argument value |
| GraphQL Variable NoSQL Injection | Query, JSON, form, raw body | 8 | NoSQL operator in a variable |
| GraphQL Deep Nesting DoS · GraphQL fragment-cycle / entity-expansion DoS | Query parameter only | 8 | Deep query sent with GET |
| Graphql Nested Query Dos | Query parameter only | 6 | Deep query sent with GET |
| GraphQL nested-query DoS | JSON, raw body | 4 | Deep query sent with POST |
Score distribution of the rules: 11 rules have 8 points, 19 rules 6, 3 rules 4 and 1 rule 2 points. If more than one rule matches in a request, the points are added up.
Solution
Signature scanning runs as soon as the GraphQL endpoint comes within the scope of WAF. The configuration below adds the positive checks. Prerequisite: WAF is on in the vService (How do I enable WAF protection on a vService?).
Page Type of the GraphQL Endpoint
- How it works A page type for
/graphqllimits the methods; the JSON common rule allows only the expected keys in the body, and the argument rule limits the length of the query text. - Where it is opened Left Monitor Panel → WAF-Protected vServices → vService → WAF Management → N page types → Add · rule → JSON tab
- Requirement Modify permission in the WAF area; Structure Checks and Argument Checks on in Checks; the host group in Monitor or Blocking mode
- vService type HTTP
Step-by-Step Configuration
On the WAF Management tab, the pencil icon of the Checks card is clicked; Structure Checks and Argument Checks must be checked. If a change was made, Edit is clicked.
On the WAF Management tab, the N page types row under the mode card of the host group is clicked.
Add on the toolbar is clicked (Add).
Path = /graphql is written; for Allowed Methods, Multiple Methods is selected and POST is checked. If the application also sends queries with GET, GET is also selected (Can I allow only certain HTTP methods on a page (Allowed Methods)?).
Add in the form is clicked.
The /graphql rule is clicked in the list; in the detail panel on the right, the JSON tab is selected and Add Common Rule for JSON is clicked.
The Allowed JSON Keys restriction is turned on, and query, operationName, variables and the variable paths the application uses (e.g. variables.id, variables.input, variables.input.email) are added; the container paths must also be in the list (Can I allow only specific keys in JSON; how are nested key paths written?). If there are many variables, the list must not exceed the Max. Allowed Arguments value. Block Invalid JSON Contents is checked.
Add in the form is clicked.
On the same tab, Add Argument Rule for JSON is clicked.
Argument Name = query is written; for JSON Value Length, a value slightly larger than the byte length of the application's longest legitimate query is entered with Less Than or Equal To. The length limit marks excessively large, batched or deep queries as a structural violation (Can I restrict a parameter's value by length or regex pattern (Value length / Value pattern)?).
Add in the form is clicked; there is no separate deployment step. The rule set is first tried in Monitor mode (When should I move from Learning to Monitor and from Monitor to Blocking?). In a cluster, the changes are sent to the peer device with the default auto-sync; if Manual Sync Mode is on, they are sent with Synchronize.
Verification
An introspection query is sent and the Attacks tab of the Event Logs record is checked.
Command: curl -s -o /dev/null -w "%{http_code}\n" -H "Content-Type: application/json" -d '{"query":"{__schema{types{name}}}"}' https://api.example.com/graphql
GraphQL Schema Introspection (8)A request is sent after a key that is not in the list is added to the body.
Allowed JSON keys (24)Common Mistakes
- Assuming that JSON Depth limits the depth of the GraphQL query. JSON Depth measures only the depth of the JSON envelope (
{"query": …, "variables": {…}}); the query text is a single string value in the JSON. The size of the query is limited with the JSON Value Length set on thequeryfield. - Searching for the GraphQL rules in a single category. The rules are spread across categories such as Information Disclosure, Application DoS, Data Query Injection, Access Control Bypass; they are listed by typing
GraphQLin the Search field in the Signature & Rule Library. - Using JSON Value Pattern like a deny list for the variables. The value pattern is a positive check: a violation is produced when the value does not match the pattern; it is not written in the form of "must not contain this".
- Not checking the Content-Type. The JSON checks work only when the Content-Type ends with
…/jsonor+json; a query that comes withapplication/graphqlis scanned as a raw body and does not go through the checks on the JSON tab. - Forgetting the variable paths. Every used path under
variablesmust be in the Allowed JSON Keys list; otherwise legitimate requests produce a 24-point violation.
How it is done in the documentation
