NoSQL Injection in Kadira

Written by Pete Corey on May 22, 2017.

Not long ago, the Meteor Development Group purchased Kadira after the Kadira team announced it would be shutting down. Shortly after, MDG open sourced the entire project on Github. 🎉

Being the curious developer that I am, I decided to sleuth through Kadira’s source looking for trouble.

I found several security issues which I reported to Meteor’s security team and were promptly fixed. Of those issues, the most notable was a NoSQL Injection vulnerability in Kadira’s user-facing kadira-ui Meteor application.

Let’s dive into the vulnerability and take a look at why it exists, how it could have been exploited, and how to prevent it.

Finding the Vulnerability

The best way to hit the ground running when looking for NoSQL Injection vulnerabilities in a Meteor application is to grep for calls to Meteor.methods, or Meteor.publish. This gives you a very quick idea of the entry points available to you as a user (or an attacker).

After finding all methods and publications, your next step is to peruse through every argument being passed into each method and publication and make sure that they’re being thoroughly checked.

In the kadira-ui project, I managed to find a Meteor method that wasn’t thoroughly checking its arguments. The "alerts.create" method was checking that the alertInfo argument matched Match.Any. Unfortunately, checking that an argument matches Match.Any is roughly equivalent to not checking it at all.

This is looking promising.

Tracing out the path of this method showed that alertInfo is passed into a function called setAppName. An appId field is pulled out of the alertInfo object and is ultimately passed directly into a MongoDB query:


var appId = alertsInfo.meta.appId;
var app = Apps.findOne(appId);

Passing an unchecked argument into a MongoDB query is the perfect recipe for a NoSQL Injection vulnerability.

Exploiting the Vulnerability

Because we, as intrepid explorers/malicious attackers, have complete control over the data passed into the Apps.findOne query, we’re presented with a few different choices in how we could exploit this vulnerability.

We could try to target a random application in the database by passing in a special MongoDB query operator when calling "alerts.create" from the client, like {_id: {$gte: ""}}:


Meteor.call("alerts.create", { meta: { appId: { _id: {$gte: ""} } } });

Or we could try something more impactful.

Because we have control over the entire query, we can pass in a $where query operator. The $where query operator is special in that it lets us pass in and execute raw Javascript in the MongoDB database process.

We can use this to our advantage to initiate a Denial of Service attack against the database and the Meteor application itself:


Meteor.call("alerts.create", {
    meta:{
        appId:{
            $where: "d = new Date; do {c = new Date;} while (c - d < 100000);"
        }
    }
});

In this example, we’re providing a query that will run a tight, unyielding loop for one hundred seconds per document in the Apps collection. It’s important to realize that this one hundred second time limit can be extended indefinitely or removed altogether.

We’ve essentially thrown the database into an infinite loop.

Pegging the CPU of the MongoDB host in this way drastically reduces the availability of the database and in many situations renders the Meteor application completely unusable.

With a single query, we’ve taken down the entire application.

Read more about the dangers of the $where query, and watch me exploit a similar vulnerability in my “NoSQL Injection in Modern Web Applications” talk given at CraterConf!

Fixing the Vulnerability

The fix for this specific instance of NoSQL Injection is to more thoroughly check the alertsInfo argument passed into the "alerts.create" method.

We’re expecting appId to be a String, so let’s check that it is:


check(alertInfo, Match.ObjectIncluding({
    meta: Match.ObjectIncluding({
        appId: String
    })
}));

If a malicious user provides anything other than a String (like a {$where: ...} query operator) in the appId field, our method will throw an exception.

Ideally, it would be better to fully flesh out the expected schema of alertInfo and avoid using Match.ObjectIncluding, but I’m not familiar enough with the application to make that change. In your application, you should check every field of every argument down to its primitive fields.

This change is enough to prevent a NoSQL Injection attack through the alertInfo.meta.appId field.

Even though Meteor isn’t actively maintaining their open sourced version of Kadira, I submitted a pull request to the project for posterity and to warn future users of the vulnerability.

Thanks to the Meteor Team

I’d like to give a huge shout out to the Meteor team for their speedy and professional response to this situation.

Nick Martin responded to my report within ten minutes and confirmed and patched my findings in their internal version of Kadira within twenty four hours.

Not only that, but they sent me a killer collection of swag!

I’d also like to thank MDG for swooping in during a time of crisis within the community, purchasing the Kadira platform, and open sourcing the entire product to the world. That act should be seen as no small gift to the Meteor community.

Inject Detect

If you’ve been following along for the past few months, you’ll know that I’m deep into the development of my security-focused project, Inject Detect.

Inject Detect is being developed to fight the exact problem I laid out in this article: NoSQL Injection. NoSQL Injection is an incredibly prevalent vulnerability in Meteor applications, and I’ve been fighting a war against it for years now.

Inject Detect is my newest and most powerful weapon to combat the threat of NoSQL Injection.

While Inject Detect is still under development, I’m fast approaching a releasable version. Be sure to sign up for the Inject Detect newsletter to stay up to date on its upcoming release!

GraphQL Authentication with Apollo and React

Written by Pete Corey on May 15, 2017.

Continuing on from last week, we have our authentication and authorization workflow ironed out in our Elixir/Absinthe powered back-end. Fantastic!

But what about the front-end?

How do we get our auth_token (or authToken, once it’s passed through Apollo) from the server? How do we manage active user sessions on the client? How do we ensure our authToken is passed up with each request? How do we handle authentication failures?

More great questions! Let’s dive in and set up client-side authentication in our React/Apollo-powered front-end.

Getting Our Authentication Token

In order to have an authToken to authenticate our GraphQL requests, we first need to get one from the server.

An authToken can be returned by any publicly accessible GraphQL mutation, such as a signIn mutation, or in my case, a verifyRequestedToken mutation.

Assuming we’re in an Apollo-wrapped React component with a mutation function called verifyRequestedToken, we can call it to retrieve our authToken:


this.props.verifyRequestedToken(token)
    .then(({ data: { verifyRequestedToken: { authToken } } }) => {
        localStorage.setItem("authToken", authToken);
    })

Once we retrieve the token from the verifyRequestedToken mutation, we’ll store it in localStorage for later use.

Attaching Our Authentication Token

Now that we have our hands on an authToken, we can send it up with each GraphQL request made from the client.

But how do we do this?

Apollo middleware gives us an easily accessible hook into all requests made against our GraphQL server. Let’s add a middleware function to our network interface that attaches our token:


networkInterface.use([{
    applyMiddleware(req, next) {
        let authToken = localStorage.getItem("authToken");
        if (authToken) {
            req.options.headers = _.extend(req.options.headers, {
                authorization: `Bearer ${authToken}`
            });
        }
        next();
    }
}]);

This middleware function checks to see if an authToken is stored in localStorage. If it is, it’s added as a bearer token to the authorization header of our GraphQL request.

Otherwise, if no authToken is found in localStorage, no authorization header is set.

Remember, if no authorization header is provided, our Absinthe server will let the unauthorized user access publicly available mutations and queries. An invalid authToken will result in an authorization error at the HTTP level.

Handling HTTP Authentication Errors

If an invalid authCode is passed up to the server, the server will return a 403 authorization error. This error is returned at the HTTP level, not as a GraphQL error.

We can add another layer of middleware, or “afterware”, to our network interface in order to catch these 403 errors as they come back from the server:


networkInterface.useAfter([{
    applyAfterware({ response }, next) {
        if (response.status === 403) {
            localStorage.removeItem("authToken");
        }
        next();
    }
}]);

In the afterware function, we check each response for a 403 status. If we encounter a 403 response, we clear the current authToken from localStorage, effectively signing out the current user.

When using Apollo in this way, clearing the authToken from localStorage signs the user our for subsequent requests, but the currently signed in user object is still floating around in the client’s data store.

In Inject Detect, I’m flushing this user out of the client’s data store by manually querying for the current user (user) after removing authToken from localStorage:


client.query({
    query: gql`
        query {
            user {
                id
            }
        }
    `,
     fetchPolicy: "network-only"
});

Specifying a fetchPolicy of "network-only" forces the Apollo client to fetch the current user from the server, rather than returning the user cached on the client.

Because this query is made without an authToken, no user is returned from the server. The user in the client’s store is cleared out, and any components relying on this query result are re-rendered.

Full disclosure - there are probably better ways to update the store in this situation. If you have any suggestions, or are doing something similar, please let me know!

Handling GraphQL Authentication Errors

You may remember that on top of throwing HTTP authentication errors, our GraphQL resolvers will also throw authorization errors if the current user doesn’t have permission to access a given query or mutation.

These errors will be handled inline, at the source of the query or mutation, just like any other GraphQL error.

Let’s run through an example.

Going off of our examples from last week, let’s pretend that we’re accidentally showing the “Sign out” button to unauthenticated users. Clicking the sign out button triggers a signOut mutation:


this.props.signOut()
    .then(() => localStorage.removeItem("jwt"))
    ...

If we try to call this mutation as an unauthenticated user, the sign_out resolver will throw a GraphQL error down to the client. We’ll need to catch this, parse the resulting graphQLErrors, and show the errors to the user in some meaningful way:


this.props.signOut()
    .then(() => localStorage.removeItem("jwt"))
    ...
    .catch((err) => {
        let errors = _.isEmpty(err.graphQLErrors)
                   ? ["Unexpected error."]
                   : _.map(err.graphQLErrors, "message");
        this.setState({ errors });
    });

Any resulting errors can be rendered by the component in a way that makes sense in the context of the application.

Final Thoughts

With that’s we’ve set up a full-stack authentication system using React, Apollo, Absinthe, and Elixir!

While all of the moving pieces can feel daunting at first glance, breaking the problem into its piece components leads us to a relatively simple solution. All we needed to build a basic Apollo-powered session management system on the client was two middleware functions and some basic error handling.

Not bad for a day’s work.

GraphQL Authentication with Elixir and Absinthe

Written by Pete Corey on May 8, 2017.

You’ve assembled your superhero stack. Your React front-end is communicating with an Elixir/Phoenix back-end through an Apollo/Absinthe GraphQL data layer.

You feel invincible.

But that feeling of invincibility quickly turns to panic as your first real development task comes down the pipe. You need to add user authentication to your system.

How do we even do authentication in this stack?

Our application will need to handle both publicly accessible and private queries and mutations through its GraphQL API. How do we set up these queries on the server, and how do we manage users’ sessions on the client?

Great questions! Let the panic pass over you and let’s dive in.

Two Layers of Authentication

Every request made against our Absinthe-based GraphQL server is done through an HTTP request. This request layer provides a fantastic opportunity to lay the groundwork for our authentication system.

Every GraphQL request that’s made against our system will come with an optional auth_token. A valid auth_token will map to a single user in our system. This auth_token is assigned to a user when they sign in.

On each request we’ll look up the user associated with the given auth_token and attach them to the context of our GraphQL resolvers.

If we can’t find a user associated with a given auth_token, we’ll return an authorization error (403) at the HTTP level. Otherwise, if no auth_token was provided, we simply won’t set the user_id in our GraphQL context and we’ll move onto processing our query and mutation resolvers.

The key to our authentication (and authorization) system is that the currently signed in user can be pulled from the GraphQL context. This context can be accessed by all of our resolvers and can be used to make decisions about what data to return, which mutations to allow, etc…

Writing Our Context Plug

The first step of building our authentication solution is to write a piece of Plug middleware that populates our GraphQL context with the currently signed in user.

To make things more real, let’s consider the context middleware I’m using for the security-focused SaaS application I’m building (Inject Detect). The middleware is based on the middleware provided by the Absinthe guide.

With that in mind, let’s build out our Plug in a module called InjectDetect.Web.Context:


defmodule InjectDetect.Web.Context do
  @behaviour Plug

  import Plug.Conn

  def init(opts)
  def call(conn, _)

end

To start, we’ll want our plug to implement the Plug behavior, and to import Plug.Conn. Implementing the Plug behavior means that we’ll need to define an init/1 function, and a call/2 function.

The call function is the entry point into our Plug middleware. Let’s flesh it out a bit:


def call(conn, _) do
  case build_context(conn) do
    {:ok, context} ->
      put_private(conn, :absinthe, %{context: context})
    {:error, reason} ->
      conn
      |> send_resp(403, reason)
      |> halt()
    _ ->
      conn
      |> send_resp(400, "Bad Request")
      |> halt()
  end
end

Here’s the meat of our context middleware. We call out to a function called build_context which builds our GraphQL context, as the name suggests.

If build_context returns an :ok tuple, we stuff the resulting context into our conn as is expected by Absinthe.

Otherwise, we return either a 403 error or a 400 error in the case of either a bad authentication token, or any other unexpected error.

Now we need to flesh out the build_context function:


def build_context(conn) do
  with ["Bearer " <> auth_token] <- get_req_header(conn, "authorization"),
       {:ok, user_id}            <- authorize(auth_token)
  do
    {:ok, %{user_id: user_id}}
  else
    []    -> {:ok, %{}}
    error -> error
  end
end

build_context pulls the auth_token out of the authorization header of the request and passes it into an authorize function. authorize either returns an :ok tuple with the current user_id, or an :error.

If authorize returns an error, we’ll pass that back up to our call function, which returns a 403 for us.

Otherwise, if the authorization header on the request is empty, we’ll return an empty map in the place of our GraphQL context. This empty context will allow our resolvers to let unauthenticated users access public queries and mutations.

Lastly, let’s take a look at authorize:


def authorize(auth_token) do
  InjectDetect.State.User.find(auth_token: auth_token)
  |> case do
       nil  -> {:error, "Invalid authorization token"}
       user -> {:ok, user.id}
     end
end

authorize is a relatively simple function.

It takes in an auth_token, looks up the user associated with that token, and either returns that user’s id, or an :error tuple if no associated user was found.


Armed with our new InjectDetect.Web.Context Plug, we can build a new :graphql pipeline in our router:


pipeline :graphql do
  plug :fetch_session
  plug :fetch_flash
  plug InjectDetect.Web.Context
end

And pipe our /graphql endpoint through it:


scope "/graphql" do
  pipe_through :graphql
  forward "/", Absinthe.Plug, schema: InjectDetect.Schema
end

Now all GraphQL requests made against our server will run through our authentication middleware, and the currently signed in user will be available to all of our GraphQL resolvers.

Contextual Authentication and Authorization

Now that the currently signed in user can be accessed through our GraphQL context, we can start to perform authentication and authorization checks in our resolvers.

But first, let’s take a look at how we would set up a public query as a point of comparison.

A Public Query

In our application the user query must be public. It will either return the currently signed in user (if a user is signed in), or nil if the current user is unauthenticated.


field :user, :user do
  resolve &resolve_user/2
end

The user query takes no parameters, and it directly calls a function called resolve_user:


def resolve_user(_args, %{context: %{user_id: user_id}}) do
  {:ok, User.find(user_id)}
end
def resolve_user(_args, _context), do: {:ok, nil}

We use pattern matching to pull the current user_id out of our GraphQL context, and then return the user with that user_id back to our client. If our context is empty, the current user is unauthenticated, so we’ll return nil back to our client.

Great, that makes sense. The query is returning data to both authenticated and unauthenticated users. It’s completely public and accessible by anyone with access to the GraphQL API.

But what about a private queries?

A Private Query

Similarly, our application has an application query that returns an object representing a user’s application registered with Inject Detect. This query should only return a specified application if it belongs to the currently signed in user.


field :application, :application do
  arg :id, non_null(:string)
  resolve &resolve_application/2
end

Once again, our application query calls out to a resolver function called resolve_application:


def resolve_application(%{id: id}, %{context: %{user_id: user_id}}) do
  case application = Application.find(id) do
    %{user_id: ^user_id} -> {:ok, application}
    _                    -> {:error, %{code: :not_found,
                                       error: "Not found",
                                       message: "Not found"}}
  end
end

def resolve_application(_args, _context), do:
  {:error, %{code: :not_found,
             error: "Not found",
             message: "Not found"}}

In this case, we’re once again pattern matching on our GraphQL context to grab the current user_id. Next, we look up the specified application. If the user_id set on the application matches the current user’s user_id, we return the application.

Otherwise, we return a :not_found error. We’ll also return a :not_found error if no user_id is found in our GraphQL context.

By making these checks, an authenticated user can only access their own applications. Anyone else trying to query against their application will receive a :not_found authorization error.

A Private Mutation with Absinthe Middleware

Let’s take a look at another way of enforcing authentication at the query level.

We have a sign_in mutation that should only be callable by a signed in user:


field :sign_out, type: :user do
  middleware InjectDetect.Middleware.Auth
  resolve &handle_sign_out/2
end

You’ll notice that we’ve added a call to an Absinthe middleware module before the call to our &handle_sign_out/2 resolver. As you might have guessed, the InjectDetect.Middleware.Auth module is where we’re enforcing an authentication check.


defmodule InjectDetect.Middleware.Auth do
  @behavior Absinthe.Middleware

  def call(resolution = %{context: %{user_id: _}}, _config) do
    resolution
  end

  def call(resolution, _config) do
    resolution
    |> Absinthe.Resolution.put_result({:error, %{code: :not_authenticated,
                                                 error: "Not authenticated",
                                                 message: "Not authenticated"}})
  end

end

The call function is our entry-point into our middleware module. It takes an Absinthe.Resolution struct as an argument, which contains the current GraphQL context.

If the context contains a user_id, we know that the user making the request is authorized. We can return the unmodified resolution from our middleware function, which lets it continue on to the &handle_sign_out/2 resolver function.

Otherwise, if no user_id is found in the context, we use Absinthe.Resolution.put_result to modify the resolution struct before returning it from our middleware. Giving the resolution a result, in this case a :not_authenticated :error tuple, will short circuit the query or mutation’s resolution and immediately return that result to the client.

This piece of middleware effectively prevents unauthenticated users from accessing the sign_out mutation.

Beautiful.

This middleware pattern is extremely powerful. It can easily be extended to check for specific user roles or other criteria, and can be easily added to an existing query or mutation.

Additionally, multiple middleware modules or functions can be chained together to create a very readable, declarative authentication and authorization scheme around your GraphQL API.

Final Thoughts

At first, all of the moving parts related to handling authentication and authorization in a GraphQL application can be overwhelming.

Thankfully, once you wrap your head around the basic strategies and building blocks involved, the end solution easily falls into place. Authorization and authentication in a GraphQL-based system isn’t much different than in any other system.

Next week, we’ll move on to answering the second set of questions raised in the beginning of this article. How do we manage user sessions on the front-end of our application?

Stay tuned!