Segurança de GraphQL
Esta aula aborda os principais riscos de segurança em APIs GraphQL, incluindo queries complexas, batching, autorização e exposição indevida de dados. São apresentadas técnicas de mitigação como limitação de profundidade, rate limiting e validação de permissões. Ao final, o aluno será capaz de identificar e proteger uma API GraphQL contra vulnerabilidades comuns.
GraphQL é uma linguagem de consulta e manipulação de dados para APIs que oferece grande flexibilidade ao cliente. No entanto, essa flexibilidade introduz riscos de segurança específicos que não existem em APIs REST tradicionais. Nesta aula, exploraremos quatro áreas críticas de segurança em GraphQL: queries complexas que podem sobrecarregar o servidor, problemas de batching que podem contornar rate limiting, falhas de autorização que expõem dados sensíveis e a exposição indevida de informações através do schema introspection.
Queries complexas
Uma das maiores vantagens do GraphQL é permitir que o cliente solicite exatamente os dados que precisa, mas isso também permite que um atacante construa consultas profundamente aninhadas e com muitos campos, que podem consumir recursos excessivos do servidor. Esse tipo de ataque é conhecido como query complexity attack e pode levar a negação de serviço (DoS).
Para mitigar, é essencial limitar a profundidade máxima das queries, o número de campos e o número de objetos retornados. Ferramentas como graphql-depth-limit e graphql-query-complexity ajudam a calcular e limitar a complexidade. Exemplo de configuração em Node.js:
const depthLimit = require('graphql-depth-limit');
const { createComplexityLimitRule } = require('graphql-query-complexity');
const server = new ApolloServer({
schema,
validationRules: [
depthLimit(10),
createComplexityLimitRule(1000)
]
});
Batching
Batching é uma técnica onde múltiplas requisições são agrupadas em uma única consulta GraphQL usando aliases ou campos com argumentos diferentes. Embora legítimo para reduzir overhead de rede, o batching pode ser usado para contornar limites de taxa (rate limiting) baseados em requisições HTTP. Um atacante pode enviar uma única requisição HTTP contendo centenas de operações, cada uma consumindo recursos.
A solução é implementar rate limiting no nível da operação (query) em vez de apenas no nível HTTP. Bibliotecas como graphql-rate-limit ou graphql-request-rate-limit permitem limitar o número de campos ou a complexidade por operação. Além disso, desabilitar o batching automático quando não necessário é uma boa prática. Exemplo de configuração com Apollo Server:
const { ApolloServer } = require('apollo-server');
const { applyMiddleware } = require('graphql-middleware');
const { rateLimit } = require('graphql-rate-limit');
const schema = applyMiddleware(
makeExecutableSchema({ typeDefs, resolvers }),
rateLimit({ window: '1m', max: 100 })
);
Autorização
Autorização em GraphQL deve ser aplicada em nível de campo ou resolver, e não apenas no endpoint. Muitas implementações falham ao verificar permissões apenas na raiz da query, permitindo que um usuário não autorizado acesse campos aninhados de objetos que não deveria ver.
A abordagem recomendada é usar um middleware que verifique permissões para cada campo solicitado. Por exemplo, no Apollo Server, pode-se usar graphql-shield para definir regras granulares. Exemplo:
const { shield, rule, and, or, not } = require('graphql-shield');
const isAuthenticated = rule()(async (parent, args, ctx) => {
return ctx.user !== null;
});
const permissions = shield({
Query: {
user: isAuthenticated,
},
User: {
email: isAuthenticated,
}
});
Nunca confie apenas na falta de exposição do campo no schema; sempre valide a autorização no resolver.
Exposição indevida
Por padrão, GraphQL expõe o esquema completo através de queries de introspection (como __schema e __type). Isso é útil durante o desenvolvimento, mas em produção pode revelar detalhes da API, incluindo campos e argumentos não documentados, facilitando ataques.
Desabilitar a introspecção em produção é uma prática recomendada. No Apollo Server, pode-se fazer assim:
const server = new ApolloServer({
schema,
introspection: process.env.NODE_ENV !== 'production'
});
Além disso, evite expor mensagens de erro detalhadas que incluam stack traces ou nomes de campos internos. Use um formato de erro genérico e registre detalhes internamente.
Boas práticas
Além das mitigações específicas, adote as seguintes práticas gerais: (1) sempre validar entradas do usuário, (2) usar listas de permissões (allowlist) para operações permitidas em vez de blocklists, (3) implementar logging e monitoramento de consultas anômalas, (4) manter o servidor GraphQL atualizado com as últimas correções de segurança.
Exercícios
- Explique como uma query complexa pode causar negação de serviço e cite duas formas de mitigação.
- Qual a diferença entre rate limiting baseado em HTTP e rate limiting baseado em operação GraphQL? Por que o segundo é necessário?
- Como garantir que um usuário não autorizado não acesse o campo 'email' de um usuário em GraphQL?
- O que é introspecção no GraphQL e por que deve ser desabilitada em produção?
- Cite duas maneiras de evitar exposição indevida de informações em erros GraphQL.