What Happens When a Shitty Coder Builds Your Backend

Written by eloyekunle | Published 2018/07/27
Tech Story Tags: javascript | cybersecurity | web-development | technology | tech

TLDRvia the TL;DR App

NOTE: For security reasons, the actual web services involved in this will not be named, so for the sake of this article I will invent a fictional online game store, called Theseus Games (TG) with fictional URL: https://theseus-games.co.uk.

I recently tried to buy a new game from TG which unfortunately was only available to Premium members (TG has two membership levels, Basic and Premium), and the Premium membership was expensive as hell. I bit my nails for a while and wondered what I could do since my future happiness seriously depended on playing several hours of said game as soon as possible. Since I’m a software engineer myself, I decided to see if I could just play around with TG’s services for a while and just see how things worked.

I reloaded TG’s game purchase page with my Chrome DevTools open in order to see what requests the website makes. First thing I noticed was that TG uses AJAX to load some of its Javascript assets, about a dozen of them. Two of these scripts caught my eye: user.js and purchase.js, and I swiftly opened them up to see what treasures were buried within.

// snippet from https://theseus-games.co.uk/assets/js/user.js

componentDidMount() {this.retrieveContactInformation();this.retrieveUserInfo();}

retrieveUserInfo() {axios.get(this.appUrl + '/user').then(function (response) {

  **this**.setState({  
    application: response.data.application,  
    active: response.data.active,  
    isLoaded: **true**      });  

}.bind(**this**)  

);}

Of course, I had built several apps with React and React Native and I immediately realized that the web frontend was built with React, communicating with a Laravel backend (I got this from the session cookies). If you know a little Javascript (or pretty much any web language haha), you can work your way through these snippets.

// snippet from _https://theseus-games.co.uk/assets/js/purchase.js_

purchaseGame = (evt) => {evt.preventDefault();

if (this.state.active == 2 || this.state.active == 33) {this.setState({isLoaded: false });

**var** newGame = {  
  gameId: **this**.state.gameId,  
  session: **this**.state.session  
};  

axios({  
  method: 'post',  
  url: **this**.appUrl + "/purchaseGame",  
  data: newGame  
}).then(  
  **function** (response) {  
    **this**.setState({  
      isLoaded: **true**        });  
  }.bind(**this**)  
);  

} else {this.setState({message: 'Sorry! This game is only available to our premium members.'});}}

Apparently, user membership information is indicated through an active field in a **User** entity, and only users where active is either 2 or 33 can be allowed to get the game. Of course the numbers must represent membership levels, and either 2 or 33 indicated Premium membership, and since I was getting the message: ‘Sorry! This game is only available to our premium members’, my active field most definitely wasn’t 2 or 33.

Further down the line in the DevTools request tab, I saw the actual GET _/user_ API request, and I found this response:

// response for GET https://theseus-games.co.uk/application/user

{"application":"182718048","active":22,"userId":60088}

Apparently, my active field was 22, which is how the web app knows to deny me access.

Immediately, I fired up Postman in order to make API requests to update my active field. From DevTools, I copied out my current session cookie and the X-XSRF-TOKEN header value as well into Postman, and I tried to make a PUT request to https://theseus-games.co.uk/application/user/60088 with payload:

// payload to PUT https://theseus-games.co.uk/application/user

{"application":"182718048","active":2,"userId":60088}

But I immediately received a 405 (Method Not Allowed) error, meaning users are updated in a different route. In turn, I tried POST /application/updateUser, POST /application/updateUserInfo and got 404s for both. At this point, I decided to abandon this approach entirely and try something else. (If I wanted to continue, I’d have inspected the other *.js files that were downloaded via AJAX to get the specific route for updating user, or just go to my account page and try to update my account, then retrieve the route; but there are many ways to skin an elephant haha.)

I have this cool Chrome extension called Tamper Chrome which is used to (surprise!) tamper with requests in the browser, and modify request headers, payloads, etc. The next approach I tried involved me tampering with the actual API requests, and there were two things I could easily try:

1. Download a copy of https://theseus-games.co.uk/assets/js/purchase.js, and modify the if condition checking the active field and then place it in my local server (e.g./var/www/html/web/purchase.js), then modify the outgoing request to https://theseus-games.co.uk/assets/js/purchase.js and change the request path to http://localhost/web/purchase.js, so it serves my local version instead.For my local modification, I could simply change:

if (this.state.status == 2 || this.state.status == 33) {...}

to:

if (true) {...}

2. In a similar manner to (1), I could serve a local version of https://theseus-games.co.uk/application/user and simply return a user object with the active field conveniently modified, then modify the AJAX request path to my local version. I mean something like:

// response for GET http://localhost/api/user.json

{"application":"182718048","active":2,"userId":60088}

I opted for (2) and did this with the beautiful [json-server](https://github.com/typicode/json-server) node package. I created a simple test.json file with contents:

{"user": {"application":"182718048","active":2,"userId":60088}}

Then I started json-server:

Next, I activated Tamper Chrome in order to reroute all further requests from current tab:

And I reloaded the page. Soon enough, the AJAX request for https://theseus-games.co.uk/application/user came up, and I simply modified that to my local json-server:

Of course, the request hit my json-server which happily responded with a few sweet bytes.

That was all I needed to do, so I decided to click the Purchase button again, and of course, all the frontend could see was that I’m a privileged TG member (haha!); then there was a loading indicator and next thing, a popup thanking me for using Theseus Games (My Pleasure, folks), and the game was rapidly downloaded to my computer.

I swiftly abandoned all earthly concerns and dedicated the next few hours of my life to the game, and when I was mentally and psychologically exhausted, I lay on my bed in solemn contemplation of just what the unfortunate folks at TG could have done to further protect their wares.

The Golden Rule here, ladies and gentlemen, is to always do both a client-side and a server-side check of all sensitive components of your web applications. In TG’s case, on the server side, at https://theseus-games.co.uk/application/purchaseGame, they didn’t, but should have simply verified my active field again, to ensure that I was authorized to purchase said game, which is actually a trivial check to make. Unfortunately, this is a mistake several web developers (newbies and even old-timers) still make when building web services, and frequently fail to tighten up their security so if you build web services, please pay a little bit more attention in the future.

Thanks for reading, folks. Share with friends and leave your feedback below. Much love.

PS: As stated above, there is no Theseus Games (TG) and _https://theseus-games.co.uk_ doesn’t exist. I’ve notified the actual company in question (not a game store, sorry) and they have resolved this issue.

Originally published at https://elijahoyekunle.com.

Need help with your web app? Contact me! — Twitter — Github — LinkedIn


Published by HackerNoon on 2018/07/27