GraphQL Non-JSON Queries over GET: Potential CSRF Vulnerability
Description
This GraphQL endpoint accepts queries via HTTP GET requests with non-JSON content types (such as query parameters or form data). Unlike JSON POST requests which browsers restrict from cross-origin forms, GET requests and non-JSON POST requests can be triggered through simple HTML forms or image tags, making them vulnerable to Cross-Site Request Forgery (CSRF) attacks. An attacker can craft malicious web pages that cause authenticated users' browsers to unknowingly execute GraphQL mutations or queries against your application.
Remediation
Configure your GraphQL server to only accept queries via POST requests with JSON content type (application/json), and reject GET requests entirely. Additionally, implement CSRF protection tokens for state-changing operations. Here are implementation examples for common frameworks:
Express.js with express-graphql:
const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const app = express();
// CSRF protection middleware - MUST come before GraphQL handler
app.use('/graphql', (req, res, next) => {
// Reject GET requests
if (req.method === 'GET') {
return res.status(405).json({ error: 'GET requests not allowed' });
}
// Verify Content-Type is application/json
if (!req.is('application/json')) {
return res.status(415).json({ error: 'Content-Type must be application/json' });
}
next();
});
// GraphQL handler comes after security checks
app.use('/graphql', graphqlHTTP({
schema: mySchema,
graphiql: false // Disable GraphiQL in production
}));Apollo Server:const { ApolloServer } = require('apollo-server-express');
const express = require('express');
const app = express();
const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true, // Enable built-in CSRF prevention
introspection: false, // Disable in production
playground: false // Disable in production
});
await server.start();
server.applyMiddleware({ app });For additional defense-in-depth, implement CSRF tokens using libraries like csurf for Express.js, and verify custom headers (e.g., X-Requested-With) that cannot be set by simple HTML forms.