What is GraphQL?
GraphQL is a strongly-typed query language that allows clients to request precisely the data they need from an API. It replaces the typical collections of REST endpoints with a single endpoint that accepts queries.
A GraphQL query is used to retrieve data from a GraphQL schema. The schema defines the structure of an API and the relationships between different entities. It specifies the types of data that can be queried as well as the operations for querying and manipulating that data.
Some key advantages of GraphQL include:
- Flexibility – Clients can request only the data they need, avoiding over and under fetching issues common with REST.
- Type safety – The GraphQL schema defines types for API data, enabling early detection of errors.
- Hierarchical – GraphQL structures APIs as graphs of data, mirroring how apps use data.
- Efficient – Clients can reduce API requests by fetching more data in a single call.
- Developer experience – GraphQL APIs are self-documenting and easy for developers to consume.
- Evolvable – GraphQL schemas can be added to without impacting existing queries.
Common GraphQL Vulnerabilities
While GraphQL introduces many benefits, it also comes with its own unique security challenges. Some common GraphQL vulnerabilities include:
- Broken Authentication and Access Controls
- Server Side Request Forgery (SSRF)
- Denial of Service
- Introspection Query Information Leaks
- Injection Attacks
- Rate Limit Bypass
We will cover each of these common vulnerabilities in more detail below.
Broken Authentication and Access Controls
Proper authentication and authorization needs to be implemented when designing a GraphQL API, just like with a REST API. Some common issues include:
- Failing to protect resolvers: Resolvers in GraphQL fulfill a similar role to controllers in a REST API. They contain the application logic and handle fetching data from databases and other back end systems. Resolvers need proper access controls to prevent data exposure.
- Non-protected schemas: The GraphQL schema defines what queries can be executed against the API. It needs proper access controls to prevent anonymous users from querying sensitive data types and fields.
- Leaked authorization rules: The GraphQL schema type system specifies what types a user can query and mutate. This inherently leaks some authorization rules that need to be protected.
- Overly permissive queries: GraphQL queries allow filtering of fields within a type. Access controls need to be applied at the field level to prevent data exposure.
These issues can allow attackers to access unauthorized data or perform privileged actions. Proper authorization should be applied in GraphQL resolvers, schemas, and query analyzers.
Server Side Request Forgery (SSRF)
GraphQL APIs often need to interact with internal systems and external services to gather data to fulfill queries. For example, a GraphQL API may call out to internal databases or microservices. Improperly configured resolvers can lead to server-side request forgery (SSRF) vulnerabilities.
Attackers can potentially abuse insecure resolver logic to interact with internal systems or external sites like AWS metadata services. This can lead to unauthorized data access or denial of service.
Extra caution needs to be taken when allowing user input for fields like URLs or IPs that resolvers use to call out to other systems. Whitelists should be used to restrict allowed destinations.
Denial of Service
The flexibility of GraphQL queries can enable denial of service attacks in a few different ways:
- Overly complex queries: GraphQL APIs can potentially accept complex nested queries. These queries can overload backend systems.
- Resource intensive queries: Attackers can craft queries that target expensive database operations or trigger complex computations in resolvers.
- Bulk requests: GraphQL allows multiple queries in a single request. Attackers can use this to overwhelm servers.
Limits should be imposed on queries to prevent denial of service attacks:
- Query depth limits
- Computational complexity thresholds
- Rate limiting.
Backend systems also need proper resource management to gracefully handle spikes in traffic volume.
Introspection Query Information Leaks
The GraphQL introspection system allows querying an API for information about its schema and the available types, fields, arguments, and more. This is intended to enable developers to understand how to properly interact with the API.
However, introspection queries can leak sensitive information in production environments like:
- Structure of the backend server
- Internal architecture details
- Comments with potential secrets or sensitive information
- Hidden fields and arguments that attackers could potentially access
Introspection should be disabled in production. If it’s needed for public APIs, sensitive fields should be excluded from introspection results.
Injection Attacks
User-supplied input needs to always be properly sanitized and validated to prevent injection attacks, including GraphQL APIs. Some potential injection attack vectors include:
- SQL injection if queries are built unsafely from user input
- Code injection if exploitable vulnerabilities exist in underlying frameworks and libraries
- GraphQL-specific attacks like stitching attack vectors together via aliases
The strong typing in GraphQL prevents some traditional injection attacks but input still needs proper encoding, sanitization, and validation before being passed to resolvers.
User input should be sanitized as early as possible, like when initially entering the GraphQL server code. Encoding, validation, and sanitization should also occur in resolvers. Context variables can assist in safely passing user input to resolvers.
Exploiting GraphQL APIs
GraphQL is a query language that provides an alternative to REST for building APIs. It offers many benefits, including improved performance, developer experience, and the ability to evolve APIs over time. However, GraphQL also introduces new security considerations compared to REST. In this comprehensive guide, we will explore common GraphQL vulnerabilities, including code examples of exploits and how to properly defend GraphQL APIs against them.
Just like any API that accepts user input, GraphQL endpoints are susceptible to injection attacks if improper input validation and sanitization is performed. Some common GraphQL injection risks include:
Exploiting GraphQL: Code Injection
If GraphQL queries are constructed unsafely from user input, code injection is possible in vulnerable GraphQL servers, frameworks or libraries:
# User input:
query {
books(filter: '); require("child_process").exec("calc.exe"); -- ') {
id
name
}
}
# Constructed query:
{
books(filter: '); require("child_process").exec("calc.exe"); -- ') {
id
name
}
}
This code injection attack leverages the user input to execute arbitrary system commands.
Exploiting GraphQL: SQL Injection
If the GraphQL API interacts with a SQL database, SQL injection is possible via unsafe construction of queries:
# User input:
query {
users(filter: "' OR 1=1 --") {
id
name
}
}
# Constructed query:
SELECT id, name
FROM users
WHERE filter = '' OR 1=1 --'
The user input injects a tautology to return all users.
To prevent injection attacks:
- Use prepared statements and query builders
- Validate and sanitize all user input
- Implement a whitelist for special fields like filter
- Run restrictive CORS policies on GraphQL endpoints
GraphQL Broken Authentication and Authorization
Proper authentication and authorization needs to be implemented to secure GraphQL APIs. Some common vulnerabilities include:
Non-Protected Resolvers
Resolvers act similar to controllers in REST APIs. They execute backend logic and return data. Permissions need to be checked in resolvers:
// Resolver without auth check
const getUsers = () => {
return User.find();
}
// Secure resolver
const getUsers = (parent, args, context) => {
if(!context.isAuthenticated) {
throw new Error('Not authenticated');
}
return User.find();
}
Leaked Authorization Rules
The GraphQL schema type system inherently reveals some auth rules. For example, the visibility of types and fields exposes if the current user can query them or not.
Overly Permissive Queries
Specific fields within a GraphQL type can be requested. Permissions should be defined at the field level:
type User {
id: ID
name: String
email: String # Should be non-nullable for authorized users
}
type Query {
user(id: ID): User
}
To properly secure authentication and authorization:
- Use JSON Web Tokens or sessions to manage authentication
- Implement role-based access control
- Limit introspection queries
- Protect resolvers with granular auth checks
- Add non-null assertions to fields in the GraphQL schema for authorized users
GraphQL Example Denial of Service
The flexibility of GraphQL enables denial of service attacks through:
- Complex nested queries
- Queries that target expensive database operations
- Resource intensive computations in resolvers
- Bulk operations via aliases
Implement query complexity limits to prevent DoS attacks:
const { ApolloServer } = require('apollo-server');
const server = new ApolloServer({
schema,
validationRules: [
queryComplexity({
// Set max query complexity
maximumComplexity: 1000
})
]
});
Additional strategies include:
- Restricting max query depth
- Limiting number of aliases
- Query cost analysis
- Limiting total query execution time
Make sure backend systems like databases can handle traffic spikes gracefully.
Server Side Request Forgery (SSRF)
If GraphQL resolvers interact with internal or external services, SSRF is possible:
const getRepoDetails = async (repoName) => {
const url = `https://api.github.com/repos/${repoName}`;
const response = await axios.get(url);
return response.data;
};
If repoName comes from user input, an attacker could force requests to internal services.
To prevent SSRF:
- Validate and sanitize user input, especially URLs/IPs
- Implement a whitelist for third party services that can be called
- Limit ports/protocols that can be accessed
Information Leakage via Introspection
GraphQL introspection provides details about the schema, including types, fields, arguments, and descriptions.
Introspection should be disabled in production. If required, sensitive fields should be excluded.
const typeDefs = gql`
type Query {
users: [User!]!
}
type User {
id: ID!
name: String!
# Exclude email from introspection
email: String!
}
`;
const server = new ApolloServer({
schema,
introspection: true, // Disable for production
// Exclude email from introspection
extensions: ({ context }) => {
if(context.excludeIntrospection) {
return {
redact: ['User.email']
}
}
}
});
The redact extension hides sensitive fields.
GraphQL provides a flexible alternative to REST APIs. However, unique security considerations need to be taken into account. Common vulnerabilities related to injection, authentication, denial of service and information disclosure can put API consumers and backend systems at risk. By properly implementing authentication, authorization, input validation, query complexity limits, and disabling introspection, companies can securely leverage GraphQL to provide next-generation API capabilities.
Rate Limit Bypass
APIs typically employ rate limiting to prevent brute force attacks or denial of service. In GraphQL, clients can bypass standard rate limits by spreading operations across aliases in a single request.
For example, normally a rate limit may allow 10 requests per minute. But a single GraphQL request could perform 100 operations or more via aliasing.
Strategies to prevent rate limit bypass include:
- Per-operation limits
- Restricting the number of aliases
- Complexity analysis of incoming queries
- Query depth limits
Properly Securing GraphQL APIs
Here are some best practices to follow when securing GraphQL APIs:
Authentication and Authorization
- Implement proper authentication like JWTs to identify API consumers.
- Leverage role-based access control to restrict permissions.
- Enforce authorization in resolvers, schemas, and query analyzers.
- Restrict Introspection in production environments.
Input Handling
- Sanitize and encode user input as early as possible.
- Validate user input meets requirements.
- Use GraphQL context for passing input to resolvers.
- Set whitelists for special fields like URLs and IPs.
Query Complexity Limits
- Set maximum query depth allowed.
- Restrict number of fields and aliases per request.
- Implement computational complexity thresholds.
- Enable automatic query cost analysis.
Rate Limiting
- Enforce rate limits per operation rather than per request.
- Restrict number of unique aliases.
- Set maximum bytes per request.
Logging and Monitoring
- Log key metrics like latency, errors, and traffic.
- Use tracing to monitorresolver execution and performance.
- Set alerts for unexpected errors and traffic spikes.
- Perform regular security testing and audits.
Additional Security Tips
- Follow the principle of least privilege in schema design.
- Disable GraphQL playground in production.
- Validate content types on requests.
- Implement CSRF tokens.
- Enable CORS if needed.
- Mask errors to not expose internals.
- Follow security best practices for underlying frameworks.
Conclusion
GraphQL provides a flexible and efficient alternative to REST APIs. However, unique security considerations need to be taken into account when designing, implementing, and securing GraphQL APIs. Common vulnerabilities like broken authentication/authorization, injection attacks, denial of service, and information leaks can put API consumers and backend systems at risk.
By following security best practices around authentication, access controls, query complexity management, input validation, and rate limiting, companies can securely take advantage of the benefits of GraphQL APIs. Proper tooling and processes for monitoring, logging, and testing also need to be implemented. With proper precautions taken by development teams, GraphQL can be safely leveraged to provide next-generation API capabilities.
Ready to see for yourself?
Test drives all platform features for yourself. No commitment and No credit card!