Validate JSON Inputs
Using JSON as a GraphQL input object can lead to vulnerabilities. This tutorial will show how one can exploit a JSON input object to perform arbitrary SQL queries with Prisma ORM, and how to prevent it.
This lesson does not currently work in the browser for security reasons. You can run it locally with the following steps:
- Clone the repository:
git clone https://github.com/Escape-Technologies/graphql-security-academy.git - Run
cd learn/packages/lessons/json-injection - Install dependencies with
yarn install - Continue from here with
yarn start,yarn exploit, etc.
More details on prisma#17710
The vulnerability
This tutorial contains a GraphQL API that uses Prisma ORM to perform search queries in a SQLite database. It has one single resolver, findUsers, that takes a JSON input object as argument. The JSON input object is used to filter the users in the database.
# GraphQL schema
type Query {
findUsers(where: JSON): [User]
# ^^^^ arbitrary JSON input object
} // GraphQL resolver
Query: {
findUsers: (_, { where }) => prisma.user.findMany({ where }),
// ^^^^^ arbitrary object...
// ...leading to arbitrary queries ^^^^^
} Our database table contains more columns than what the API exposes. Indeed, there is an apiKey used for authentication purposes, and while an attack cannot read it directly, it can leverage Prisma’s API to get it.
# Legitimate query
query {
findUsers(where: { email: { endsWith: "@example.com" } }) {
email
}
}
# Malicious query
query {
findUsers(where: { apiKey: { startsWith: "0" } }) {
email
}
} - Start the API with
npm installandnpm start. - Run the exploit in
exploit/index.tswithnpm run exploit.
How to prevent it
We will use zod to validate the JSON input object. Zod is a TypeScript library that allows to define a schema for a JavaScript object, and to validate it.
Query: {
findUsers: (_, { where }) => {
// Allows equality, contains, startsWith and endsWith filters on columns
const filterSchema = z.union([
z.string(),
z
.object({
contains: z.string(),
startsWith: z.string(),
endsWith: z.string(),
})
.partial(),
]);
// Restrict to firstName, lastName and email
const whereSchema = z
.object({
firstName: filterSchema,
lastName: filterSchema,
email: filterSchema,
})
.partial();
// Validate the input object
const results = whereSchema.safeParse(where);
if (results.success) {
// Use the safe version of the input objects
return prisma.user.findMany({ where: results.data });
} else {
// Return an error in case the input object is invalid
return new GraphQLError('Validation error');
}
},
} - Install zod with
npm install zod. - Use the zod schema that allows searching with
email,firstNameandlastNamefields, but not others.
You can now try to run the exploit again. It should not work anymore since Zod strips all unknown fields like apiKey.