GraphQL-Specific Vulnerabilities
GraphQL-Specific Vulnerabilities
GraphQL's flexible query language introduces unique SQL injection risks:
// Vulnerable GraphQL resolver
const resolvers = {
Query: {
// Dangerous: Dynamic field selection based on GraphQL query
users: async (parent, args, context) => {
const { where, orderBy } = args;
// Building SQL from GraphQL input - VULNERABLE!
let sql = 'SELECT * FROM users';
if (where) {
const conditions = Object.entries(where)
.map(([field, value]) => `${field} = '${value}'`)
.join(' AND ');
sql += ` WHERE ${conditions}`;
}
if (orderBy) {
sql += ` ORDER BY ${orderBy.field} ${orderBy.direction}`;
}
return await context.db.query(sql);
}
}
};
// Secure GraphQL implementation with proper validation
import { GraphQLScalarType } from 'graphql';
import { Kind } from 'graphql/language';
// Custom scalar for safe SQL identifiers
const SafeSQLIdentifier = new GraphQLScalarType({
name: 'SafeSQLIdentifier',
serialize: value => value,
parseValue: value => {
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value)) {
throw new Error('Invalid SQL identifier');
}
return value;
},
parseLiteral: ast => {
if (ast.kind !== Kind.STRING) {
throw new Error('SQL identifier must be a string');
}
return ast.value;
}
});
const secureResolvers = {
Query: {
users: async (parent, args, context) => {
const { where, orderBy, limit = 100 } = args;
// Use query builder with parameterization
const query = context.db('users').select('*');
// Safe where clause construction
if (where) {
const allowedFields = ['id', 'username', 'email', 'status'];
Object.entries(where).forEach(([field, value]) => {
if (allowedFields.includes(field)) {
if (value.contains) {
query.where(field, 'like', `%${value.contains}%`);
} else if (value.equals) {
query.where(field, '=', value.equals);
} else if (value.in) {
query.whereIn(field, value.in);
}
}
});
}
// Safe ordering
if (orderBy && ['username', 'created_at'].includes(orderBy.field)) {
query.orderBy(orderBy.field, orderBy.direction || 'asc');
}
// Safe pagination
query.limit(Math.min(limit, 1000));
return await query;
}
}
};