Segurança em GraphQL: introspection e query depth
1. Introdução aos Riscos de Segurança em GraphQL
GraphQL difere fundamentalmente de REST em termos de segurança. Enquanto REST expõe endpoints fixos e previsíveis, GraphQL oferece um único endpoint que aceita queries arbitrárias definidas pelo cliente. Essa flexibilidade poderosa amplia significativamente a superfície de ataque.
Em REST, um atacante está limitado aos endpoints existentes (/users, /posts). Em GraphQL, qualquer campo definido no schema pode ser consultado em qualquer combinação. Isso significa que dados sensíveis inadvertidamente expostos no schema tornam-se alvos fáceis.
Os principais vetores de ataque em GraphQL incluem:
- Introspection: exposição completa do schema da API
- Query Depth: queries profundamente aninhadas que consomem recursos excessivos
- Query Complexity: queries com muitos campos que sobrecarregam o backend
2. Introspection: O Mapa do Tesouro para Atacantes
Introspection é um recurso nativo do GraphQL que permite consultar o schema completo da API. Em desenvolvimento, é essencial para ferramentas como GraphiQL e GraphQL Playground. Em produção, porém, torna-se um mapa detalhado para atacantes.
Considere este schema hipotético:
type Query {
user(id: ID!): User
users: [User]
}
type User {
id: ID!
username: String!
email: String!
passwordHash: String! # Campo sensível!
internalToken: String! # Outro campo sensível
creditCard: CreditCard
}
Um atacante pode usar introspection para descobrir passwordHash e internalToken:
# Query de introspection
query {
__schema {
types {
name
fields {
name
type {
name
}
}
}
}
}
Ferramentas comuns de exploração incluem:
- GraphiQL: interface interativa para explorar schemas
- GraphQL Voyager: visualização gráfica do schema
- Scripts automatizados: ferramentas como
graphql-introspectionque extraem schemas completos
3. Mitigando Introspection em Produção
A mitigação mais direta é desabilitar introspection em produção. Exemplos práticos:
Apollo Server (Node.js):
const server = new ApolloServer({
typeDefs,
resolvers,
introspection: process.env.NODE_ENV !== 'production',
});
GraphQL-Java:
GraphQLSchema schema = GraphQLSchema.newSchema()
.query(queryType)
.build();
GraphQL graphQL = GraphQL.newGraphQL(schema)
.instrumentation(new MaxQueryDepthInstrumentation(5))
.build();
// Desabilitar introspection
GraphQL graphQLNoIntrospection = GraphQL.newGraphQL(schema)
.instrumentation(new DisableIntrospection())
.build();
Estratégias alternativas:
- Whitelist de IPs: permitir introspection apenas para IPs internos
- Autenticação condicional: exigir token específico para acesso ao
__schema - Ambientes segregados: manter introspection ativo apenas em staging/desenvolvimento
4. Query Depth: Ataques de Complexidade e Negação de Serviço
Query depth refere-se ao nível de aninhamento de uma query. Queries profundas podem causar estouro de recursos no backend, especialmente quando envolvem joins e resoluções de dados complexas.
Exemplo de ataque com query profundamente aninhada:
query DeepQuery {
user(id: 1) {
posts {
comments {
author {
posts {
comments {
author {
posts {
comments {
author {
username
}
}
}
}
}
}
}
}
}
}
}
Esta query com 6 níveis de profundidade pode gerar milhares de consultas ao banco de dados. Um atacante pode disparar centenas dessas queries simultaneamente, causando um ataque de negação de serviço (DoS).
O custo de processamento cresce exponencialmente com cada nível adicional de aninhamento, especialmente em schemas com relações N+1.
5. Implementando Limites de Query Depth
Exemplo com graphql-depth-limit no Node.js:
const depthLimit = require('graphql-depth-limit');
const { ApolloServer } = require('apollo-server');
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [depthLimit(5)], // Máximo de 5 níveis
});
Exemplo com Python (Ariadne):
from ariadne import graphql_sync
from ariadne.validation import depth_limit_validator
def create_depth_middleware(max_depth=5):
async def middleware(request, schema, query):
errors = depth_limit_validator(schema, max_depth)(query)
if errors:
return {
"errors": [{"message": f"Query exceeds maximum depth of {max_depth}"}]
}
return None
return middleware
Tratamento de erros:
# Resposta para cliente legítimo
{
"errors": [
{
"message": "Query depth (8) exceeds maximum allowed depth (5)",
"locations": [{"line": 2, "column": 3}]
}
]
}
6. Além do Depth: Query Complexity e Persisted Queries
Query Complexity permite limitar por peso de campos, não apenas por profundidade:
const { createComplexityLimitRule } = require('graphql-validation-complexity');
const server = new ApolloServer({
validationRules: [
createComplexityLimitRule(1000, {
onCost: (cost) => console.log(`Query cost: ${cost}`),
}),
],
});
Campos mais custosos (como users que retorna listas grandes) recebem pesos maiores:
type Query {
user(id: ID!): User @complexity(value: 1)
users: [User] @complexity(value: 10) # Mais caro
}
Persisted Queries (APQ): queries pré-aprovadas armazenadas no servidor:
// Registrar query persistida
apolloClient.registerPersistedQuery(
'query GetUser { user(id: 1) { name email } }',
'hash123...'
);
// Executar apenas queries persistidas
const server = new ApolloServer({
persistedQueries: true,
cache: new MemcachedCache(),
});
APQ reduz drasticamente a superfície de ataque, pois apenas queries conhecidas são aceitas.
7. Monitoramento e Resposta a Incidentes
Logging de queries rejeitadas:
// Middleware de logging
server.applyMiddleware({
app,
context: async ({ req }) => {
const query = req.body.query;
const depth = calculateDepth(query);
if (depth > 5) {
console.warn(`Query rejeitada por depth excessivo: ${depth} níveis`);
console.warn(`IP: ${req.ip}, Query: ${query.substring(0, 200)}`);
// Alertar equipe de segurança
await sendAlert({
type: 'GRAPHQL_DEPTH_EXCEEDED',
ip: req.ip,
depth: depth,
timestamp: new Date().toISOString()
});
}
}
});
Integração com WAF:
- Configurar regras para bloquear queries com
__schemaem produção - Rate limiting por IP para queries de introspection
- Análise de padrões: picos de queries rejeitadas indicam tentativas de exploração
8. Conclusão e Checklist de Segurança
GraphQL oferece poder e flexibilidade, mas exige cuidados específicos de segurança. As três camadas fundamentais são:
- Desabilitar introspection em produção — remove o mapa da API
- Definir limites de depth e complexity — previne ataques de DoS
- Implementar persisted queries — reduz superfície de ataque
Checklist de deploy seguro para GraphQL:
- [ ] Introspection desabilitado em produção
- [ ] Limite de query depth configurado (recomendado: 5-7 níveis)
- [ ] Limite de query complexity configurado
- [ ] Rate limiting implementado por IP
- [ ] Logging de queries rejeitadas ativo
- [ ] Persisted queries consideradas para APIs críticas
- [ ] Testes de penetração regulares no endpoint GraphQL
A segurança em GraphQL não é opcional — é uma responsabilidade contínua que deve ser integrada ao ciclo de desenvolvimento.
Referências
- GraphQL Security Cheat Sheet - OWASP — Guia oficial da OWASP com as principais práticas de segurança para GraphQL, incluindo introspection e depth limiting
- Securing Apollo Server - Apollo GraphQL — Documentação oficial da Apollo sobre segurança, incluindo configuração de introspection e validação de queries
- graphql-depth-limit - npm — Pacote Node.js para limitar profundidade de queries GraphQL, com exemplos de uso e configuração
- GraphQL Query Complexity Analysis - Shopify Engineering — Artigo técnico da Shopify sobre como implementaram análise de complexidade para prevenir abusos
- Persisted Queries with Apollo Client - Apollo GraphQL — Documentação oficial sobre Automatic Persisted Queries (APQ) e como reduzir superfície de ataque
- GraphQL Security: Common Vulnerabilities and How to Prevent Them - HackerOne — Guia da HackerOne com exemplos reais de vulnerabilidades em GraphQL e técnicas de mitigação
- GraphQL Voyager — Ferramenta de visualização de schemas GraphQL que pode ser usada tanto para desenvolvimento quanto para exploração por atacantes