Aista – Magic Cloud – Generate a CRUD app in seconds

Categories
GraphQL

Securing GraphQL

Securing GraphQL is almost impossible due to business logic injection attacks. In this article, I show you how it can be done though.

A month ago Oscar posted about how he hacked BeReal, by bypassing the business logic BeReal had implemented on the client, and injected his own business logic – BTW, nice dog Oscar 😀

Oscar's dog

Oscar succeeded because BeReal created their product using Firebase, which of course makes it very tempting to write business logic on the client, the same way GraphQL makes it tempting to put business logic on the client. When the client is responsible for applying the business logic, you basically have zero security for reasons Oscar explains in his article. I like to refer to it as follows …

Business Logic Injection Attacks

And, this is a problem with not only Firebase, but also Hasura and Supabase. For junior developers looking to “create something beautiful” of course, implementing everything in frontend JavaScript code, and using GraphQL to access the server, sounds very tempting. However, this is for all practical concerns the equivalent of drilling a hole in the boat you’re sitting in. It’s madness!

There exists methods to secure GraphQL, Firebase, and PostgREST of course, such that injecting malicious business logic becomes impossible – However, all solutions basically implies turning OFF GraphQL construction on the frontend client – At which point (obviously) neither GraphQL, nor PostgREST, nor Firebase have much legitimate remaining value proposition left …

The “solution” proposed by Hasura, which is to implement business logic in Stored Procedures and Database Functions really doesn’t deserve a comment to be honest with you. And their remaining “solutions”, which is to create your own HTTP REST API, implies you no longer have any value from GraphQL, and you might as well manipulate and retrieve your data directly from your HTTP API, like Aista does – Shameless plug, I’m the CEO of Aista.

Notice, of course, implementing your own HTTP REST API is a solution to the GraphQL business logic injection security threat – However, if you have to write your own business logic HTTP REST API, why use Hasura, Firebase, or Supabase then? The entire value proposition for developers wanting to use these tech stacks, is that they can simply “start churning out JavaScript code / iOS code / Android SDK code, connect to GraphQL, Firebase, or PostgREST, and their apps are done” …

As the use case with BeReal clearly illustrates, this is not only not a solution, but it’s basically the equivalent of putting business logic validation into the hands of your users. Let me illustrate the problem for you in case you don’t get it …

  1. Create a Facebook post
  2. Intercept the invocation towards Facebook
  3. Add the flag “isAd” to your invocation
  4. Set your budget to 1 trillion dollars
  5. Set the flag “hasPaid” to true

Congratulations, you’re now running a trillion dollar Facebook marketing campaign without paying!

This is a fundamental problem with allowing the client to construct queries, and is not something that can be fixed, without adding code that literally validates every single query you’re accepting on the server. At which point of course, the validation logic becomes a “bajillion” times larger than your original codebase, required to get some MVP up running for a frontend app, that seems to be working perfectly initially. Hence, the cost of your “rapid MVP” is that you cannot set it into production, and you’ll have to spend possibly 3 times as much time to secure it, before you can publish it.

This is basically a variation of why whitelisting activities the user is allowed to perform is the only way to apply security, and why most security experts will tell you that “blacklisting activities is ‘cheese doodles security'” …

GraphQL, Firebase, PostgREST, Hasura, and Supabase are basically whitelisting EVERYTHING. Implying the only method to secure your “queries” is to start “blacklisting” things, unless you create a ‘strongly typed’ API and intercepts EVERYTHING you’re passing into these service providers before transmitting it to your data storage …

The solution

In the video below I am proposing how to rapidly turn off GraphQL construction on “the edge” by using Aista Magic Cloud as your own interceptor, for then to hide the JWT token going towards your GraphQL, Firebase, or PostgREST endpoint, and only allowing Magic to access your JWT token, while doing validation in your Magic cloudlet.

With Magic, this should be a simple job, requiring maybe one or two days to fix. To get started securing your GraphQL endpoints, you can use the link below.

Disclaimer – I am the CEO of Aista, and you might want to ask your senior developer(s) if the above assessment of the security threats related to GraphQL, PostgREST, and Firebase is correct or not, before trusting my words. After all, both Hasura, Supabase, and Firebase are competitors of Aista …

Just sayin’ …

Below is the code I am using in the video.

.arguments
   email:string
.description:Intercepts our GraphQL endpoint

// Endpoint we're intercepting.
.endpoint:"https://my-app.hasura.app/graphql"

/*
 * Lambda object executed as an "intercepted lambda object"
 * before invocation to HTTP endpoint.
 */
.before
   validators.mandatory:x:@.arguments/*/email
   validators.email:x:@.arguments/*/email

/*
 * This is where you would parametrise your above [.before] object
 * with for instance arguments given by the client.
 */

// Evaluating [.before] lambda object.
eval:x:@.before

// Retrieving Hasura's JWT token from configuration settings
config.get:"hasura:jwt"

// Forwarding arguments given to endpoint to intercepted endpoint.
add:x:../*/http.post/*/payload
   get-nodes:x:@.arguments/*

// Invoking the intercepted HTTP endpoint.
http.post:x:@.endpoint
   headers
      Content-Type:application/json
      Authorization:x:@config.get
   convert:true
   payload

// Sanity checking invocation to endpoint.
if
   not
      and
         mte:x:@http.post
            .:int:200
         lt:x:@http.post
            .:int:400
   .lambda
      log.error:Something went wrong as we invoked our intercepted HTTP endpoint
         endpoint:x:@.endpoint
         status:x:@http.post
         result:x:@http.post/*/content
      throw:Intercepted endpoint did not return success
         public:true
         status:502

// Return result to caller.
add:x:+
   get-nodes:x:@http.post/*/content/*
return