Expand my Community achievements bar.

Radically easy to access on brand approved content for distribution and omnichannel performant delivery. AEM Assets Content Hub and Dynamic Media with OpenAPI capabilities is now GA.

Tuesday Tech Bytes - AEM - Week 02 - Understanding and Mitigating GraphQL Introspection Vulnerabilities

Avatar

Community Advisor

7/16/24

ShaileshBassi_6-1721145147285.png

 

Introduction

 

Before we start describing Introspection & GraphQL, it is important to understand the term Schema.

Schema: It is a contract between the server and the client. It specifies how the client can request the data based on the capabilities of the API.

GraphQL: GraphQL query is an API for delivery of the content in form of JSON, specially used in the headless architecture. With GraphQL, multiple query results can be fetched using a single API call which reduces the number of the calls required to render a page in the ecosystem.

Introspection: It is feature of the GraphQL Specification that allows clients to get the information about the GraphQL API’s schema at the runtime, which can be utilized for the various purposes, such as generating documentation, performing validations and many other tasks.

Key aspects of GraphQL introspection include:

__schema and __type queries: These are built-in GraphQL queries used for introspection.

  • __schema: Returns the schema representation.
  • __type: Returns details about specific types within the schema.

Unauthenticated GraphQL Introspection is the ability where anyone gets access to the GraphQL introspection functionalities without any restrictions, which means anyone will be able to retrieve the APIs schema, and its types, fields and relationships. By default, almost all the GraphQL instances come with introspection enabled which always anybody to see the layout of the entire database.

For doing it, one needs to simply send the POST request to the application system with certain parameters.

 

Problem Statement

 

Introspection aids a potential attacker in enumerating key pieces of information about the overall GraphQL system. For example, an attacker could get all the query names, variable names, etc. that makes it easier to construct and unexpectedly call query, mutation, or subscription calls. Example of such queries

Getting the list of the types:

ShaileshBassi_0-1721150736338.png

In the above result, all those preceding with double underscore, indicates that they are part of the introspection system.

Getting the fields for the specific schema:

ShaileshBassi_1-1721150777293.png
 

Solution

 

The approach here is the same as in introspection – disable GraphQL schema exploration in public ally accessible environments unless necessary.

There is no OOTB mechanism to disable introspection query or _schema query to GraphQL endpoints.

There are two alternatives for solving this problem.

Option 1: The first approach to avoid the potential attacker to enter the system would be to fully rely on Persisted Queries and disable the GraphQL endpoints at the dispatcher level.

/0106 { /type "deny" /method '(POST|OPTIONS)' /url "/content/_cq_graphql/test/endpoint.json" }

or

/0106 { /type "deny" /method '(POST|OPTIONS)' /url "/content/_cq_graphql/*/endpoint.json" }

This way either your specific endpoint or all the endpoints could be blocked.

Option 2: The queries could be disabled by introduction custom javax.servlet.Filter

(service = javax.servlet.Filter.class, property = {
        "sling.filter.pattern" + "=/content/cq:graphql/.*/endpoint",
        "sling.filter.methods" + "=POST",
        "sling.filter.extensions" + "=json",
        "sling.filter.scope" + "=REQUEST"
})

public class IntrospectionQueryFilter implements Filter {
    
    public void init(FilterConfig filterConfig) {
        // empty
    }

    
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws ServletException, IOException {

        if (!isAuthor() && servletRequest instanceof HttpServletRequest &&   
        servletResponse instanceof HttpServletResponse) {
              final HttpServletRequest httpServletRequest = 
                   (HttpServletRequest)servletRequest;
              final HttpServletResponse httpServletResponse = 
                   (HttpServletResponse) servletResponse;
                  
              HttpServletRequestWrapper requestWrapper = 
                    new HttpServletRequestWrapper(httpServletRequest);

              String graphQLBody = IOUtils.toString(requestWrapper.getInputStream(), 
                    StandardCharsets.UTF_8);

              if (StringUtils.isNotBlank(graphQLBody) && 
                        graphQLBody.contains("__schema")) {
                 httpServletResponse.sendError(SlingHttpServletResponse.SC_BAD_REQUEST);
	             return;
              }
           }

     }

   private boolean isAuthor() {
        //ToDo – Add the logic to check the instance type, by run mode or configuration.
        return true;
    }