Theseus Games 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 (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 members (TG has two membership levels, and ), 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. Premium Basic Premium 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: and , and I swiftly opened them up to see what treasures were buried within. user.js purchase.js // snippet from https://theseus-games.co.uk/assets/js/user.js componentDidMount() { .retrieveContactInformation(); .retrieveUserInfo();} this this retrieveUserInfo() {axios.get( .appUrl + '/user').then( (response) { this function **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. // s nippet from _https://theseus-games.co.uk/assets/js/purchase.js_ purchaseGame = (evt) => {evt.preventDefault(); ( .state.active == 2 || .state.active == 33) { .setState({isLoaded: }); if this this this 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**) ); } { .setState({message: 'Sorry! This game is only available to our premium members.'});}} else this Apparently, user membership information is indicated through an field in a entity, and only users where is either or can be allowed to get the game. Of course the numbers must represent membership levels, and either or indicated membership, and since I was getting the message: , my field most definitely wasn’t or . active **User** active 2 33 2 33 Premium ‘Sorry! This game is only available to our premium members’ active 2 33 Further down the line in the DevTools request tab, I saw the actual API request, and I found this response: GET _/user_ // response for GET https://theseus-games.co.uk/application/user {"application":"182718048","active":22,"userId":60088} Apparently, my field was , which is how the web app knows to deny me access. active 22 Immediately, I fired up Postman in order to make API requests to update my field. From DevTools, I copied out my current session cookie and the header value as well into Postman, and I tried to make a request to with payload: active X-XSRF-TOKEN PUT https://theseus-games.co.uk/application/user/60088 // 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 , 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 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.) POST /application/updateUser POST /application/updateUserInfo *.js I have this cool Chrome extension called 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: Tamper Chrome Download a copy of , and modify the condition checking the field and then place it in my local server (e.g. ), then modify the outgoing request to and change the request path to , so it serves my local version instead.For my local modification, I could simply change: 1. https://theseus-games.co.uk/assets/js/purchase.js if active /var/www/html/web/purchase.js https://theseus-games.co.uk/assets/js/purchase.js http://localhost/web/purchase.js if (this.state.status == 2 || this.state.status == 33) {...} to: if (true) {...} In a similar manner to , I could serve a local version of and simply return a user object with the field conveniently modified, then modify the AJAX request path to my local version. I mean something like: 2. (1) https://theseus-games.co.uk/application/user active // response for GET http://localhost/api/user.json {"application":"182718048","active":2,"userId":60088} I opted for and did this with the beautiful node package. I created a simple file with contents: (2) [json-server](https://github.com/typicode/json-server) test.json {"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 came up, and I simply modified that to my local json-server: https://theseus-games.co.uk/application/user 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 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 ( ), and the game was rapidly downloaded to my computer. Purchase My Pleasure, folks 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 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 , they didn’t, but should have simply verified my 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 ) 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. Golden Rule https://theseus-games.co.uk/application/purchaseGame active and even old-timers 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
Share Your Thoughts