Источник: https://github.com/AnaktaCTF/CTF/blob/main — WEB/GraphQL.md

GraphQL – это язык запросов API, который позволяет клиентам гибко запрашивать только нужные данные. В отличие от REST API, GraphQL позволяет объединять несколько запросов в один, что делает его мощным инструментом. Однако его гибкость и универсальность создают серьезные проблемы безопасности, если API не настроен должным образом. В этой статье разобраны основные векторы атак на GraphQL, методы защиты и инструменты тестирования безопасности.

Архитектура GraphQL и отличие от REST

GraphQL и REST – два подхода к построению API, но они отличаются принципами работы.

REST (Representational State Transfer) строится на основе множества эндпоинтов, каждый из которых отвечает за определенный ресурс (например, /players, /teams, /matches). Клиент делает несколько запросов к разным эндпоинтам, чтобы получить нужные данные. Это может привести к:

  • избыточной передаче данных (over-fetching), когда клиент получает больше информации, чем необходимо;

  • недостаточной передаче данных (under-fetching), когда клиенту приходится делать несколько запросов, чтобы собрать всю необходимую информацию.

GraphQL использует единый эндпоинт (/graphql), который принимает запросы на получение только необходимых данных. Клиент может запрашивать конкретные поля, избегая проблем over-fetching и under-fetching. Однако такой подход создает новые риски:

  • возможность утечки скрытых данных, если схема API не настроена должным образом;

  • уязвимость к сложным рекурсивным запросам, которые могут перегрузить сервер.

Основные концепции GraphQL

Схема: определяет типы данных, доступные в API, и отношения между ними. Схема служит контрактом между клиентом и сервером, описывая, какие запросы можно выполнять и какие данные можно получать.

Типы: основные строительные блоки схемы. Включают скалярные типы (например, Int, String, Boolean) и объектные типы, которые могут содержать несколько полей различных типов.​

Запросы: используются для чтения данных. Клиент определяет структуру запроса, и сервер возвращает данные в точно таком же формате.

Мутации: используются для изменения данных (создание, обновление, удаление).

Подписки: позволяют клиенту получать обновления в реальном времени при изменении данных на сервере.

Пример схемы GraphQL:

type Author {
 id: ID!
 name: String!
 books: [Book]
}

type Book {
 id: ID!
 title: String!
 author: Author
}

type Query {
 authors: [Author]
 books: [Book]
 book(id: ID!): Book
}

type Mutation {
 addBook(title: String!, authorId: ID!): Book
}

В данном примере определяются типы Author и Book, а также запросы для получения списка авторов, списка книг и конкретной книги по ID. Мутация addBook позволяет добавить новую книгу с указанным названием и идентификатором автора.

Основные уязвимости в GraphQL

  1. Инспекция схемы (Introspection Attack)

    GraphQL поддерживает встроенную возможность introspection (самоанализ), которая позволяет клиенту запрашивать структуру API, включая все доступные типы, запросы и мутации. Хоть это и удобно, но может стать уязвимостью, если не отключено в продакшене. Злоумышленник, получивший доступ к схеме, может использовать её для поиска скрытых API и уязвимых эндпоинтов.

    Запрос introspection в GraphQL Playground:

    {
      __schema {
        types {
          name
          fields {
            name
          }
        }
      }
    }
    

    Если introspection включен, сервер вернет структуру API, позволяя атакующему анализировать потенциальные цели.

  2. Инъекции в GraphQL (GraphQL Injection)

    GraphQL-инъекции похожи на традиционные SQL-инъекции. Они происходят, когда входные данные пользователя обрабатываются без проверки и используются для выполнения внутренних запросов.

    Пример GraphQL-инъекции:

    {
      user(id: "1 OR 1=1") {
        username
      }
    }
    

    Если API уязвимо, оно может вернуть список всех пользователей вместо одного. Что может привести к утечке конфиденциальной информации.

    Другой вариант атаки – внедрение в мутации:

    mutation {
      updateUser(id: "1", username: "hacker") {
        username
      }
    }
    

    Если сервер не проверяет права доступа, атакующий может изменить данные других пользователей.

  3. DDoS-атаки через сложные запросы

    GraphQL позволяет создавать вложенные запросы, что может быть использовано злоумышленниками для перегрузки сервера. Если сервер не ограничивает глубину рекурсивных запросов, злоумышленник может создать бесконечную рекурсию и вызвать отказ в обслуживании.

    Пример атаки:

    {
      user {
        friends {
          friends {
            friends {
              username
            }
          }
        }
      }
    }
    

    Если сервер не настроен на ограничение глубины вложенных запросов, он будет обрабатывать их до полного исчерпания ресурсов.

Методы и способы защиты GraphQL API

  • Ограничение доступа к запросам интроспекции: интроспекция позволяет клиентам получать информацию о схеме API. В производственной среде рекомендуется отключать интроспекцию, чтобы злоумышленники не могли получить информацию о структуре API.

  • Валидация входных данных: необходимо проверять и ограничивать входные данные, чтобы предотвратить инъекции и другие атаки. Например, можно использовать директивы для валидации данных на уровне схемы.

  • Ограничение глубины и сложности запросов: чтобы предотвратить атаки типа "отказ в обслуживании" (DoS) с помощью глубоких или сложных рекурсивных запросов, следует устанавливать ограничения на максимальную глубину и сложность запросов.

  • Ограничение частоты запросов (Rate Limiting): для предотвращения перебора уязвимых мутаций или брутфорс-атак рекомендуется ограничивать количество запросов от одного клиента за определенный период времени. Это можно реализовать с помощью специальных плагинов или middleware.

  • Использование безопасного транспортного протокола: при использовании HTTP для запросов и мутаций необходимо применять HTTPS для шифрования данных и защиты их от перехвата.

  • Аутентификация и авторизация: необходимо внедрять надежные механизмы аутентификации и авторизации, чтобы гарантировать, что только авторизованные пользователи могут выполнять определенные операции или получать доступ к определенным данным.

Пример реализации ограничения глубины запросов на Node.js с использованием graphql-depth-limit:

const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const { makeExecutableSchema } = require('@graphql-tools/schema');
const depthLimit = require('graphql-depth-limit');

const typeDefs = `
 type Query {
   hello: String
 }
`;

const resolvers = {
 Query: {
   hello: () => 'Hello world!',
 },
};

const schema = makeExecutableSchema({ typeDefs, resolvers });

const app = express();

app.use(
 '/graphql',
 graphqlHTTP({
   schema: schema,
   validationRules: [depthLimit(5)], // Ограничение глубины запросов до 5
 })
);

app.listen(4000, () => {
 console.log('Server is running on http://localhost:4000/graphql');
});

В данном примере используется middleware graphql-depth-limit для ограничения глубины запросов до 5 уровней, что помогает предотвратить атаки с использованием чрезмерно глубоких запросов.​

Реализация перечисленных мер безопасности позволит защитить GraphQL API от распространенных уязвимостей и обеспечить стабильную и безопасную работу сервиса.

Инструменты для тестирования безопасности GraphQL

GraphQLmap – автоматизированный инструмент для тестирования GraphQL API на уязвимости.

GraphQL Voyager – инструмент для визуализации схем API и поиска потенциальных уязвимостей.

InQL (Burp Suite Plugin) – расширение Burp Suite для анализа GraphQL API и обнаружения скрытых эндпоинтов.


Заключение

GraphQL – мощный инструмент для построения API, обеспечивающий гибкость в запросах данных. Однако его особенности могут привести к различным уязвимостям, таким как утечки данных, инъекции и перегрузка сервера сложными запросами. Настройка ограничений на доступ к данным, глубину вложенности запросов и контроль аутентификации помогают минимизировать риски. Понимание возможных атак и использование инструментов тестирования безопасности позволяют выявлять и устранять уязвимости, обеспечивая стабильность и защиту API.