# ✅ Security Tests

Security tests are the set of tests that are run against the API to ensure that it is secure.

On this page, you will find the list of all the security tests that are run by Escape as well as their description, parameters and remediations.

Category/Name OWASP Severity
Request Forgery / SSRF A10:2021 HIGH
Request Forgery / Partial SSRF A10:2021 HIGH
Request Forgery / GET based CSRF A05:2021 HIGH
Request Forgery / POST based CSRF A05:2021 MEDIUM
Injection / XXE A03:2021 HIGH
Injection / XSS A03:2021 HIGH
Injection / Stored XSS A03:2021 HIGH
Injection / SQL A03:2021 HIGH
Injection / NoSQL A03:2021 HIGH
Injection / Directory traversal A03:2021 HIGH
Injection / Bash command A03:2021 HIGH
Information Disclosure / Source code disclosure A05:2021 HIGH
Information Disclosure / Stack traceback A05:2021 MEDIUM
Information Disclosure / Field suggestion A05:2021 MEDIUM
Information Disclosure / Debug mode A05:2021 MEDIUM
Information Disclosure / GraphQL IDE A05:2021 LOW
Information Disclosure / Introspection INFO
HTTP / SSL certificate expiration A05:2021 HIGH
HTTP / HeartBleed A06:2021 HIGH
HTTP / Unsecure HTTP A05:2021 MEDIUM
HTTP / CRLF A03:2021 MEDIUM
HTTP / Security Headers A05:2021 LOW
HTTP / Open redirection A05:2021 LOW
HTTP / CORS A05:2021 LOW
DOS / Unreachable server A05:2021 HIGH
DOS / Security timeout A05:2021 HIGH
DOS / Recursive Fragment A05:2021 MEDIUM
DOS / Directive overloading A05:2021 MEDIUM
DOS / Automatic Persisted Queries A05:2021 LOW
DOS / Circular introspection A05:2021 INFO
Access Control / Tenant isolation A01:2021 HIGH
Access Control / Secrets leaks A01:2021 HIGH
Access Control / Private fields A01:2021 HIGH
Access Control / Private data A01:2021 HIGH
Access Control / Public Unsafe Route A01:2021 MEDIUM
Access Control / IDOR A01:2021 MEDIUM
Introspection / Typing inconsistency A03:2021 MEDIUM
Introspection / Zombie objects LOW
Introspection / Unhandled operation INFO
Introspection / Undefined objects A06:2021 INFO
Introspection / Typing inconsistency (interceptor) INFO
Introspection / Imputable JSON scalars A03:2021 INFO
Complexity / Pagination limit A01:2021 MEDIUM
Complexity / Large JSON A05:2021 MEDIUM
Complexity / Field limit A03:2021 MEDIUM
Complexity / Depth limit A05:2021 MEDIUM
Complexity / Width limit A03:2021 LOW
Complexity / Cyclic query A05:2021 LOW
Complexity / Character limit (interceptor) A05:2021 LOW
Complexity / Batch Limit A05:2021 LOW
Complexity / Alias limit A05:2021 LOW

# Request Forgery


SSRF

# SSRF

Identifier Severity
request_forgery/ssrf HIGH

SSRF (Server-Side Request Forgery) vulnerability occurs when the attacker can send any request as if the server was sending it.


Remediations

Generic

Basic blacklisting and regular expressions are a bad approach to mitigating SSRF.

The correct ways to prevent SSRF are:

  • Whitelisting and DNS resolution: whitelist the hostname (DNS name) or IP address that your application needs to access. (Best method to prevent SSRF))
  • Response handling: To prevent response data from leaking to the attacker, you must ensure that the received response is as expected. Under no circumstances should the raw response body from the request sent by the server be delivered to the client.
  • Disabling unused URL schemas: if your application only uses HTTP or HTTPS to make requests, allow only these URL schemas. Once unused URL schemas are disabled, the attacker will be unable to exploit the web application to make requests using potentially dangerous schemas such as file:///, dict://, ftp://, and gopher://.
  • Authentication on internal services.

Reference:

https://owasp.org/www-community/attacks/Server_Side_Request_Forgery (opens new window)


Partial SSRF

# Partial SSRF

Identifier Severity
request_forgery/partial_ssrf HIGH

Partial Server-Side Request Forgery occurs when the attacker can manipulate a request made by the server.


Remediations

Generic

Basic blacklisting and regular expressions are a bad approach to mitigating SSRF.

The correct ways to prevent SSRF are:

  • Whitelisting and DNS resolution: whitelist the hostname (DNS name) or IP address that your application needs to access. (Best method to prevent SSRF))
  • Response handling: To prevent response data from leaking to the attacker, you must ensure that the received response is as expected. Under no circumstances should the raw response body from the request sent by the server be delivered to the client.
  • Disabling unused URL schemas: if your application only uses HTTP or HTTPS to make requests, allow only these URL schemas. Once unused URL schemas are disabled, the attacker will be unable to exploit the web application to make requests using potentially dangerous schemas such as file:///, dict://, ftp://, and gopher://.
  • Authentication on internal services.

Reference:

https://0xn3va.gitbook.io/cheat-sheets/web-application/graphql-vulnerabilities#abuse-graphql-as-an-api-gateway (opens new window)


Configuration

{
    "checks": {
        "request_forgery/partial_ssrf": {  
            "options":{ 
                "skip_objects": [], 
            }, 
            "skip": False,
        }
    }
}

Options:

skip_objects : List of object that are to be skipped by the security test.


GET based CSRF

# GET based CSRF

Identifier Severity
request_forgery/get_based_csrf HIGH

CSRF (Cross-Site Request Forgery) occurs when an external website has the ability to make API calls impersonating a user by visiting the website while being authenticated to your API.

Allowing API calls through GET requests can lead to CSRF attacks because cookies are added automatically to GET requests made by the browser.


Remediations

Generic

Forbid API calls through GET requests to prevent CSRF attacks.

Apollo

Pass csrfPrevention: true to new ApolloServer().

Check out the the CSRF prevention documentation (opens new window) for the best CSRF prevention techniques.

Awsappsync

Make sure that your API does not use Cookie-based authentication.

There are many other ways to authenticate a user with AppSync:

  • API Keys
  • Amazon Cognito User Pools
  • OpenID Connect
  • AWS Identity and Access Management (IAM)
  • AWS Lamba custom authentication

AppSync: Authorization and Authentication (opens new window)

Whichever method you use, verify that authentication occurs through headers because authentication headers are not automatically added by the targeted user browser (while Cookies are).

To avoid any risk, you can block every GET request and allow only POST requests, which are immune to this attack, but it comes at a cost. (see AWS pricing for the corresponding services)

  • Block GET requests with AWS API Gateway (prefered method):

Put your AppSync API behind an API Gateway.

API Gateway Documentation (opens new window)

You can then configure the API Gateway to act as an HTTP Proxy to your AppSync endpoint and configure it to allow only POST requests.

  • Block GET requests with AWS Web Application Firewall:

Add the following Web ACL rule in the AWS WAF Console to block every GET request to the API:

{
  "Name": "block-get",
  "Priority": 0,
  "Statement": {
    "ByteMatchStatement": {
      "SearchString": "GET",
      "FieldToMatch": {
        "Method": {}
      },
      "TextTransformations": [
        {
          "Priority": 0,
          "Type": "NONE"
        }
      ],
      "PositionalConstraint": "EXACTLY"
    }
  },
  "Action": {
    "Block": {}
  },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "add-headers"
  }
}

You can also configure it manually by using the following field values :

If:

  • Field to match = HTTP method
  • Positional constraint = Exactly matches string
  • Search string = GET

Then:

  • Block

See: AppSync API with AWS WAF (opens new window).

To learn more on AWS WAF, see: AWS WAF (opens new window).

Reference:

https://blog.doyensec.com/2021/05/20/graphql-csrf.html (opens new window)


POST based CSRF

# POST based CSRF

Identifier Severity
request_forgery/post_based_csrf MEDIUM

The canonical content-type for GraphQL queries is JSON (application/json). Other content-types should be rejected as they facilitate CSRF attacks (e.g., via form submission).


Remediations

Generic

Only allow requests with the Content-Type header set to application/json.

Apollo

Only allow requests with the Content-Type header set to application/json.

With Express.js, the enforce-content-type middleware can be used to block unwanted content types.

 const enforceContentType = require('enforce-content-type')

 app.use(enforceContentType({
     type: 'application/json'
 }))

See: enforce-content-type Github Repo (opens new window).

Reference:

https://blog.doyensec.com/2021/05/20/graphql-csrf.html (opens new window)

# Injection


XXE

# XXE

Identifier Severity
injection/xxe HIGH

External entities is an element of XML documents, and attackers may replace the entity value with malicious data, alternate referrals, or compromise the security of the data the server/XML application has access to. Attackers may also use External Entities to have the web services download malicious code or content on the server for use in secondary or follow up attacks.


Remediations

Generic

To safely prevent XXE attacks, always disable DTDs (External Entities) completely. Depending on the parser, the method should be similar to the following:

factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);

Disabling DTDs also helps secure the parser against Denial of Services attacks such as Billion Laughs.

If it is not possible to disable DTDs completely, disable external entities and external document type declarations in the way that's specific to each parser.

Reference:

http://projects.webappsec.org/XML-External-Entities (opens new window)


XSS

# XSS

Identifier Severity
injection/xss HIGH

XSS (Cross-site scripting) is an attack where malicious code (eg. JavaScript) is injected into the application and executed.


Remediations

Generic

Preventing cross-site scripting is trivial in some cases but can be much harder depending on the complexity of the application and the ways it handles user-controllable data.

To effectively prevent XSS vulnerabilities, use a combination of the following measures:

  • Filter user input on arrival as strictly as possible, based on what you expect as legitimate input.
  • Encode user-controllable data as soons as it is outputted in HTTP reponses to prevent it from being interpreted as active content (ie. code). Depending on the output context, this might require applying combinations of HTML, URL, JavaScript, and CSS encoding.
  • Use the right headers to ensure that browsers interpret the responses the way you intended. For example, to prevent XSS in HTTP responses that are not supposed to contain any HTML or JavaScript, you can use the Content-Type and X-Content-Type-Options headers.
  • Use Content Security Policy (CSP) to reduce the severity of any XSS vulnerability that still occurs.

Reference:

https://portswigger.net/web-security/cross-site-scripting (opens new window)


Configuration

{
    "checks": {
        "injection/xss": {  
            "options":{ 
                "skip_objects": [], 
            }, 
            "skip": False,
        }
    }
}

Options:

skip_objects : List of object that are to be skipped by the security test.


Stored XSS

# Stored XSS

Identifier Severity
injection/stored_xss HIGH

Stored code injections are attacks where malicious code (such as JavaScript) is injected into the application and stored in the database to be later executed. Stored XSS is one of those attacks. Stored XSS is when an XSS vulnerability originates from the database because of malicious code previously inserted in it.


Remediations

Generic

Preventing cross-site scripting is trivial in some cases but can be much harder depending on the complexity of the application and the ways it handles user-controllable data.

To effectively prevent XSS vulnerabilities, use a combination of the following measures:

  • Filter user input on arrival as strictly as possible, based on what you expect as legitimate input.
  • Encode user-controllable data as soons as it is outputted in HTTP reponses to prevent it from being interpreted as active content (ie. code). Depending on the output context, this might require applying combinations of HTML, URL, JavaScript, and CSS encoding.
  • Use the right headers to ensure that browsers interpret the responses the way you intended. For example, to prevent XSS in HTTP responses that are not supposed to contain any HTML or JavaScript, you can use the Content-Type and X-Content-Type-Options headers.
  • Use Content Security Policy (CSP) to reduce the severity of any XSS vulnerability that still occurs.

Reference:

https://portswigger.net/web-security/cross-site-scripting (opens new window)


Configuration

{
    "checks": {
        "injection/stored_xss": {  
            "options":{ 
                "skip_objects": [], 
            }, 
            "skip": False,
        }
    }
}

Options:

skip_objects : List of object that are to be skipped by the security test.


SQL

# SQL

Identifier Severity
injection/sql HIGH

A SQL injection vulnerability occurs when users can insert (or “inject”) malicious SQL code in a legit SQL query that is built from user-submitted input. A successful SQL injection exploit can read sensitive data from the database, modify database data, execute administration operations on the database (such as shutting down the DBMS), recover the content of a given file from the DBMS file system and in some cases issue commands to the operating system.


Remediations

Generic

Primary defenses:

  • Use of Prepared Statements (with parameterized queries). This prevention techniques is the most effective one as it will completely shutdown any SQL injection attacks. Keep in mind that prepared statements must be used everywhere, even if no user inputted data is found in the query.
  • Use of Stored Procedures.
  • Allow-list Input Validation. Usage whitelist is recommended to prevent SQL injection attacks as whitelisting is more effective then black listing.
  • Escaping all user supplied input.

Additional defenses:

  • Enforcing Least Privilege.
  • Performing Allow-list Input Validation as a secondary line of defense.

Reference:

https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html (opens new window)


Configuration

{
    "checks": {
        "injection/sql": {  
            "options":{ 
                "skip_objects": [], 
            }, 
            "skip": False,
        }
    }
}

Options:

skip_objects : List of object that are to be skipped by the security test.


NoSQL

# NoSQL

Identifier Severity
injection/nosql HIGH

A NoSQL injection vulnerability occurs when users can insert (or “inject”) malicious NoSQL code in a legit SQL query that is built from user-submitted input. A successful NoSQL injection exploit can read sensitive data from the database, modify database data, execute administration operations on the database (such as shutting down the DBMS), recover the content of a given file from the DBMS file system and in some cases issue commands to the operating system.


Remediations

Generic

Primary defenses:

  • Use a sanitization library.
  • Cast the inputs to the expected type (eg: The username and password are strings so cast the variables to a string).
  • Never use where, mapReduce, or group operators with user input: they allow the attacker to inject JavaScript and are therefore much more dangerous than others. For extra safety, set javascriptEnabled to false in mongod.conf (if using mongoDB).
  • Enforce Least Privilege.

Reference:

https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/05.6-Testing_for_NoSQL_Injection (opens new window)


Configuration

{
    "checks": {
        "injection/nosql": {  
            "options":{ 
                "skip_objects": [], 
            }, 
            "skip": False,
        }
    }
}

Options:

skip_objects : List of object that are to be skipped by the security test.


Directory traversal

# Directory traversal

Identifier Severity
injection/directory_traversal HIGH

Directory traversal occurs when a server allows an attacker to read a file or directories outside of the normal web server directory, and local file inclusion gives the attacker the ability to include an arbitrary local file (from the web server) in the web server's response.

Example: getProfilePicture(name: '../../../etc/password') returns the server's /etc/password file.


Remediations

Generic

There are multiple ways to prevent directory traversal attacks:

  • Avoid using parameters entered directly by the user.
  • Set up a file/folder name whitelist system: allow only certain folders and/or types of extensions, thus excluding all others.
  • Compartmentalize your data and implement middlewares. These can take the form of an interface to the (potentially external) file system on which the data users may request is stored. If the attached data storage is dedicated to this purpose only and does not contain sensitive data, the risk is limited, even if a user manages to bypass the limitations that this middleware can put in place.
  • Restrict access of the GraphQL worker to what is strictly necessary. By restricting as much as possible the files and folders to which the GraphQL worker has access, you reduce the range of files potentially exposed by an attack.
  • Take advantage of virtualization. With virtualization, it is possible to have several virtual machines completely isolated from each other. The GraphQL worker can therefore be isolated on its own virtual machine, allowing it access only to the elements absolutely necessary for its proper execution.

Reference:

https://escape.tech/blog/file-inclusion-directory-traversal-graphql/ (opens new window)


Configuration

{
    "checks": {
        "injection/directory_traversal": {  
            "options":{ 
                "skip_objects": [], 
            }, 
            "skip": False,
        }
    }
}

Options:

skip_objects : List of object that are to be skipped by the security test.


Bash command

# Bash command

Identifier Severity
injection/bash_command HIGH

A system command was successfully executed on your application's system. Command injections happen when a user manages to successfully execute arbitrary commands on the host's operating system by abusing a vulnerable endpoint.


Remediations

Generic

To prevent command injection attacks: -Never use user-submitted input in shell commands. -If supported by your language, add semgrep to your development process to ensure detection of potentially vulnerable system shell calls. -Use proper input validation techniques to detect and prevent command injection. Keep in mind the input validation should be implemented in the backend as it will be easily bypassed if done in the frontend.

Reference:

https://owasp.org/www-community/attacks/Command_Injection (opens new window)


Configuration

{
    "checks": {
        "injection/bash_command": {  
            "options":{ 
                "skip_objects": [], 
            }, 
            "skip": False,
        }
    }
}

Options:

skip_objects : List of objects that are to be skipped by the security test.

# Information Disclosure


Source code disclosure

# Source code disclosure

Identifier Severity
information_disclosure/source_code_disclosure HIGH

The source code for the current page was disclosed by the web server.


Remediations

Generic

Ensure that .git, .svn, .htaccess metadata files are not deployed to the web server or application server, or cannot be accessed.

Reference:

https://www.zaproxy.org/docs/alerts/41/ (opens new window)


Configuration

{
    "checks": {
        "information_disclosure/source_code_disclosure": {  
            "options":{ 
                "size_threshold": 200, 
                "diff_threshold": 0.1, 
                "small_response_diff_threshold": 0.4, 
            }, 
            "skip": False,
        }
    }
}

Options:

size_threshold : The threshold size indicating whether a response is small or not.

diff_threshold : The percentage by which 2 responses can differ and still be considered identical.

small_response_diff_threshold : The percentage by which 2 small responses can differ and still be considered identical.


Stack traceback

# Stack traceback

Identifier Severity
information_disclosure/stack_traceback MEDIUM

Details about database-level or code-level errors have been found in the response. This may cause information leaks, allowing attackers to identify the exact database or dependency you are using, and can therefore lead to highly targeted attacks against your application.

Example: sending getUser(id: null) returns { success: false, message: "SQL Error: Postgres 3.6 has encountered an error : Invalid ID."}.


Remediations

Generic

Always avoid database or code error stack traces to be returned directly to the client.

Apollo

Never allow database or code error stack traces to be returned directly to the client.

This remediation is supported by our GraphQL Armor (opens new window) middleware.

When using Apollo you can also disable exception tracebacks in the response by either setting NODE_ENV to production or enforcing it:

const server = new ApolloServer({
  ...,
  debug: false
)}

To learn more, you can read Apollo's documentation on omitting stack traces (opens new window).

*Note: if you are working in a development or staging environment, error disclosure might happen on purpose. In that case, check if this is also the case in production by:

  • Using our cURL copy button to find the request that generated the stack trace.
  • Changing URL and authentication tokens to match your production environment.

If the issue doesn't happen in production, you can safely ignore it.*

Graphqlyoga

Enable error masking (opens new window) to hide stack traces in production.

Since it is enabled by default, you are probably missing the variable NODE_ENV=production in your production environment.

Hasura

Set the HASURA_GRAPHQL_DEV_MODE env variable to false in all user-facing environments.

Source: https://hasura.io/docs/latest/graphql/core/deployment/graphql-engine-flags/reference/

Note: if you are working in a development or staging environment, error disclosure might happen on purpose. In that case, make sure your production environment has HASURA_GRAPHQL_DEV_MODE set to false

Reference:

https://infosecwriteups.com/exploiting-error-based-sql-injections-bypassing-restrictions-ed099623cd94 (opens new window)


Field suggestion

# Field suggestion

Identifier Severity
information_disclosure/field_suggestion MEDIUM

If introspection is disabled on your target, Field Suggestion still allow users infer the entire schema, with a tool like Clairvoyance (opens new window).

If you query a field with a typo, GraphQL will attempt to suggest fields close to what was requested. Example:

  Error: Cannot query field "createSesion" on type "RootMutation". Did you mean "createSession", "createUser", "createFile", or "createImage"?

Remediations

Generic

Disable Field Suggestion in production.

Apollo

Block field suggestion is supported by GraphQL Armor (opens new window) middleware.

Graphqlgo

graphql-go/graphql does not allow to disable field suggestion as of now.

However, you can filter field suggestion by discarding answers containing "Did you mean" with this middleware :

type FilterResponseWriter struct {
  writer    http.ResponseWriter
  blacklist []string
  errorPtr  *bool
}

func (w FilterResponseWriter) Header() http.Header {
  return w.writer.Header()
}

func (w FilterResponseWriter) Write(data []byte) (int, error) {
  if *w.errorPtr {
    return 0, errors.New("write error")
  }
  for _, s := range w.blacklist {
    if bytes.Contains(data, []byte(s)) {
      *w.errorPtr = true
      return 0, errors.New("field not found")
    }
  }
  return w.writer.Write(data)
}

func (w FilterResponseWriter) WriteHeader(statusCode int) {
  w.writer.WriteHeader(statusCode)
}

func blockFieldSuggestion(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    var error bool
    newWriter := &FilterResponseWriter{writer: w, blacklist: []string{"Did you mean \\\""}, errorPtr: &error}
    next.ServeHTTP(newWriter, r)
    if error {
      w.Write([]byte("{\"errors\":[{\"message\":\"Field not found.\"}],\"data\":null}"))
    }
  })
}

Then you apply the middleware to your endpoint :

func main(){
  ...
  h := handler.New(&handler.Config{
    Schema:   &schema
  })
  http.Handle("/graphql", blockFieldSuggestion(h))
}
Graphqlyoga

Block field suggestion is supported by GraphQL Armor (opens new window) middleware.

Or, you can use the standalone envelop plugin (opens new window).

Reference:

https://blog.yeswehack.com/yeswerhackers/how-exploit-graphql-endpoint-bug-bounty/ (opens new window)


Debug mode

# Debug mode

Identifier Severity
information_disclosure/debug_mode MEDIUM

When Debug mode is left turned on by developers, it allows attackers to gather precious information from excessive error reporting messages such as entire stack traces or tracebacks.


Remediations

Generic

Disabled Debug mode.

Reference:

https://www.infosecmatter.com/bug-bounty-tips-8-oct-14/#4-graphql-notes-for-beginners (opens new window)


GraphQL IDE

# GraphQL IDE

Identifier Severity
information_disclosure/graphql_ide LOW

A GraphQL IDE provides an interface for users to interact with the Endpoint, but an IDE can also leave room for potential vulnerabilities.


Remediations

Generic

Disable GraphQL IDE, or restrict it. Head over to your specific engine documentation to know how to do it.

Apollo

GraphQL Playground is deprecated and disabled by default since Apollo v3. If you installed it voluntarily with the corresponding plugin, you should consider disabling it to improve security.

If you still use Apollo v2, you can disable GraphQL Playground by either:

  • Setting the environment variable NODE_ENV to production
  • Explicitly disabling it:
    const server = new ApolloServer({
      // ...
      playground: false,
    });
    

Source:


Introspection

# Introspection

Identifier Severity
information_disclosure/introspection INFO

GraphQL introspection enables you to query a GraphQL server for information about the underlying schema, including data like types, fields, queries, mutations, and even the field-level descriptions. It discloses sensitive information that potentially allows an attacker to design malicious operations.


Remediations

Generic

Introspection should primarily be used as a discovery and diagnostic tool when we're in the development phase of building out GraphQL APIs. While it's still possible for bad actors to learn how to write malicious queries by reverse engineering your GraphQL API through a lot of trial and error, disabling introspection is a form of security by obscurity.

Apollo

To disable Introspection, either set NODE_ENV to production or enforce it :

const server = new ApolloServer({
  typeDefs,
  resolvers,
  introspection: false
});

Source: https://escape.tech/blog/9-graphql-security-best-practices/ (opens new window)

Ariadne

When defining the ariadne.asgi.GraphQL app make sure to add the flag introspection=False

Awsappsync

Add ACL rule to prevent GraphQL __schema introspection queries to the API. This is achieved by blocking any HTTP body that includes the string "__schema". This would be entered into the Rule JSON editor when creating a web ACL in the AWS WAF Console.

{
    "Name": "Block-Introspection",
    "Priority": 5,
    "Action": {
        "Block": {}
    },
    "VisibilityConfig": {
        "SampledRequestsEnabled": true,
        "CloudWatchMetricsEnabled": true,
        "MetricName": "Block-Introspection"
    },
    "Statement": {
        "ByteMatchStatement": {
            "FieldToMatch": {
                "Body": {}
            },
            "PositionalConstraint": "CONTAINS",
            "SearchString": "__schema",
            "TextTransformations": [
                {
                    "Type": "NONE",
                    "Priority": 0
                }
            ]
        }
    }
}

Don't forget to associate the previously created ACL rule with your AppSync API.

For more information refer to :

AWS AppSync - Developer Guide (opens new window)

Integrate an AppSync API with AWS WAF (opens new window)

AWS Web Application Firewall (opens new window)

Graphene

When using Graphene, here is how you would disable introspection for your schema.

from graphql import validate, parse
from graphene import ObjectType, Schema, String
from graphene.validation import DisableIntrospection


  class MyQuery(ObjectType):
  name = String(required=True)


  schema = Schema(query=MyQuery)

  # introspection queries will not be executed.

  validation_errors = validate(
    schema=schema.graphql_schema,
    document_ast=parse('THE QUERY'),
    rules=(
      DisableIntrospection,
    )
  )

Source: docs.graphene-python.org (opens new window)

Graphqlgo

graphql-go/graphql does not allow you to disable Introspection.

However, you can disable it with a custom middleware filtering the keyword __schema:


func blockIntrospection(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    bodyBytes, _ := ioutil.ReadAll(r.Body)
    r.Body.Close()
    r.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
    body_lower := bytes.ToLower(bodyBytes)
    subslice := "__schema"
    if bytes.Contains(body_lower, []byte(subslice)) {
      no_introspection := "{\"errors\": [{\"message\": \"Introspection is disabled.\"}],\"data\": null}"
      w.Write([]byte(no_introspection))
    } else {
      next.ServeHTTP(w, r)
    }
  })
}

...

func main(){
  ...
  h := handler.New(...) // GraphQL handler

  http.Handle("/graphql", blockIntrospection(h))

}
Graphqljava
GraphQLSchema schema = GraphQLSchema.newSchema()
.query(StarWarsSchema.queryType)
.fieldVisibility( NoIntrospectionGraphqlFieldVisibility.NO_INTROSPECTION_FIELD_VISIBILITY )
.build();

Source: https://www.graphql-java.com/documentation/v11/execution/

Graphqlphp
use GraphQL\GraphQL;
use GraphQL\Validator\Rules\DisableIntrospection;
use GraphQL\Validator\DocumentValidator;
DocumentValidator::addRule(new DisableIntrospection());<code>
</code>

Source: https://webonyx.github.io/graphql-php/security/#disabling-introspection

Hasura

Hasura allows you to control who can run an introspection query.

To do so:

  • Go to Project Console > Security Settings > Schema Introspection
  • Select a role (e.g., guest)
  • Check "Disabled"

See the official guide (opens new window) for more information.

Ruby
class MySchema < GraphQL::Schema
disable_introspection_entry_points if Rails.env.production?
end

Source: https://github.com/rmosolgo/graphql-ruby/blob/master/guides/schema/introspection.md#disabling-introspection

Reference:

https://www.apollographql.com/blog/graphql/security/why-you-should-disable-graphql-introspection-in-production/ (opens new window)

# HTTP


SSL certificate expiration

# SSL certificate expiration

Identifier Severity
http/ssl_certificate_expiration HIGH

The SSL certificate found on the website is no longer valid. This is most probably due to the fact that the SSL certificate is expired.


Remediations

Generic

Purchase a new SSL certificate.

Reference:

https://www.thesslstore.com/blog/what-happens-when-your-ssl-certificate-expires/ (opens new window)


HeartBleed

# HeartBleed

Identifier Severity
http/heartbleed HIGH

The TLS implementation in OpenSSL 1.0.1 before 1.0.1g does not properly handle Heartbeat Extension packets, which allows remote attackers to obtain sensitive information from process memory via crafted packets that trigger a buffer over-read, potentially disclosing sensitive information.


Remediations

Generic

To effectively prevent HeartBleed attacks:

  • Update to OpenSSL 1.0.1g or later.
  • Re-issue HTTPS certificates.
  • Change asymmetric private keys and shared secret keys, since these may have been compromised with no evidence of corruption in the server log files.

Reference:

https://www.cvedetails.com/cve-details.php?t=1&cve_id=CVE-2014-0160 (opens new window)


Unsecure HTTP

# Unsecure HTTP

Identifier Severity
http/unsecure_http MEDIUM

This security best practice should be enforced by your organization, at least for your API routes.

Here are 4 risk examples when allowing GraphQL communication using unsecure HTTP:

  • Man in the Middle attacks: hackers can intercept and alter data from a legitimate request.
  • Misuse of data: confidential information can be accessed by hackers.
  • Downranking of websites: your website can be considered insecure by search engines and rated as not trustworthy.
  • Loss of customers' trust: without a secure HTTPS padlock displayed on your website, users may rightfully consider it to be unsafe.

Using HSTS (opens new window) is not a solution to this problem, as it won't protect against MITM attacks and regular "public wifi sniffing" until connection has been upgraded to HTTPS. As recommended by Google (opens new window), make sure that no cookies are being sent through HSTS, which is likely the case if you are using a GraphQL API over HSTS.


Remediations

Generic

Enforce using HTTPS (using an SSL certificate) in order to protect your users' connections. In most cases, this must be done at ingress/(reverse-)proxy level.

If you are using Let's Encrypt certificates, make sure to authorize HTTPS to HTTPS redirections on the path /.well-known/acme-challenge/ to avoid any issues.

Reference:

https://developers.google.com/search/docs/advanced/security/https (opens new window)


CRLF

# CRLF

Identifier Severity
http/crlf MEDIUM

CRLF occurs when an attacker can abuse the carriage return character (\r) and a newline character (\n) in an HTTP request in order to inject new headers or a new body for the HTTP request. This attack is a very dangerous attack as it can give the attacker the ability to create whatever request he wants.


Remediations

Generic

The only way to prevent CRLF attacks is to carefully sanitize every message that is sent by the client.

Reference:

http://www.watchfire.com/resources/HTTPResponseSplitting.pdf (opens new window)


Security Headers

# Security Headers

Identifier Severity
http/security_headers LOW
  • Cache-Control:
  • The HTTP 'Cache-Control' header is used to specify directives for caching mechanisms.
  • The server did not return (or returned an invalid) 'Cache-Control' header, which means pages containing sensitive information could be stored client-side and then be exposed to unauthorized persons.
  • Content-Type:

    • The Content-Type header was either missing or empty.
  • X-Content-Type-Options:

    • The Anti-MIME-Sniffing header X-Content-Type-Options was not set to ‘nosniff’.
    • This allows older versions of Internet Explorer and Chrome to perform MIME-sniffing on the response body, potentially causing the response body to be interpreted and displayed as a content type other than the declared content type. Current (early 2014) and legacy versions of Firefox will use the declared content type (if one is set), rather than performing MIME-sniffing.
  • Strict-Transport-Security:

    • HTTP Strict Transport Security (HSTS) is a web security policy mechanism whereby a web server declares that complying user agents (such as a web browser) are to interact with it using only secure HTTPS connections (i.e. HTTP layered over TLS/SSL).
    • HSTS is an IETF standard track protocol specified in RFC 6797.
  • CookiesSecure:

    • A cookie has been set without the secure flag, which means that the cookie can be accessed via unencrypted connections.
  • CookiesHttpOnly:

    • A cookie has been set without the HttpOnly flag, which means that JavaScript code can access the cookie.
    • If a malicious script runs on this page, then the cookie will be accessible and can be transmitted to another hacker-controlled site. If this is a session cookie, then session hijacking may be possible.
  • VersionDisclosure:

    • The web/application server is leaking server version information via one or more HTTP response headers.
    • Access to such information may facilitate attackers identifying other frameworks/components your web application is reliant upon, and the vulnerabilities of such components may be subject to the leaked information.

Remediations

Generic
  • Cache-Control:

    • Whenever possible, ensure the cache-control HTTP header is set with no-cache, no-store, must-revalidate, and that the pragma HTTP header is set with no-cache.
  • Content-Type:

    • Ensure each page sets the specific and appropriate content-type value for the delivered content.
  • X-Content-Type-Options:

    • Ensure that the application/web server sets the Content-Type header appropriately and that it sets the X-Content-Type-Options header to 'nosniff' for all web pages.
    • If possible, ensure that the end user uses a standards-compliant and modern web browser that does not perform MIME-sniffing at all or that can be directed by the web application/web server to not perform MIME-sniffing.
  • Strict-Transport-Security:

    • Ensure that your web server, application server, load balancer, etc., are configured to enforce Strict-Transport-Security.
  • CookiesSecure:

    • Whenever a cookie contains sensitive information or is a session token, it should always be passed using an encrypted channel.
    • Ensure that the secure flag is set for cookies containing such sensitive information
  • CookiesHttpOnly:

    • Ensure that the HttpOnly flag is set for all cookies.
  • VersionDisclosure:

    • Remove headers disclosing server-side softwares version.
Apollo

When using Apollo with Express.js, helmet (opens new window) can take care of the security headers.

const helmet = require("helmet");
...
app.use(helmet);
Awsappsync
  • Add security headers with the API Gateway

Put your AppSync API behind an API Gateway and configure it to act as a proxy to your AppSync endpoint (e.g., using the HTTP Proxy feature).

API Gateway Documentation (opens new window)

Then you can manually add headers to each resource. (There is only one resource if your API Gateway is only used to proxy a single AppSync endpoint)

Here is an example of security headers you can add :

Cache-Control: no-store
Content-Security-Policy: default-src 'self'
Strict-Transport-Security: max-age=63072000
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
  • Add security headers using only AWS AppSync

AWS AppSync currently does not allow to append custom headers to every response.

However, custom response headers can be configured individually for every resolver by using response mapping templates.

To do this, go to:

  • AppSync > {Your App} > Schema

For every attached resolver :

  • Go to the resolver configuration
  • In the "Configure the response mapping template" section, add this :
$util.http.addResponseHeader("Cache-Control", "no-store")
$util.http.addResponseHeader("Content-Security-Policy", "default-src 'self'")
$util.http.addResponseHeader("Strict-Transport-Security", "max-age=63072000")
$util.http.addResponseHeader("X-Content-Type-Options", "nosniff")
$util.http.addResponseHeader("X-Frame-Options", "SAMEORIGIN")
$util.http.addResponseHeader("X-XSS-Protection", "1; mode=block")

You can safely ignore this warning if you did this for every single resolver.

However, it may still appear here as GraphQL requests like query { __typename } are not associated with a resolver; therefore, you cannot add custom response headers. (Which doesn't matter as such requests cannot leak data as no actual field is queried)

AWS AppSync adds support for custom response headers (opens new window)

HTTP helpers in $util.http (opens new window)

Resolver Mapping Template Programming Guide (opens new window)

Graphene

To add Security Headers to Django, follow this guide :

How to Score A+ for Security Headers on Your Django Website (opens new window)

For Flask, use Google's flask-talisman (opens new window)

Graphqlgo

You can use a HTTP middleware to add security headers.

For instance, with srikrsna/security-headers (opens new window):

import (
  secure "github.com/srikrsna/security-headers"
)

h := handler.New(&handler.Config{
  Schema:   &schema,
  ...
})

s := &secure.Secure{
  STSIncludeSubdomains: true,
  STSPreload:           true,
  STSMaxAgeSeconds:     90,

  FrameOption: secure.FrameAllowFrom,
  FrameOrigin: "https://example.com/",

  ContentTypeNoSniff: true,

  XSSFilterBlock: true,

  HPKPPins: []string{
  "HBkhsug765gdKHhvdj6jdb7jJh/j+soZS7sWs=",
  "hjshHSHU68hbdkHhvdkgksgsg+jd/jHJ68HBH=",
  },
  HPKPMaxAge:            5184000,
  HPKPReportURI:         "https://www.example.org/hpkp-report",
  HPKPIncludeSubdomains: true,

  ExpectCTMaxAge:    5184000,
  ExpectCTEnforce:   true,
  ExpectCTReportUri: "https://www.example.org/ct-report",

  ReferrerPolicy: secure.ReferrerStrictOriginWhenCrossOrigin,
}
http.Handle("/graphql", s.Middleware()(h))

http.ListenAndServe(":8082", nil)

Reference:


Open redirection

# Open redirection

Identifier Severity
http/open_redirection LOW

Open redirects are part of the OWASP 2010 Top Ten vulnerabilities and occur when an application allows user-supplied input (e.g. http://nottrusted.com) to control an offsite redirect. For example an attacker could supply a user with the following link http://example.com/example.php?url=http://malicious.example.com.


Remediations

Generic

This check looks at user-supplied input in query string parameters and POST data to identify where open redirects might be possible. It is generally an accurate way to find where 301 or 302 redirects could be exploited by spammers or phishing attacks. To avoid the open redirect vulnerability, parameters of the application script/program must be validated before sending 302 HTTP code (redirect) to the client browser. Implement safe redirect functionality that only redirects to relative URL's, or a list of trusted domains

Reference:

https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html (opens new window)


CORS

# CORS

Identifier Severity
http/cors LOW

Attackers can exploit CORS (Cross-Origin Resource Sharing) misconfigurations on the web server to perform CSRF (Cross-Site Request Forgery) attacks and send unauthorized commands from an authenticated user session.


Remediations

Generic

Configure the 'Access-Control-Allow-Origin' HTTP header to a more restrictive set of domains, or remove all CORS headers entirely, to allow the web browser to enforce the Same Origin Policy (SOP) in a more restrictive manner.

If your API is public and used in websites you don't control yourself, you want to allow any request origin and you can safely ignore this alert.

See: enable-cors.org (opens new window).

Apollo

Configure the 'Access-Control-Allow-Origin' HTTP header to a more restrictive set of domains, or remove all CORS headers entirely, to allow the web browser to enforce the Same Origin Policy (SOP) in a more restrictive manner.

For instance, with apollo-server-express, you can restrain request origin to only a few whitelisted domains:

await server.start();

const corsOptions = {
  origin: ["https://www.your-app.example", "https://studio.apollographql.com"]
};

server.applyMiddleware({
  app,
  cors: corsOptions,
  path: "/graphql",
});

Source: https://www.apollographql.com/docs/apollo-server/security/cors/ (opens new window).

If your API is public and used in websites you don't control yourself, you want to allow any request origin and you can safely ignore this alert.

Awsappsync

Add CORS headers with the API Gateway.

Put your AppSync API behind an API Gateway and configure it to act as a proxy to your AppSync endpoint (e.g., using the HTTP Proxy feature).

To learn how to do so, see AWS's API Gateway documentation (opens new window).

You can then manually enable CORS for each resource (only for one if you created the gateway for a single AppSync endpoint):

API Gateway console > {your api gateway} > Resources > {your created resource} > Actions : Enable CORS

Reference:

https://portswigger.net/web-security/cors (opens new window)

# DOS


Unreachable server

# Unreachable server

Identifier Severity
dos/unreachable_server HIGH

The server went offline for too long. This can be caused by a server crash, a network issue, or a misconfiguration.

Often, this is a sign of a bigger issue.


Remediations

Generic

We recommend you to investigate two possible causes:

  • Unhandled exceptions in your code: Verify that your code is handling exceptions properly.
  • Uncontrolled resource consumption: Verify what resolver has been used to leverage the data source.

Reference:

https://medium.com/workflowgen/graphql-query-timeout-and-complexity-management-fab4d7315d8d (opens new window)


Security timeout

# Security timeout

Identifier Severity
dos/security_timeout HIGH

Requests that take a long time to process can be used by attackers to create Denial-of-Service (DoS) situations.

This security test is based on an arbitrary timeout threshold that might not match your application's requirements. To learn how to change it, head over to the configuration section below.

Example: Querying getAllUsers(){ contacts { contacts }} returns a response after 15s.


Remediations

Generic

Implement a server timeout. For example, a server configured with a 5 seconds timeout would stop the execution of any query that takes over 5 seconds.

Pros: -Simple to implement. -Most security strategies use a timeout as a final layer of protection.

Cons: -Damage can already have been done before the timeout kicks in. -Can trigger other issues. Stoping connection after a certain time may result in strange behaviors and corrupt data.

Warning : When a timeout is configured on the server, the socket may be closed while the underlying request continues. Make sure that the request is actually canceled.

Awsappsync

AWS AppSync enforces a timeout of 30s on each request by default.

If your API sits behind an API Gateway (opens new window), you can configure a different (but lower than the hard 30s limit) timeout in the AWS API Gateway console by following this path:

AWS API Gateway console > {Your App} > Resources > Integration Request > "Use default timeout".

Graphqlgo

Implement a server timeout by following this guide:

The complete guide to Go net/http timeouts - Cloudfare blog (opens new window)

Hasura

Hasura allows you to set a custom query timeout.

To do so: -Go to Project Console > Security Settings > API Limits. -Click on "Global". -Set a timeout (e.g., 10s).

Stepzen

There is no known remediation for StepZen.

Reference:

https://medium.com/workflowgen/graphql-query-timeout-and-complexity-management-fab4d7315d8d (opens new window)


Configuration

{
    "checks": {
        "dos/security_timeout": {  
            "options":{ 
                "threshold_low": 1, 
                "threshold_medium": 2, 
                "threshold_high": 4, 
            }, 
            "skip": False,
        }
    }
}

Options:

threshold_low : Duration of a request (in seconds) before raising a low level alert

threshold_medium : Duration of a request (in seconds) before raising a low level alert

threshold_high : Duration of a request (in seconds) before raising a low level alert


Recursive Fragment

# Recursive Fragment

Identifier Severity
dos/recursive_fragment MEDIUM

This is a DoS vulnerability that allows an attacker with specifically designed queries to cause stack overflow panics. Any user with access to the GraphQL handler can send these queries and cause stack overflows. This in turn could potentially compromise the ability of the server to serve data to its users.


Remediations

Generic

Implement a maximum recursion limit.

Reference:

https://github.com/graph-gophers/graphql-go/security/advisories/GHSA-mh3m-8c74-74xh (opens new window)


Directive overloading

# Directive overloading

Identifier Severity
dos/directive_overloading MEDIUM

Directive Overloading occurs when a user can send a query with many consecutive directives and overload the engine handling those directives.


Remediations

Generic

Limit the number of directives allowed in a query. This should be handled by the GraphQL engine while parsing the document, otherwise this can lead to a heap overflow.

Apollo

Upgrade to GraphQL>=16.0.0 if you are not already up to date. You can also use our GraphQL Armor (opens new window) middleware to limit the number of directives allowed in a query.

Graphqlyoga

Upgrade to GraphQL>=16.0.0 if you are not already up to date. You can also use our GraphQL Armor (opens new window) middleware to limit the number of directives allowed in a query.

Reference:

no-reference-available-yet


Configuration

{
    "checks": {
        "dos/directive_overloading": {  
            "options":{ 
                "threshold": 50, 
            }, 
            "skip": False,
        }
    }
}

Options:

threshold : Maximum number of directives allowed before raising an alert in the fast check.


Automatic Persisted Queries

# Automatic Persisted Queries

Identifier Severity
dos/automatic_persisted_queries LOW

The absence of Automatic Persisted Queries can cause backend performance problems at scale.

GraphQL clients send queries to Apollo Servers as HTTP requests, including the GraphQL query string. Depending on your GraphQL schema, the size of a valid query string might be arbitrarily large. As query strings become larger, increased latency and network usage can noticeably degrade client performance. A persisted query is a query string cached on the server-side, along with its unique identifier (SHA-256 hash of the query). Clients can send this identifier instead of the full query string, drastically reducing request sizes.

To make a query string persist, your GraphQL server must first receive it from a requesting client. Each unique query string must therefore be sent to the server at least once. Once a client has sent a query string to persist, any other client executing that query can benefit from APQ.


Remediations

Generic

To improve network performance for large query strings, enable APQ if your GraphQL server supports it.

Apollo

Enable Automatic Persisted queries. For a complete guide on how to do so, see Apollo's Automatic Persisted Queries documentation (opens new window).

Dgraph

For a complete guide on the matter, see dgraph's Persisted Queries documentation (opens new window).

Gqlgen

Enable Automatic persisted Queries For a complete guide on how to do so, see gqlgen's Automatic Persisted Queries documentation (opens new window).

Graphene

Automatic Persisted Queries are not supported by Graphene alone.

However, if you use Graphene with Django (opens new window), the django-graphql-persist (opens new window) library allows you to implement Automatic Persisted Queries.

Graphqlapiforwp

Automatic Persisted Queries on graphqlapiforwp are different than with other engines. Learn more on graphqlapiforwp's Persisted Query guide (opens new window). You can also implement custom persisted queries using WP GraphQL Lock (opens new window).

Ruby

Add graphql-persisted_queries to your Gemfile gem 'graphql-persisted_queries' and add the plugin to your schema class:

class GraphqlSchema < GraphQL::Schema
  use GraphQL::PersistedQueries
end

Pass the :extensions argument as part of a context to all calls of GraphqlSchema#execute, usually it happens in GraphqlController, GraphqlChannel and tests:

GraphqlSchema.execute(
  params[:query],
  variables: ensure_hash(params[:variables]),
  context: {
    extensions: ensure_hash(params[:extensions])
  },
  operation_name: params[:operationName]
)

Source: https://github.com/DmitryTsepelev/graphql-ruby-persisted_queries.

Reference:

https://www.apollographql.com/docs/apollo-server/performance/apq/ (opens new window)


Circular introspection

# Circular introspection

Identifier Severity
dos/circular_introspection INFO

A circular introspection has been performed on the system. This can be exploited to conduct a Denial of Service attack.

The GraphQL introspection is done recursively on your schema. By calling several objects that reference each other, an attacker can create a circular reference.

__schema {
  types {
    fields {
      type {
        fields {
          type ... and so on infinitely
        }
      }
    }
  }
}

Creating a circular reference will increase the size of the returned document exponentially. The larger your schema is, the bigger the document will be.

This DOS attack is hard to exploit as the size of the query might be limited by a server before reaching your backend.


Remediations

Generic

To prevent circular introspection from being abused:

  • Limit the maximum depth of the introspection query.
  • Limit the maximum elapsed time to execute a GraphQL query.
  • Disable your introspection.

# Access Control


Tenant isolation

# Tenant isolation

Identifier Severity
access_control/tenant_isolation HIGH

Uses the rules defined by the users to detect same object instances detected by two different users whereas this is prohibited. According to the rules provided in the configuration file, the same instance or object can be detected by two different users which is prohibited.


Remediations

Generic

When accessing the application via GraphQL, we must validate whether or not the user has access to the requested elements from the schema. Especially we must implement access control policies on every path of the Graph leading considered field or object.

The authorization logic belongs to the business logic layer, and from there it is accessed by GraphQL. This way, the application can have a single source of truth for authorization, which can then be used for other access points.

Among the several access control policies we can implement in our application, the two most popular ones are Role-Based Access Control (RBAC) and Attribute-Based Access Control (ABAC).

  • With Role-Based Access Control, we grant permissions based on roles, and then assign the roles to the users. For instance, WordPress has an administrator role with access to all resources, and the editor, author, contributor, and subscriber roles, which each restrict permissions in varying degrees, such as being able to create and publish a blog post, just create it, or just read it.
  • With Attribute-Based Access Control, permissions are granted based on metadata that can be assigned to different entities, including users, assets, and environment conditions (such as the time of the day or the visitor's IP address). For instance, in WordPress, the capability edit_others_posts is used to validate whether the user can edit other users' posts.

In general terms, ABAC is preferable over RBAC because it allows us to configure permissions with fine-grained control, and the permission is unequivocal in its objective.

Apollo

See Apollo's Access Control Documentation (opens new window). For large scale applications, you might want to use a specific package like (opens new window) for easy Access Control Management.

Awsappsync

Appsync provides several methods for protecting critical information.

  • For implementing fine-grained access control, see https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html#fine-grained-access-control
Hasura

See Hasura's detailed documentation for Authorization Management (opens new window)

Reference:

https://blog.logrocket.com/authorization-access-control-graphql/ (opens new window)


Configuration

{
    "checks": {
        "access_control/tenant_isolation": { 
            "parameters":{ 
                "objects": ['**value**'], 
                "scalars": {'**value**': ['**value**']}, 
            },  
            "skip": False,
        }
    }
}

Examples:

Accessiblity of objects private instances for differents users
{
  ... Authentication settings ...
  ... Other configuration settings ...

  "checks": {

    ... Other checks ...

    "access_control/tenant_isolation": {
      "parameters": {
        "objects": [
          "MyVeryPrivateData",            # Record access to object `MyVeryPrivateData`
                                          #  if two different users access the same object
                                          #  (i.e. two different users access the same self bound private data)
                                          #  the an alert will be raised.
        ],
        "scalars": {
          "Post": [
            "createdBy",                  # Record access to field `createdBy` of object `Post`
                                          #  if two different users can access the same scalar value
                                          #  an alert will be raised.
          ]
        }
      }
    }

    ... Other checks ...
  }

  ... Other configuration settings ...
}

Parameters:

objects : A list of objects that are private.

{'objects': ['**value**']}

scalars : A list of object fields that are private.

{'scalars': {'**value**': ['**value**']}}


Secrets leaks

# Secrets leaks

Identifier Severity
access_control/secrets_leaks HIGH

The database exposes sensitive data to the public, such as secrets, private keys, tokens, passwords, etc. This security check detects this sensitive data.


Remediations

Generic
Apollo

See Apollo's Access Control Documentation (opens new window). For large scale applications, you'll want to use a specific package like GraphQL Shield (opens new window) for quick and easy Access Control management.

Awsappsync

Appsync provides several other methods for protecting critical information. -To learn more on implementing fine-grained access control, head over to https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html#fine-grained-access-control. -To learn more on filtering critical data directly from responses, see https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html#aws-appsync-filtering-information.

Hasura

See Hasura's detailed documentation for authorization management (opens new window).

Reference:

https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html (opens new window)


Configuration

{
    "checks": {
        "access_control/secrets_leaks": {  
            "options":{ 
                "blacklist": [], 
            }, 
            "skip": False,
        }
    }
}

Options:

blacklist : List of elements to ignore.


Private fields

# Private fields

Identifier Severity
access_control/private_fields HIGH

According to the rules provided in the configuration file, objects and object fields can be accessed by unauthorized users.


Remediations

Generic

When accessing the application through GraphQL, it is important to validate whether or not the user is given access to the requested elements from the schema. Access control policies must therefore be implemented on every path of the Graph leading to the given field or object.

The authorization logic belongs to the business logic layer, and from there it is accessed by GraphQL. This way, the application can have a single source of truth for authorization, which can then be used for other access points.

Among the several access control policies that can be implemented in an application, the two most popular ones are Role-Based Access Control (RBAC) and Attribute-Based Access Control (ABAC).

  • With Role-Based Access Control, permissions are granted based on roles which are later assigned to the users. For instance, WordPress has an administrator role with access to all resources, and the editor, author, contributor, and subscriber roles which each restrict permissions in various degrees, such as being able to create and publish a blog post, just create it, or simply read it.
  • With Attribute-Based Access Control, permissions are granted based on metadata that can be assigned to different entities including users, assets, or environment conditions (the time of the day or the visitor's IP address for example). In the WordPress example, the capability edit_others_posts is used to validate whether or not the user can edit other users' posts.

For most use cases, ABAC is preferable to RBAC as it allows for finely tuned permissions with explicit goals.

Apollo

See Apollo's Access Control documentation (opens new window). For large scale applications, you'll want to use a specific package like GraphQL Shield (opens new window) for quick and easy Access Control management.

Awsappsync

Appsync provides several methods for protecting critical information. -To learn how to implement fine-grained access control, head over to https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html#fine-grained-access-control.

Hasura

See Hasura's detailed documentation for Authorization Management (opens new window).

Reference:

https://blog.logrocket.com/authorization-access-control-graphql/ (opens new window)


Configuration

{
    "checks": {
        "access_control/private_fields": { 
            "parameters":{ 
                "**user**": , 
            },  
            "options":{ 
                "empty_values_are_positive": False, 
            }, 
            "skip": False,
        }
    }
}

Examples:

Accessiblity of private objects in a GraphQL API for not authenticated users.
{
  "auth": {
    "noauth": {                      #   
      "tech": "noauth"               # Default value on a new application 
    },                               #
    ... REDACTED AUTH ...
  },
  "users": {                         
    "noauth": {                      #
      "auth": "noauth"               # Default value on a new application 
    },                               #
    "exampleUser": {
      ... REDACTED AUTH ...
    }
  }
  ... Other configuration settings ...

  "checks": {

    ... Other checks ...

    "access_control/private_fields": {
      "noauth": {                    <--- Triggering on a "users" object key
        "Query": [                   <--- Object name
          "getUsersById",            <--- Field name
          "getAllUsers"              # Here, the noauth user is not supposed to 
                                     #  have access to the "getUsersById" and "getAllUsers" queries.
        ],
        "Mutation": [
          ...
        ],
        "User": [                    <--- Object name
          "id"                       # Here, the noauth user is not supposed to
                                     #  have access to the "id" field of the "User" object
        ]
      }
    }

    ... Other checks ...
  }

  ... Other configuration settings ...
}

Parameters:

user : An object where the key are GraphQL objects name and the value is an array of field's name that the user is not supposed to have access to.

{'__user': {'**value**': ['**value**']}}

Options:

empty_values_are_positive : When the API returns a None value without error is the field considered to be successfully accessed ?


Private data

# Private data

Identifier Severity
access_control/private_data HIGH

According to the rules provided in the configuration file, private data can be accessed by unauthorized users.


Remediations

Generic

The best way to ensure that private data can only be accessed by authorized users is by implementing a proper access control system. To do so, access control must be applied to every object and every link in the graphQL schema.

Apollo

See Apollo's Access Control documentation (opens new window). For large scale applications, you'll want to use a specific package like GraphQL Shield (opens new window) for quick and easy Access Control management.

Awsappsync

Appsync provides several methods for protecting critical information.

  • To learn more on implementing fine-grained access control, head over to https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html#fine-grained-access-control.
Hasura

See Hasura's detailed documentation for Authorization Management (opens new window).

Reference:

https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html (opens new window)


Configuration

{
    "checks": {
        "access_control/private_data": { 
            "parameters":{ 
                "**user**": , 
            },  
            "skip": False,
        }
    }
}

Parameters:

user : A list of values the user should never be able to access.

{'__user': ['**value**']}


Public Unsafe Route

# Public Unsafe Route

Identifier Severity
access_control/public_unsafe_route MEDIUM

A route that mutate application data should not be public.


Remediations

Generic

Restrict access to the route, using an authentication middleware for example.

Reference:

https://owasp.org/Top10/A01_2021-Broken_Access_Control/ (opens new window)


IDOR

# IDOR

Identifier Severity
access_control/idor MEDIUM

We are able to enumerate a field without any limits.


Remediations

Generic

Change the argument to one that cannot be enumerated.


Configuration

{
    "checks": {
        "access_control/idor": {  
            "options":{ 
                "threshold_res": 0.8, 
                "threshold_enum": 0.6, 
            }, 
            "skip": False,
        }
    }
}

Options:

threshold_res : Rate of correct responses to an argument being enumerated to raise an alert.

threshold_enum : Rate of iterable values of a field to be considered iterable.

# Introspection


Typing inconsistency

# Typing inconsistency

Identifier Severity
introspection/typing_inconsistency MEDIUM

Look for typing misconfigurations by checking if a mutation parameter with the wrong parameter type succeeds.


Remediations

Generic

Do not resolve queries with a wrong argument type.

Apollo

Apollo doesn't allow arguments of the wrong type by default.

Example:

{
  "errors": [
    {
      "message": "String cannot represent a non string value: 123",
      "extensions": {
        "code": "GRAPHQL_VALIDATION_FAILED"
      }
    }
  ]
}

This error should appear if you are using Apollo.

Awsappsync

AWS AppSync doesn't allow arguments of the wrong type by default.

Example:

{
  "data": null,
  "errors": [
    {
      "path": null,
      "locations": [
        {
          "line": 1,
          "column": 18,
          "sourceName": null
        }
      ],
      "message": "Validation error of type WrongType: argument 'a' with value 'StringValue{value='4'}' is not a valid 'Int' @ 'testType'"
    }
  ]
}

Reference:

https://cheatsheetseries.owasp.org/cheatsheets/GraphQL_Cheat_Sheet.html (opens new window)


Configuration

{
    "checks": {
        "introspection/typing_inconsistency": {  
            "options":{ 
                "skip_objects": [], 
            }, 
            "skip": False,
        }
    }
}

Options:

skip_objects : List of object that are to be skipped by the security test.


Zombie objects

# Zombie objects

Identifier Severity
introspection/zombie_objects LOW

Zombie objects are objects that are not accessible from any query, mutation,or subscription, but are still declared in your GraphQL schema. Most of the time, zombie objects reveal legacy or unused part of your codebase. Because they are not maintained nor patched, they are a privileged vector of attack and represent a severe security risk for your application.


Remediations

Generic

Remove zombie objects from your GraphQL schema and associated code if they are indeed useless in your codebase, otherwise make them accessible from at least one query, mutation or subscription.

Reference:

https://cwe.mitre.org/data/definitions/561.html (opens new window)


Unhandled operation

# Unhandled operation

Identifier Severity
introspection/unhandled_operation INFO

Some operations can be found in the introspection but have no handler implemented.


Remediations

Generic

Either remove the operations from your schema or create a handler for them.


Undefined objects

# Undefined objects

Identifier Severity
introspection/undefined_objects INFO

Undefined objects are objects that use the built-in GraphQL object type instead of referencing a custom one. They can be at the root of security issues due to their unstructured nature.


Remediations

Generic

Enforce strong typing in your GraphQL objects.


Typing inconsistency (interceptor)

# Typing inconsistency (interceptor)

Identifier Severity
introspection/typing_inconsistency_interceptor INFO

This security check verifies that all the data returned in the response matches its expected type, as defined in the introspection.


Remediations

Generic

Update your resolver to make the introspection type match the actual returned type.

Reference:

https://cheatsheetseries.owasp.org/cheatsheets/GraphQL_Cheat_Sheet.html (opens new window)


Imputable JSON scalars

# Imputable JSON scalars

Identifier Severity
introspection/imputable_json_scalars INFO

Imputable JSON scalars are an arbitrary GraphQL scalar type that allow users to return JSON objects from the GraphQL schema. It is a bad practice and may represent an unhandled data leak risk for your application.


Remediations

Generic

When possible, use typed input objects instead.

# Complexity


Pagination limit

# Pagination limit

Identifier Severity
complexity/pagination_limit MEDIUM

A security check that ensures that an attacker cannot launch a DoS attack by quering all the nodes in a connection.


Remediations

Generic

To prevent such an attack, limit pagination variables.


Configuration

{
    "checks": {
        "complexity/pagination_limit": {  
            "options":{ 
                "skip_objects": [], 
            }, 
            "skip": False,
        }
    }
}

Options:

skip_objects : List of object that are to be skipped by the security test.


Large JSON

# Large JSON

Identifier Severity
complexity/large_json MEDIUM

Inputting a very large sized JSON as an argument.


Remediations

Generic

Limit the maximum size of a JSON that can be inputted.

Reference:

https://gusralph.info/file-upload-checklist/ (opens new window)


Configuration

{
    "checks": {
        "complexity/large_json": {  
            "options":{ 
                "skip_objects": [], 
            }, 
            "skip": False,
        }
    }
}

Options:

skip_objects : List of object that are to be skipped by the security test.


Field limit

# Field limit

Identifier Severity
complexity/field_limit MEDIUM

Attackers may craft complex queries by requesting a significant number of fields.

This could lead to potential DoS attacks or information leakage.


Remediations

Generic

Limit query complexity by using a library specific to your engine.

Apollo

Add a module to compute query complexity and set a threshold so that overly broad requests get canceled.

For a user-friendly module which requires no schema modification whatsoever, check out the graphql-validation-complexity (opens new window) module.

import { createComplexityLimitRule } from 'graphql-validation-complexity';

const ComplexityLimitRule = createComplexityLimitRule(1000);

const apolloServer = new ApolloServer({
    ...
    validationRules: [ComplexityLimitRule],
});

For a more customizable module that lets you manually configure the cost of each field/type of your schema, take a look at the graphql-cost-analysis (opens new window) module.

This second option is best suited for a more realistic complexity estimator as all fields may not be equal in terms of complexity.

To learn more about complexity estimation, you can read: Securing Your GraphQL API from Malicious Queries (opens new window).

Source: https://escape.tech/blog/9-graphql-security-best-practices/ (opens new window).

Graphene

With graphene-django, it is possible to implement a custom GraphQL backend to limit query complexity, such as this one: graphene-django query cost analysis / complexity limits (opens new window).

Graphqlphp
use GraphQL\GraphQL;
use GraphQL\Validator\Rules\QueryComplexity;
use GraphQL\Validator\DocumentValidator;

$rule = new QueryComplexity($maxQueryComplexity = 100);
DocumentValidator::addRule($rule);

GraphQL::executeQuery(/*...*/);

Source: https://github.com/webonyx/graphql-php/blob/master/docs/security.md#query-complexity-analysis (opens new window)

Hasura

Limit the number of fecthed fields with Response Limiting. To learn how to implement it, check out Hasura's Response Limiting guide (opens new window).

Reference:

https://www.apollographql.com/blog/graphql/security/securing-your-graphql-api-from-malicious-queries/ (opens new window)


Configuration

{
    "checks": {
        "complexity/field_limit": {  
            "options":{ 
                "threshold": 100, 
            }, 
            "skip": False,
        }
    }
}

Options:

threshold : Maximum fields before raising an alert (-1 = infinite).


Depth limit

# Depth limit

Identifier Severity
complexity/depth_limit MEDIUM

GraphQL does not limit how deep a query can be.

Since GraphQL schemas are often cyclic graphs, a query like the one below could technically be crafted:

query IAmEvil {
  author(id: "abc") {
    posts {
      author {
        posts {
          author {
            posts {
              author {
                # Can go on as deep as the client wants
              }
            }
          }
        }
      }
    }
  }
}

This can lead to potential DoS attacks or information leakage.


Remediations

Generic

Secure your application by preventing clients from abusing query depth. To do so, add a Maximum Query Depth limit based on your knowledge of the schema and how deep you believe a legitimate query could go. By analyzing the query document's abstract syntax tree (AST), a GraphQL server is able to reject or accept a request based on its depth.

For instance, using graphql-ruby with the max query depth setting set to 3 gives the following result:

{
  "errors": [
    {
      "message": "Query has depth of 6, which exceeds max depth of 3"
    }
  ]
}

Since the document's AST is analyzed statically, the query does not even execute, which adds no load on your GraphQL server.

Depth alone is often not enough to cover all abusive queries. For example, a query requesting an enormous amount of nodes on the root will be very expensive but unlikely to be blocked by a query depth analyzer.

Apollo

This remediation is supported by our GraphQL Armor (opens new window) middleware.

You can also limit the depth of queries with the very light graphql-depth-limit (opens new window) library.

Check how deep legitimate queries should be and set a maximum depth accordingly.

import depthLimit from 'graphql-depth-limit'

const server = new ApolloServer({
  // ...
  validationRules: [depthLimit(5)]
});

Source: https://escape.tech/blog/9-graphql-security-best-practices/ (opens new window).

Awsappsync

For now, AppSync does not allow out-of-the-box query depth limit configuration.

This can however be bypassed by implementing depth limit using Velocity Template Language VTL in Escape's Resolver. Below is an example of using the Matches regex to determine the length of selectionSetList. This example enforces a depth limit of 3 and can be added inside of an AppSync resolver function.

#set($selectionSetList = $ctx.info.selectionSetList)

#foreach ($item in $selectionSetList)
  #if($item.matches(".*/.*/./."))
    $util.error("Error: Queries with more than 3 levels found. At level - $item")
  #end
#end
#return($ctx.prev.result)

Source: https://robertbulmer.medium.com/aws-appsync-rate-and-max-depth-limiting-c536e5ba43d6.

Graphene

With graphene-django, it is possible to implement a custom GraphQL backend to limit query complexity, such as this one: graphene-django query cost analysis / complexity limits (opens new window).

Graphqlphp
use GraphQL\GraphQL;
use GraphQL\Validator\Rules\QueryDepth;
use GraphQL\Validator\DocumentValidator;

$rule = new QueryDepth($maxDepth = 10);
DocumentValidator::addRule($rule);

GraphQL::executeQuery(/*...*/);

Source: https://github.com/webonyx/graphql-php/blob/master/docs/security.md#limiting-query-depth.

Graphqlyoga

This remediation is supported by our GraphQL Armor (opens new window) middleware.

You can also use the standalone envelop plugin (opens new window).

Hasura

Hasura allows for manual query depth limit configuration directly in the security settings:

  • Go to Project Console > Security Settings > API Limits.
  • Click on "Global".
  • Set a depth limit (e.g., 3).

Reference:

https://www.howtographql.com/advanced/4-security/ (opens new window)


Configuration

{
    "checks": {
        "complexity/depth_limit": {  
            "options":{ 
                "threshold": 3, 
            }, 
            "skip": False,
        }
    }
}

Options:

threshold : Maximum depth before raising an alert (-1 = infinite).


Width limit

# Width limit

Identifier Severity
complexity/width_limit LOW

GraphQL defines the maximum width of a query as the maximum number of subfields queried from one field.

If no limit is set on query width, clients may therefore craft a complex query that could lead to potential DoS attacks or information leakage.


Remediations

Generic

Set a threshold on the maximum number of subfields that can be queried simultaneously.

Apollo

Add a module to compute query complexity and set a threshold on this complexity so that overly broad requests get canceled.

For a user-friendly module which requires no schema modification whatsoever, check out the graphql-validation-complexity (opens new window) module.

import { createComplexityLimitRule } from 'graphql-validation-complexity';

const ComplexityLimitRule = createComplexityLimitRule(1000);

const apolloServer = new ApolloServer({
    ...
    validationRules: [ComplexityLimitRule],
});

For a more customizable module that lets you manually configure the cost of each field/type of your schema, take a look at the graphql-cost-analysis (opens new window) module.

This second option is best suited for a more realistic complexity estimator as all fields may not be equal in terms of complexity.

To learn more on complexity estimation, you can read: Securing Your GraphQL API from Malicious Queries (opens new window).

Graphene

With graphene-django, it is possible to implement a custom GraphQL backend to limit query complexity, such as this one: graphene-django query cost analysis / complexity limits (opens new window).

Hasura

Hasura allows you to set a width (=node) limit.

To do so: -Go to Project Console > Security Settings > API Limits. -Click on "Global". -Set a node limit (e.g., 15).

Reference:

https://www.apollographql.com/blog/graphql/security/securing-your-graphql-api-from-malicious-queries/ (opens new window)


Configuration

{
    "checks": {
        "complexity/width_limit": {  
            "options":{ 
                "threshold": 20, 
            }, 
            "skip": False,
        }
    }
}

Options:

threshold : Maximum width before raising an alert (-1 = infinite).


Cyclic query

# Cyclic query

Identifier Severity
complexity/cyclic_query LOW

GraphQL allows developers to nest queries and objects. Attackers can abuse this feature by calling a deeply nested query similar to a recursive function and causing a Denial of Service by exhausting CPU, memory, or other resources.


Remediations

Generic

Although the ability to fetch a cyclic query is necessary for some GraphQL application, it is best to always implement security measures to control these cyclic queries: -Set query timeouts: restrict the time a query is allowed to run. -Set a maximum query depth: limit the tolerated depth of queries in order to prevent overly deep queries from abusing resources. -Set a maximum query complexity: limit the complexity of queries to mitigate the abuse of GraphQL resources. -Use server-time-based throttling: limit the amount of server time a user can consume. -Use query-complexity-based throttling: limit the total complexity of queries a user can consume.

Apollo

Although the ability to fetch a cyclic query is necessary for some GraphQL application, it is best to always implement security measures to control these cyclic queries: -Set a maximum query depth: limit the depth of allowed queries in order to prevent overly deep queries from abusing GraphQL resources.

You can easily limit query depth with the very light graphql-depth-limit (opens new window) library.

Add a maximum query depth limit based on your knowledge of the schema and how deep you believe a legitimate query could go.

import depthLimit from 'graphql-depth-limit'

const server = new ApolloServer({
  ...
  validationRules: [depthLimit(5)]
  });

Source: https://escape.tech/blog/9-graphql-security-best-practices/ (opens new window).

-Set maximum query complexity: limit the complexity of allowed queries to prevent overly complex queries from abusing GraphQL resources.

To do so, add a module to compute the complexity of each query and set a threshold on this complexity so that overly broad requests get canceled.

For a user-friendly module which requires no schema modification whatsoever, check out the [graphql-validation-complexity](https://github.com/4Catalyzer/graphql-validation-complexity) module.

```javascript
import { createComplexityLimitRule } from 'graphql-validation-complexity';

const ComplexityLimitRule = createComplexityLimitRule(1000);

const apolloServer = new ApolloServer({
    ...
    validationRules: [ComplexityLimitRule],
});
```

For a more customizable module that lets you manually configure the cost of each field/type of your schema, take a look at the [graphql-cost-analysis](https://github.com/pa-bru/graphql-cost-analysis) module.

This second option is best suited for a more realistic complexity estimator as all fields may not be equal in terms of complexity.

To learn more about complexity estimation, you can read: [Securing Your GraphQL API from Malicious Queries](https://www.apollographql.com/blog/graphql/security/securing-your-graphql-api-from-malicious-queries/).


Source: <https://escape.tech/blog/9-graphql-security-best-practices/>.
Graphene

With graphene-django, it is possible to implement a custom GraphQL backend to limit query complexity, such as this one: graphene-django query cost analysis / complexity limits (opens new window).

Reference:

https://www.apollographql.com/blog/graphql/security/securing-your-graphql-api-from-malicious-queries/ (opens new window)


Character limit (interceptor)

# Character limit (interceptor)

Identifier Severity
complexity/character_limit_interceptor LOW

Clients using GraphQL may craft a query with a huge amount of characters. This could lead to potential DoS attacks or information leakage.


Remediations

Generic

Reject requests containing more than a certain number of characters. For instance, 3,000 is a coherent threshold for characters.

This naïve approach will not prevent clever hackers from crafting costly requests if short field names are available. One should prefer the better but more difficult to implement "query complexity" method and set a complexity threshold instead.

Apollo

Reject requests containing more than a certain number of characters.

For instance, 3,000 is a coherent threshold for characters.

This remediation is supported by GraphQL Armor (opens new window) middleware.

Otherwise, this is an example code for Apollo with Express.js:

import bodyParser from "body-parser";
...
app.use(bodyParser.json({ limit : 3000, type : '*/*' }));

Note: If your application is designed to send big graphql queries, you might want to put a higher character limit.

This naïve approach will not prevent clever hackers from crafting costly requests if short field names are available. One should additionally use the better but more difficult to implement "query complexity" method and set a complexity threshold.

Source: https://www.apollographql.com/blog/graphql/security/securing-your-graphql-api-from-malicious-queries/ (opens new window)

Awsappsync

Add ACL rule to prevent requests bigger than a threshold. (e.g., 3000 characters) This would be entered into the Rule JSON editor when creating a web ACL in the AWS WAF Console :

{
  "Name": "BodySizeRule",
  "Priority": 1,
  "RuleAction": {
    "Block": {}
  },
  "Statement": {
    "SizeConstraintStatement": {
      "ComparisonOperator": "GE",
      "FieldToMatch": {
        "Body": {}
      },
      "Size": 3000,
      "TextTransformations": [
        {
          "Priority": 0,
          "Type": "NONE"
        }
      ]
    }
  },
  "VisibilityConfig": {
    "CloudWatchMetricsEnabled": true,
    "MetricName": "BodySizeRule",
    "SampledRequestsEnabled": true
  }
}

Don't forget to associate the previously created ACL rule with your AppSync API.

For more information refer to :

AWS AppSync - Developer Guide (opens new window)

Integrate an AppSync API with AWS WAF (opens new window)

AWS Web Application Firewall (opens new window)

Graphqlgo

You can limit query size with a net/http middlware.

func limitBodySize(next http.Handler, limit int64) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    limitedBody := http.MaxBytesReader(w, r.Body, limit)
    bodyBytes, err := ioutil.ReadAll(limitedBody)
    limitedBody.Close()
    if err != nil {
      message := "{\"errors\": [{\"message\": \"Request too large.\"}],\"data\": null}"
      w.Write([]byte(message))
    } else {
      r.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
      next.ServeHTTP(w, r)
    }
  })
}

func main(){
  ...
  h := handler.New(&handler.Config{
    Schema:   &schema
  })
  http.Handle("/graphql", limitBodySize(h, 3000))
}
Graphqlyoga

Reject requests containing more than a certain number of characters.

This remediation is supported by GraphQL Armor (opens new window) middleware.

Otherwise, you can use the standalone envelop plugin (opens new window) directly.

Reference:

https://shopify.engineering/rate-limiting-graphql-apis-calculating-query-complexity (opens new window)


Configuration

{
    "checks": {
        "complexity/character_limit_interceptor": {  
            "options":{ 
                "threshold": 15500, 
            }, 
            "skip": False,
        }
    }
}

Options:

threshold : Maximum characters before raising an alert (-1 = infinite)


Batch Limit

# Batch Limit

Identifier Severity
complexity/batch_limit LOW

Some GraphQL engines support batching of multiple queries into a single request. This allows users to request multiple objects or multiple instances of objects efficiently. However, an attacker can leverage this feature to evade many security measures, including rate limiting.


Remediations

Generic

Disable or limit queries batching in your GraphQL engine.

Apollo

Disable query batching in ApolloServer constructor options.

This remediation is supported by GraphQL Armor (opens new window) middleware.

Otherwise, you can switch off batching in the ApolloServer constructor options.

const server = new ApolloServer({
  ...
  allowBatchedHttpRequests: false,
)}

Source: https://www.apollographql.com/docs/apollo-server/requests/#batching (opens new window)

Reference:

https://lab.wallarm.com/graphql-batching-attack/ (opens new window)


Configuration

{
    "checks": {
        "complexity/batch_limit": {  
            "options":{ 
                "threshold": 15, 
            }, 
            "skip": False,
        }
    }
}

Options:

threshold : Maximum number of batched documents allowed to be sent


Alias limit

# Alias limit

Identifier Severity
complexity/alias_limit LOW

GraphQL supports the aliasing of multiple sub-queries into a single query. It allows requesting multiple instances of objects efficiently and without conflicts. However, attackers can leverage this feature to bypass many security measures, including rate limiting.

Query example:

query {
  a: myself{username}
  b: myself{username}
  ...
}

Response example:

{
  "data": {
    "a": {"username":"EscapeAdmin"},
    "b": {"username":"EscapeAdmin"},
    ...
  }
}

Remediations

Generic

Limit query aliasing in your GraphQL Engine to prevent aliasing-based attacks.

Apollo

Adding a limit on request complexity is a much better alternative to disabling aliasing, which can trigger other issues.

This remediation is supported by our GraphQL Armor (opens new window) middleware.

When using Apollo, you can also add a module to compute the query complexity and set a threshold so that overly broad requests get canceled.

For a user-friendly module which requires no schema modification whatsoever, check out the graphql-validation-complexity (opens new window) module.

import { createComplexityLimitRule } from 'graphql-validation-complexity';

const ComplexityLimitRule = createComplexityLimitRule(1000);

const apolloServer = new ApolloServer({
    ...
    validationRules: [createComplexityLimitRule(1000)],
});

For a more customizable module that lets you manually configure the cost of each field/type of your schema, take a look at the graphql-cost-analysis (opens new window) module.

This second option is best suited for a more realistic complexity estimator as all fields may not be equal in terms of complexity.

To learn more about complexity estimation, you can read: Securing Your GraphQL API from Malicious Queries (opens new window)

Reference:

https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/12-API_Testing/01-Testing_GraphQL (opens new window)