# ✅ 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.


# Request Forgery

Identifier Severity
request_forgery/ssrf HIGH
request_forgery/partial_ssrf HIGH
request_forgery/get_based_csrf HIGH
request_forgery/post_based_csrf MEDIUM

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

First, it is important to note that simple blacklisting and regular expressions are a bad approach to mitigate SSRF.

The correct ways to prevent SSRF are:

  • Whitelisting and DNS Resolution: The most robust way to avoid server-side request forgery (SSRF) is to whitelist the hostname (DNS name) or IP address that your application needs to access.
  • 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.
  • Disable unused URL schemas: If your application only uses HTTP or HTTPS to make requests, allow only these URL schemas. If you disable unused URL schemas, the attacker will be unable to use 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

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

First, it is important to note that simple blacklisting and regular expressions are a bad approach to mitigate SSRF.

The correct ways to prevent SSRF are:

  • Whitelisting and DNS Resolution: The most robust way to avoid server-side request forgery (SSRF) is to whitelist the hostname (DNS name) or IP address that your application needs to access.
  • 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.
  • Disable unused URL schemas: If your application only uses HTTP or HTTPS to make requests, allow only these URL schemas. If you disable unused URL schemas, the attacker will be unable to use 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)


Check configuration

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

Options:

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


GET based CSRF

Identifier Severity
request_forgery/get_based_csrf HIGH

CSRF (Cross-Site Request Forgery) happens when an external website gains the ability to make API calls impersonating a user if he visits 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

Disallow 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

Ensure that your API does not use Cookie-based authentication.

There are many 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, ensure that authentication occurs through headers because authentication headers are not added automatically 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)

Then you can configure the API gateway to act as an HTTP Proxy to your AppSync endpoint and configure it to use only POST method.

  • Block GET requests with AWS Web Application Firewall

Here is the Web ACL rule to block every GET request to the API, which you can add in the AWS WAF Console:

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

Or, if you prefer to configure it manually, use the following fields values :

If:

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

Then:

  • Block

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

AWS WAF (opens new window)

Reference:

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


POST based CSRF

Identifier Severity
request_forgery/post_based_csrf MEDIUM

CSRF happens when an external website gains the ability to make API calls impersonating a user if he visits the website while being authenticated to your API.

POST requests are natural CSRF targets since they usually change the application state. GraphQL endpoints typically accept Content-Type headers set to application/json only, which is widely believed to be invulnerable to CSRF. As multiple layers of middleware may translate the incoming requests from other formats (such as query parameters, application/x-www-form-urlencoded, multipart/form-data), GraphQL implementations are often affected by CSRF.

Another incorrect assumption is that JSON cannot be created from URL-encoded requests. When both of these assumptions are made, many developers may incorrectly forego implementing proper CSRF protections.


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

enforce-content-type Github Repo (opens new window)

Reference:

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

# Injection

Identifier Severity
injection/xxe HIGH
injection/xss HIGH
injection/stored_xss HIGH
injection/sql HIGH
injection/nosql HIGH
injection/directory_traversal HIGH
injection/bash_command HIGH

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 may compromise the security of the data the server/XML application has access to. Attackers may also use External Entities to have the web services server download malicious code or content to the server for use in secondary or follow on attacks.


Remediations

Generic

The safest way to prevent XXE is always to 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 makes the parser secure against denial of services (DOS) attacks such as Billion Laughs. If it is not possible to disable DTDs completely, then external entities and external document type declarations must be disabled in the way that's specific to each parser.

Reference:

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


XSS

Identifier Severity
injection/xss HIGH

XSS (Cross-site scripting) is an attack where malicious code (eg. JavaScript) is injected into the application and stored in the database. In the Stored XSS vulnerability, the (maliciously) executed code originates from the database. The attack occurs due to malicious data previously sent to the database.


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.

In general, effectively preventing XSS vulnerabilities is likely to involve a combination of the following measures:

  • Filter input on arrival. Filter user input as strictly as possible based on what you expect as input.
  • Encode data on output. At the point where user-controllable data is output in HTTP responses, encode the output 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 appropriate response headers. 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 to ensure that browsers interpret the responses the way you intend.
  • Content Security Policy. As a last line of defense, you can use Content Security Policy (CSP) to reduce the severity of any XSS vulnerabilities that still occur.

Reference:

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


Check configuration

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

Options:

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


Stored XSS

Identifier Severity
injection/stored_xss HIGH

Malicious code injection is an attack where malicious code (such as JavaScript) is injected into the application and stored in the database. CSS is one of those attacks. Stored XSS is when an XSS vulnerability originates from the database and the attack occurs due to malicious data that is already existent in the database.


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.

In general, effectively preventing XSS vulnerabilities is likely to involve a combination of the following measures:

  • Filter input on arrival. Filter user input as strictly as possible based on what you expect as input.
  • Encode data on output. At the point where user-controllable data is output in HTTP responses, encode the output 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 appropriate response headers. 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 to ensure that browsers interpret the responses the way you intend.
  • Content Security Policy. As a last line of defense, you can use Content Security Policy (CSP) to reduce the severity of any XSS vulnerabilities that still occur.

Reference:

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


Check configuration

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

Options:

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


SQL

Identifier Severity
injection/sql HIGH

A SQL injection vulnerability happens when users can insert (or “inject”) malicious SQL code in a legit SQL query that is built from user-submitted input.


Remediations

Generic
  • Primary Defenses:
    • Option 1: Use of Prepared Statements (with Parameterized Queries). This prevention techniques is the most effective one as it will completely shutdown any SQL injection attacks. It is also important to note that prepared statement must be used everywhere even if a user inputted data is not used in the query.
    • Option 2: Use of Stored Procedures.
    • Option 3: Allow-list Input Validation. Usage whitelist is recommended to prevent SQL injection attacks as whitelisting is more effective then black listing.
    • Option 4: Escaping All User Supplied Input.
  • Additional Defenses:
    • Enforcing Least Privilege.
    • Performing Allow-list Input Validation as a Secondary Defense.

Reference:

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


Check configuration

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

Options:

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


NoSQL

Identifier Severity
injection/nosql HIGH

A NoSQL injection vulnerability happens when users can insert (or “inject”) malicious NoSQL code in a legit SQL query that is built from user-submitted input.


Remediations

Generic
  • Primary Defenses:
    • Option 1: Use a sanitization library
    • Option 2: Cast the user to the expected type (eg: The user and password are strings so cast the variables to a string)
    • Option 3: Never use where, mapReduce, or group operators with user input because these operators allow the attacker to inject JavaScript and are therefore much more dangerous than others. For extra safety, set javascriptEnabled to false in mongod.conf in case you are using mongoDB.
    • Option 4: Enforcing 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)


Check configuration

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

Options:

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


Directory Traversal

Identifier Severity
injection/directory_traversal HIGH

Directory traversal is when a server allows an attacker to read a file or directories outside of the normal web server directory, and local file inclusion allows an 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 methods that can be used to prevent directory traversal attacks:

  • Avoid using parameters entered directly by the user.
  • Setting up a file/folder name whitelist system. A common practice is to whitelist certain folders, and certain types of extensions, thus excluding all others.
  • Compartmentalizing your data and setting up middleware. Another option is to implement middleware. it can take the form of an interface to the (potentially external) file system on which the data users may request s 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.
  • Limit 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)


Check configuration

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

Options:

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


Bash command

Identifier Severity
injection/bash_command HIGH

A system command was executed successfully on your application system. Command injection happens when an user can successfully execute arbitrary commands on the host operating system via 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. It is important to note that the input validation should be implemented in the backend as it will be easily bypassed if it is done in the frontend.

Reference:

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


Check configuration

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

Options:

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

# Information Disclosure

Identifier Severity
information_disclosure/source_code_disclosure HIGH
information_disclosure/stack_traceback MEDIUM
information_disclosure/field_suggestion MEDIUM
information_disclosure/debug_mode MEDIUM
information_disclosure/graphql_ide LOW
information_disclosure/introspection INFO

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 not accessable

Reference:

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


Check 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 to decide that a response is small or not

diff_threshold : The percentage that 2 responses could differ and still be considered identical

small_response_diff_threshold : The percentage that 2 small responses could differ and still be considered identical


Stack Traceback

Identifier Severity
information_disclosure/stack_traceback MEDIUM

Details about database-level or code-level errors have been sent through the response. This can lead to information leaks such as allowing attackers to identify which database or dependency you are using, and could 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 stacktrace to be directly returned to the client.

Apollo

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

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

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

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

For more details, see Apollo's documentation on omitting stacktraces (opens new window).

*Note: if this is a development or staging environment, disclosing errors can happen on purpose. In this case, to check if this issue also happens in production:

  • use our cURL copy button to get the request that generated the stack trace
  • change URL and authentication tokens to match your production environment

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

Hasura

Set 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 this is a development or staging environment, disclosing errors can happen on purpose. In this case, verify that 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

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

Identifier Severity
information_disclosure/debug_mode MEDIUM

When Debug mode is left open by developers, it allows an attacked to obtain precious information from excessive error reporting messages such as entire stacktraces 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

Identifier Severity
information_disclosure/graphql_ide LOW

A GraphQL IDE provides an interface for users to interact with the Endpoint, but an IDE is alse a cause for potential vulnerability.


Remediations

Generic

Disable GraphQL IDE, or restrict it. Check 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 :

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

Source:


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

Identifier Severity
http/ssl_certificate_expiration HIGH
http/heartbleed HIGH
http/unsecure_http MEDIUM
http/crlf MEDIUM
http/security_headers LOW
http/open_redirection LOW
http/cors LOW

SSL Certificate Expiration

Identifier Severity
http/ssl_certificate_expiration HIGH

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

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

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

Identifier Severity
http/unsecure_http MEDIUM

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

Here are 4 risks example when allowing GraphQL communication using unsecure HTTP:

  • Man in the Middle Attacks: Hackers might intercept and alter data from a legitimate request.
  • Misuse of data: Confidential information might be accessed by hackers.
  • Downranking of websites: Your website may be marked as insecure by Search Engines and rated as not trustworthy.
  • Loss of customers trust: If your website doesn't display a secure HTTPS padlock, users may consider your website insecure (and they would be right)

Using HSTS (opens new window) is not a solution to this problem, it won't protect against MITM attack and regular "public wifi sniffing" until connection has been upgraded to HTTPS. As Google (opens new window) is recommending, you should 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 connection. In most cases, this must be done at your ingress/(reverse-)proxy level.

If you are using lets encrypt certificates, make sure to authorize http redirection to https 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

Identifier Severity
http/crlf MEDIUM

CRLF occurs when an attacker can use the carry return character (\r) and a newline character (\n) in the 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

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

Identifier Severity
http/open_redirection LOW

Open redirects are one of the OWASP 2010 Top Ten vulnerabilities. This check looks at user-supplied input in query string parameters and POST data to identify where open redirects might be possible. Open redirects occur when an application allows user-supplied input (e.g. http://nottrusted.com) to control an offsite redirect. This is generally a pretty accurate way to find where 301 or 302 redirects could be exploited by spammers or phishing attacks. For example an attacker could supply a user with the following link http://example.com/example.php?url=http://malicious.example.com.


Remediations

Generic

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 URI's, or a list of trusted domains

Reference:

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


CORS

Identifier Severity
http/cors LOW

Hackers may exploit CORS (Cross-Origin Resource Sharing) misconfiguration 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).

API Gateway Documentation (opens new window)

Then you can manually enable CORS for each resource (only 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

Identifier Severity
dos/security_timeout HIGH
dos/recursive_fragment MEDIUM
dos/directive_overloading MEDIUM
dos/automatic_persisted_queries LOW
dos/circular_introspection INFO

Security timeout

Identifier Severity
dos/security_timeout HIGH

A request that took much time 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 requirements. To change it, check the configuration section below.

Example: Querying getAllUsers(){ contacts { contacts }} returns 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 is taking more than 5 seconds to execute.

Pros:

  • Simple to implement.
  • Most strategies will still use a timeout as a final protection.

Cons:

  • Damage can already be done even when the timeout kicks in.
  • Sometimes hard to implement. Cutting connections after a certain time may result in strange behaviors and may corrupt data.

Warning : When you configure a timeout on the server, the socket may be closed when in fact the underlying request continues. Make sure that the request is actually canceled.

Awsappsync

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

Suppose your API is behind an API Gateway (opens new window). In that case, you can configure a different (but lower than the hard 30s limit) timeout in the AWS API Gateway console, 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 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)


Check configuration

{
    "checks": {
        dos/security_timeout: {  
            "options":{ 
                "threshold_medium": 4, 
                "threshold_high": 10, 
            }, 
            "skip": False,
        }
    }
}

Options:

threshold_medium : Maximum duration of a request (in seconds) for a medium alert

threshold_high : Maximum duration of a request (in seconds) for a high alert


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 Max recursion limit

Reference:

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


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 when it handles 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 might lead to a heap overflow.

Apollo

Upgrade to GraphQL>=16.0.0 if you are not. You can also use the 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. You can also use the GraphQL Armor (opens new window) middleware to limit the number of directives allowed in a query.

Reference:

no-reference-available-yet


Check configuration

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

Options:

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


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 Server 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, thus reducing request sizes dramatically.

To make a query string persist, your GraphQL server must first receive it from a requesting client. Consequently, each unique query string must be sent to the server at least once. After any client sends a query string to persist, every 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, see: Apollo's Automatic persisted queries Documentation (opens new window).

Dgraph

Enable Automatic persisted queries, for a complete guide on the matter checkout dgraph's Persisted queries Documentation (opens new window).

Gqlgen

Enable Automatic persisted queries, for a complete guide on the matter checkout 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 library django-graphql-persist (opens new window) allows you to implement Automatic Persisted Queries.

Graphqlapiforwp

The idea of persisted queries on graphqlapiforwp is a little different learn more on graphqlapiforwp persisted query guide (opens new window). You can also implement a 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 :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

Identifier Severity
dos/circular_introspection INFO

Circular-query using Introspection was fetched and processed, this my pose a potential risk later along the growth of the schema.


Remediations

Generic

None

# Access Control

Identifier Severity
access_control/tenant_isolation HIGH
access_control/secrets_leaks HIGH
access_control/private_fields HIGH
access_control/private_data HIGH
access_control/idor MEDIUM

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)


Check configuration

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

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

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 these sensitive data.


Remediations

Generic
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
  • For 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)


Private fields

Identifier Severity
access_control/private_fields HIGH

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


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)


Check configuration

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

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

Identifier Severity
access_control/private_data HIGH

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


Remediations

Generic

Private data is not supposed to be accessed by unauthorized users, and to ensure that, a proper access control system should be put in place ensuring that every user has the right to access the data specified to him. To accomplish this, the 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 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://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html (opens new window)


Check configuration

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

Parameters:

user : A list of values that the user is never supposed to access.

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


IDOR

Identifier Severity
access_control/idor MEDIUM

We are able to enumerate a field in a check without any limits.


Remediations

Generic

Change the argument to an argument that cannot be enumerated.


Check configuration

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

Options:

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

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

# Introspection

Identifier Severity
introspection/typing_inconsistency MEDIUM
introspection/zombie_objects LOW
introspection/unhandled_queries INFO
introspection/undefined_objects INFO
introspection/typing_inconsistency_interceptor INFO
introspection/imputable_json_scalars INFO

Typing inconsistency

Identifier Severity
introspection/typing_inconsistency MEDIUM

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


Remediations

Generic

Don't resolve queries with a wrong argument type.

Apollo

Apollo disallows 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 not appear if you are using Apollo.

Awsappsync

AWS AppSync disallows 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)


Check configuration

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

Options:

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


Zombie objects

Identifier Severity
introspection/zombie_objects LOW

Zombie objects are objects that are not accessible from any query, mutation or subscription but declared in your GraphQL Schema. Most of the time, zombie objects reveal legacy or unused part of your codebase. Because they are maintained nor patched, they are a privileged vector of attack and represent a 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, else make them accessible from at least one query, mutation or subscription.

Reference:

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


Unhandled Queries

Identifier Severity
introspection/unhandled_queries INFO

Some queries are found in the introspection but have no handle implemented.


Remediations

Generic

Either remove the queries or create a handler for them.


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 objects. They might represent a security flow due to their non-structured nature.


Remediations

Generic

Ensure strong typing in your GraphQL objects.


Typing inconsistency (interceptor)

Identifier Severity
introspection/typing_inconsistency_interceptor INFO

This 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

Identifier Severity
introspection/imputable_json_scalars INFO

Imputable JSON scalars is a arbitrary GraphQL scalar type that allows you to return JSON objects from your GraphQL Schema. It is a bad practice and may represent an unhandled data leak risk on your application.


Remediations

Generic

When possible, prefer using typed input objects.

# Complexity

Identifier Severity
complexity/pagination_limit MEDIUM
complexity/large_json MEDIUM
complexity/field_limit MEDIUM
complexity/depth_limit MEDIUM
complexity/width_limit LOW
complexity/cyclic_query LOW
complexity/character_limit_interceptor LOW
complexity/batch_limit LOW
complexity/alias_limit LOW

Pagination Limit

Identifier Severity
complexity/pagination_limit MEDIUM

A check that insures that an attacker cannot DOS by quering all the nodes in a connection


Remediations

Generic

limit pagination variables


Check configuration

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

Options:

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


Large JSON

Identifier Severity
complexity/large_json MEDIUM

Inputting as an argument a JSON of a very large size


Remediations

Generic

Restrict the size of json that is inputted

Reference:

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


Check configuration

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

Options:

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


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 using a library for your specific engine.

Apollo

You can use a module to compute the complexity of each query and set a threshold on this complexity so that too broad requests are canceled.

To do so, you can either go with the simple to use graphql-validation-complexity (opens new window) module, which doesn't need you to modify your schema.

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

const ComplexityLimitRule = createComplexityLimitRule(1000);

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

Or you can use the more configurable graphql-cost-analysis (opens new window) that lets you manually configure the cost of each field/type of your schema.

This is more realistic as a complexity estimator as fields may not be equal in terms of complexity.

To get more information about complexity estimation, 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, you can 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

Limiting the number of fetched fields can be achieved through Response Limiting, here (opens new window) is a guide to its implementation.

Reference:

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


Check configuration

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

Options:

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


Depth limit

Identifier Severity
complexity/depth_limit MEDIUM

Clients may craft requests as deep as they want.

Since GraphQL schemas are often cyclic graphs, this means a client could craft a query like this one:

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

This could lead to potential DoS attacks or information leakage.


Remediations

Generic

What if we could prevent clients from abusing query depth like this? Knowing your schema might give you an idea of how deep a legitimate query can go. You can implement a Maximum Query Depth limit. 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, we get 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 GraphQL Armor (opens new window) middleware.

Otherwise, you limit the depth of queries with the very light graphql-depth-limit (opens new window) library.

Check how deep you expect legitimate queries to 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, there is no way in AppSync to configure a query depth limit out-of-the-box.

However, we can implement this depth limit using Velocity Template Language VTL in our 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 placed inside of a 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, you can 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 GraphQL Armor (opens new window) middleware.

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

Hasura

Hasura allo To do so:

  • 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)


Check configuration

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

Options:

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


Width limit

Identifier Severity
complexity/width_limit LOW

In GraphQL, the maximum width of a query is defined as the maximum number of subfields queried from one field.

When allowing queries with a large width, clients using GraphQL may craft a complex query that could lead to potential DoS attacks or information leakage.


Remediations

Generic

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

Apollo

You can use a module to compute the complexity of each query and set a threshold on this complexity so that too broad requests are canceled.

To do so, you can either go with the simple to use graphql-validation-complexity (opens new window) module, which doesn't need you to modify your schema.

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

const ComplexityLimitRule = createComplexityLimitRule(1000);

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

Or you can use the more configurable graphql-cost-analysis (opens new window) that lets you manually configure the cost of each field/type of your schema.

This is more realistic as a complexity estimator as fields may not be equal in terms of complexity.

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

Graphene

With graphene-django, you can 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)


Check configuration

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

Options:

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


Cyclic query

Identifier Severity
complexity/cyclic_query LOW

GraphQL allows developers to nest queries and objects. Attackers can use this ability maliciously 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

Being able to fetch a cyclic query can be normal behavior of your GraphQL application, but you must ensure that some security measures control this cyclic query:

  • Timeouts: restrict the time a query is permitted to run.
  • Maximum query depth: limit the depth of allowed queries, which may prevent too deep queries from abusing resources.
  • Set 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

Being able to fetch a cyclic query can be normal behavior of your GraphQL application, but you must ensure that some security measures control this cyclic query:

Graphene

With graphene-django, you can 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)

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)


Check configuration

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

Options:

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


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)


Check configuration

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

Options:

threshold : Maximum number of batched documents allowed to be sent


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 evade many security measures, including rate limiting.

Example query :

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

Example response :

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

Remediations

Generic

Limit queries aliasing in your GraphQL Engine to mitigate aliasing-based attacks.

Apollo

You should not disable aliasing. Instead, put a limit on request complexity.

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

Otherwise, you can use a module to compute the complexity of each query and set a threshold on this complexity so that too broad requests are canceled.

To do so, you can either go with the simple to use graphql-validation-complexity (opens new window) module, which doesn't need you to modify your schema.

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

const ComplexityLimitRule = createComplexityLimitRule(1000);

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

Or you can use the more configurable graphql-cost-analysis (opens new window) that lets you manually configure the cost of each field/type of your schema.

This is more realistic as a complexity estimator as fields may not be equal in terms of complexity.

To get more information about complexity estimation, 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)