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 install
andnpm start
. - Run the exploit in
exploit/index.ts
withnpm 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
,firstName
andlastName
fields, 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
.