Inyecciones GraphQL: Explicación y PoC
Las inyecciones GraphQL son vulnerabilidades de seguridad que ocurren cuando un atacante manipula consultas o mutaciones de GraphQL para realizar acciones no autorizadas. GraphQL es un lenguaje de consulta para APIs que permite a los clientes pedir exactamente los datos que necesitan de manera flexible. Esta flexibilidad, sin embargo, puede ser explotada si no se implementan las medidas de seguridad adecuadas.
Funcionamiento de las inyecciones GraphQL
Las inyecciones GraphQL funcionan de manera similar a las inyecciones SQL. Un atacante puede insertar código malicioso en una consulta o mutación para alterar su comportamiento. Esto puede llevar a la exposición de datos sensibles, modificación de datos o ejecución de comandos no autorizados en el servidor.
Ejemplo básico de una inyección GraphQL
Supongamos que tenemos una consulta GraphQL que obtiene información de un usuario basado en un ID:
query getUser($id: ID!) {user(id: $id) {idname}}
Si la validación de entrada no es adecuada, un atacante podría inyectar un valor malicioso en el ID:
{
«id»: «1 OR 1=1»}
Esto podría alterar la consulta en el backend para que devuelva todos los usuarios en lugar de uno solo, similar a cómo funcionan las inyecciones SQL.
GraphQL permite a los clientes especificar exactamente qué datos necesitan a través de consultas y mutaciones. Esta capacidad de personalizar las consultas es poderosa, pero también puede ser peligrosa si no se manejan adecuadamente las entradas del usuario.
Ejemplo de consulta GraphQL estándar:
query getUser($id: ID!) {user(id: $id) {idname}}
En esta consulta, el cliente solicita los campos id, name y email del usuario con el id especificado.
Tipos de Inyección en GraphQL
• Inyección de argumentos: Si los argumentos no se validan correctamente, un atacante puede manipular los parámetros para alterar la consulta.
Consulta legítima:
query {user(id: «1») {idname}}
Consulta manipulada:
query {
user(id: «1 OR 1=1») {idname}}
• Inyección de campos: Los atacantes pueden intentar acceder a campos no autorizados al manipular los campos solicitados.
query {user(id: «1») {idname}}
Consulta manipulada:
query {user(id: «1») {idnamepassword}}
Si el backend no restringe el acceso a ciertos campos, el atacante podría obtener datos sensibles como contraseñas.
• Manipulación de mutaciones: Los atacantes pueden alterar mutaciones para cambiar datos en formas no deseadas.
Mutación legítima:
mutation {updateUser(id: «1», input: { name: «John», role: «user» }) {idnamerole}}
Mutación manipulada:
mutation {updateUser(id: «1», input: { name: «attacker», role: «admin» }) {idnamerole}}
Si no se validan adecuadamente los permisos, un atacante podría cambiar su rol a «admin».
Iniciemos sesión con: admin/admin
Tenemos dos obstáculos, solo nos devuelve la primera fila, y no podemos ver el valor de la contraseña
const UserType = new graphql.GraphQLObjectType({name: «User»,fields: {id: { type: graphql.GraphQLID },username: { type: graphql.GraphQLString },isAdmin: { type: graphql.GraphQLBoolean },},});
Medidas de seguridad para prevenir inyecciones GraphQL
Validación y sanitización de entradas:
Utilizar bibliotecas de validación para asegurar que los parámetros sean del tipo y formato esperados.
Ejemplo en JavaScript utilizando validator.js:
const { isUUID } = require(‘validator’);const resolvers = {Query: {user: async (_, { id }) => {if (!isUUID(id, 4)) {throw new Error(‘Invalid ID format’);}return await getUserById(id);}}};
Uso de esquemas de tipos estrictos:
type User {id: ID!name: String!email: String!}type Query {user(id: ID!): User}type Mutation {updateUser(id: ID!, input: UpdateUserInput!): User}input UpdateUserInput {name: Stringrole: String}
Autenticación y autorización:
const server = new ApolloServer({typeDefs,resolvers,context: ({ req }) => {const token = req.headers.authorization || »;const user = getUserFromToken(token);if (!user) throw new AuthenticationError(‘You must be logged in’);return { user };}});
Rate limiting y logging:
const rateLimit = require(‘express-rate-limit’);const apiLimiter = rateLimit({windowMs: 15 * 60 * 1000, // 15 minutosmax: 100 // límite de 100 solicitudes por IP});
app.use(‘/graphql’, apiLimiter);
Uso de herramientas de seguridad:
const helmet = require(‘helmet’);app.use(helmet());
