GraphQL NoSQL Injection Through JSON Types

Written by Pete Corey on Jun 12, 2017.

One year ago today, I wrote an article discussing NoSQL Injection and GraphQL. I praised GraphQL for eradicating the entire possibility of NoSQL Injection.

I claimed that because GraphQL forces you to flesh out the entirety of your schema before you ever write a query, it’s effectively impossible to succumb to the incomplete argument checking that leads to a NoSQL Injection vulnerability.

Put simply, this means that an input object will never have any room for wildcards, or potentially exploitable inputs. Partial checking of GraphQL arguments is impossible!

I was wrong.

NoSQL Injection is entirely possible when using GraphQL, and can creep into your application through the use of “custom scalar types”.

In this article, we’ll walk through how the relatively popular GraphQLJSON scalar type can open the door to NoSQL Injection in applications using MongoDB.

Custom Scalars

In my previous article, I explained that GraphQL requires that you define your entire application’s schema all the way down to its scalar leaves.

These scalars can be grouped and nested within objects, but ultimately every field sent down to the client, or passed in by the user is a field of a known type:

Scalars and Enums form the leaves in request and response trees; the intermediate levels are Object types, which define a set of fields, where each field is another type in the system, allowing the definition of arbitrary type hierarchies.

Normally, these scalars are simple primitives: String, Int, Float, or Boolean. However, sometimes these four primitive types aren’t enough to fully flesh out the input and output schema of a complex web application.

Custom scalar types to the rescue!

Your application can define a custom scalar type, along with the set of functionality required to serialize and deserialize that type into and out of a GraphQL request.

A common example of a custom type is the Date type, which can serialize Javascript Date objects into strings to be returned as part of a GraphQL query, and parse date strings into Javascript Date objects when provided as GraphQL inputs.

Searching with JSON Scalars

This is all well and good. Custom scalars obviously are a powerful tool for building out more advanced GraphQL schemas. Unfortunately, this tool can be abused.

Imagine we’re building a user search page. In our contrived example, the page lets users search for other users based on a variety of fields: username, full name, email address, etc…

Being able to search over multiple fields creates ambiguity, and ambiguity is hard to work with in GraphQL.

To make our lives easier, let’s accept the search criteria as a JSON object using the GraphQLJSON custom scalar type:


type Query {
    users(search: JSON!): [User]
}

Using Apollo and a Meteor-style MongoDB driver, we could write our users resolver like this:


{
    Query: {
        users: (_root, { search }, _context) => {
            return Users.find(search, {
                fields: {
                    username: 1, 
                    fullname: 1, 
                    email: 1
                }
            });
        }
    }
}

Great!

But now we want to paginate the results and allow the user to specify the number of results per page.

We could add skip and limit fields separately to our users query, but that would be too much work. We’ve already seen how well using the JSON type worked, so let’s use that again!


type Query {
    users(search: JSON!, options: JSON!): [User]
}

We’ve extended our users query to accept an options JSON object.


{
    Query: {
        users: (_root, { search, options }, _context) => {
            return Users.find(search, _.extend({
                fields: {
                    _id: 1,
                    username: 1, 
                    fullname: 1, 
                    email: 1
                }
            }, options));
        }
    }
}

And we’ve extended our users resolver to extend the list of fields we return with the skip and limit fields passed up from the client.

Now, for example, our client can make a query to search for users based on their username or their email address:


{
    users(search: "{\"username\": {\"$regex\": \"sue\"}, \"email\": {\"$regex\": \"sue\"}}",
          options: "{\"skip\": 0, \"limit\": 10}") {
        _id
        username
        fullname
        email
    }
}

This might return a few users with users with "sue" as a part of their username or email address.

But there are problems here.

Imagine a curious or potentially malicious user making the following GraphQL query:


{
    users(search: "{\"email\": {\"$gte\": \"\"}}",
          options: "{\"skip\": 0, \"limit\": 10}") {
        _id
        username
        fullname
        email
    }
}

The entire search JSON object is passed directly into the Users.find query. This query will return all users in the collection.

Thankfully, a malicious user would only receive our users’ usernames, full names, and email addresses. Or would they?

The options JSON input could also be maliciously modified:


{
    users(search: "{\"email\": {\"$gte\": \"\"}}",
          options: "{\"fields\": {}}") {
        _id
        username
        fullname
        email
    }
}

By passing in their own fields object, an attacker could overwrite the fields specified by the server. This combination of search and options would return all fields (specified in the GraphQL schema) for all users in the system.

These fields might include sensitive information like their hashed passwords, session tokens, purchase history, etc…

Fixing the Vulnerability

In this case, and in most cases, the solution here is to be explicit about what we expect to receive from the client. Instead of receiving our flexible search and options objects from the client, we’ll instead ask for each field individually:


type Query {
    users(fullname: String,
          username: String,
          email: String,
          skip: Number!,
          limit: Number!): [User]
}

By making the search fields (fullname, username, and email) optional, the querying user can omit and of the fields they don’t wish to search on.

Now we can update our resolver to account for this explicitness:


{
    Query: {
        users: (_root, args, _context) => {
            let search = _.extend({}, args.fullname ? { fullname } : {},
                                      args.username ? { username } : {},
                                      args.email ? { email } : {});
            return Users.find(search, {
                fields: {
                    _id: 1,
                    username: 1, 
                    fullname: 1, 
                    email: 1
                },
                skip: args.skip,
                limit: args.limit
            });
        }
    }
}

If either fullname, username, or email are passed into the query, we’ll add them to our query. We can safely dump this user-provided data into our query because we know it’s a String at this point thanks to GraphQL.

Lastly, we’ll set skip and limit on our MongoDB query to whatever was passed in from the client. We can be confident that our fields can’t possibly be overridden.

Final Thoughts

Custom scalar types, and the JSON scalar type specifically, aren’t all bad. As we discussed, they’re a powerful and important tool for building out your GraphQL schema.

However, when using JSON types, or any other sufficiently expressive custom scalar types, it’s important to remember to make assertions about the type and shape of user-provided data. If you’re assuming that the data passed in through a JSON field is a string, check that it’s a string.

If a more primitive GraphQL type, like a Number fulfills the same functionality requirements as a JSON type, even at the cost of some verbosity, use the primitive type.

Behold the Power of GraphQL

Written by Pete Corey on Jun 5, 2017.

Imagine you’re build out a billing history page. You’ll want to show the current user’s basic account information along with the most recent charges made against their account.

Using Apollo client and React, we can wire up a simple query to pull down the information we need:


export default graphql(gql`
    query {
        user {
            id
            email
            charges {
                id
                amount
                created
            }
        }
    }
`)(Account);

Question: Where are the user’s charges being pull from? Our application’s data store? Some third party service?

Follow-up question: Does it matter?

A Tale of Two Data Sources

In this example, we’re resolving the current user from our application’s data store, and we’re resolving all of the charges against that user with Stripe API calls.

We’re not storing any charges in our application.

If we take a look at our Elixir-powered Absinthe schema definition for the user type, we’ll see what’s going on:


object :user do
  field :id, :id
  field :email, :string
  field :charges, list_of(:stripe_charge) do
    resolve fn
      (user, _, _) ->
        case Stripe.get_charges(user.customer_id) do
          {:ok, charges} -> {:ok, charges}
          _ -> InjectDetect.error("Unable to resolve charges.")
        end
    end
  end
end

The id and email fields on the user type are being automatically pulled out of the user object.

The charges field, on the other hand, has a custom resolver function that queries the Stripe API with the user’s customer_id and returns all charges that have been made against that customer.

Keep in mind that this is just an example. In a production system, it would be wise to add a caching and rate limiting layer between the client and the Stripe API calls to prevent abuse…

Does It Matter?

Does it matter that the user and the charges agains the user are being resolves from different data sources? Not at all.

This is the power of GraphQL!

From the client’s perspective, the source of the data is irrelevant. All that matters is the data’s shape and the connections that can be made between that data and the rest of the data in the graph.

Have You Tried Just Using a Function?

Written by Pete Corey on May 29, 2017.

Last month I read Sasa Juric’s To Spawn, or Not to Spawn article and its been lurking in my subconscious ever since.

I was recently working on the command handler and event sourcing system that drives my new project, Inject Detect, and this exact topic reared its head. I realized that I had been overcomplicating the project with exessive usage of Elixir processes.

Refactoring my command handler from a process into simple functions hugely simplified the application, and opened the doors for a new set of functionality I wanted to implement.

The Command Handler Process

For a high level overview, a “command” in Inject Detect represents something you want to do in the system, like requesting a new sign-in token for a user (RequestSignInToken), or ingesting a batch of queries from a user’s application (IngestQueries).

Commands are “handled” by passing them to the command handler:


%IngestQueries{application_id: application.id, queries: queries}
|> CommandHandler.handle(command)

The job of the command handler is to determine if the command is allowable based on the state of the system and the current user’s authorizations. If valid, the command being handled (IngestQueries in this case) will produce a list of events (such as IngestedQuery and IngestedUnexpectedQuery). These events are saved to the database, and a handful of “event listeners” are notified.

Command Handler as a Process

Originally, the CommandHandler was implemented as a GenServer-based Elixir process. The call to CommandHandler.handle triggered a GenServer.call to the CommandHandler process:


def handle(command, context \\ %{}) do
  GenServer.call(__MODULE__, {:handle, command, context})
end

The corresponding handle_call callback would handle the command, store the resulting events, and synchronously notify any interested listeners:


def handle_call({:handle, command, context}, _, []) do
  with {:ok, events, context} <- handle_command(command, context),
       {:ok, _}               <- store_events(events),
  do
    notify_listeners(events, context)
    {:reply, {:ok, context}, []}
  else
    error -> {:reply, error, []}
  end
end

Triggering Commands from Listeners

For several weeks, this solution worked just fine. It wasn’t until I started adding more complex event listeners that I ran into real issues.

I mentioned earlier that event listeners are notified whenever an event is produced by a command. In some cases, these listeners may want to fire off a new command. For instance, when an IngestedUnexpectedQuery event is fired, a listener may want to execute a SendUnexpectedEmail command.

Implementing this feature blew up in my face.

Because listeners are called synchronously from my CommandHandler.handle function, another call to CommandHandler.handle from within a listener would result in a GenServer timeout.

The first call to CommandHandler.handle  won’t reply until the second CommandHandler.handle  call is finished, but the second CommandHandler.handle call won’t be processed until the first call finishes. The second call will wait until it hits its timeout threshold and eventually fail.

We’ve hit a deadlock.

The only way to handle this situation would be to execute either the second call to CommandHandler.handle, or the entire listener function within an unsupervised, asynchronous process:


Task.start(fn -> 
  %SendUnexpectedEmail{...}
  |> CommandHandler.handle
end)

I wasn’t willing to go down this road due to testing difficulties and a general distrust of unsupervised children.

Command Handler as a Function

After mulling over my deadlock problem, the solution slapped me in the face.

The functionality of the command handler could be entirely implemented as a module of simple functions. No process or GenServer required.

A quick refactor led me to this solution:


def handle(command, context, listeners) do
  with {:ok, events, context} <- handle_command(command, context),
       {:ok, _}               <- store_events(events)
  do
    notify_listeners(events, context, listeners)
    {:ok, context}
  end
end

After the refactor, a synchronously called event listener can recursively call CommandHandler.handle to handle any follow-up commands it wants to execute.

Perfect.

Have You Tried Just Using a Function?

In hindsight, I had no particular reason for implementing the CommandHandler module as a GenServer. It managed no state and had no specific concurrency concerns that demanded the use of a process.

When given a hammer, everything starts to look like a nail.

Remember to use the right tool for the job. In many cases, the right tool is the simplest tool. Often, the simplest tool for the job is to just use a function.