Editor's note: This post was originally published in January 2018 and has been revamped and updated for accuracy and comprehensiveness. The latest update was in September 2020.
Welcome! A quick question: Do you know what’s using your API? Really?
In this mini-series, I am going to use a fictional product, “ShipFast”, to walk you through the process of defending against various exploits in a mobile application to gain access to data on a remote server allowing real users of the system to gain an unfair business advantage at the expense of the company.
In this first post, I will provide some context before we dive into the walkthrough.
We’ll be using the Android platform for the mobile app written in the Kotlin programming language and an Express Node.js backend server.
Full source code for the demo is available on github so that you can gain an understanding of the exploits and defences I’ll be demonstrating.
"ShipFast" is a shipment delivery service company who subcontract the pickup and delivery of shipments to anyone wishing to earn some additional income. These guys are the "Shippers". They earn a wage for the delivery of shipments, but also have the opportunity to earn an additional bonus for some shipments which include an associated gratuity. Not all shipments have a gratuity, and the gratuity is different for each shipment, therefore not all shipments are equal.
ShipFast provides the actual delivery service to customers on a subscription basis. Shippers are paid their standard wage, and any gratuity provided by customers ahead of time is passed on, 100%, to Shippers. Good for them.
ShipFast needs to run a tight ship, so they are keen to maintain optimal efficiency in their service and therefore ensure that distances covered by Shippers to pick up shipments from one location and deliver them to another location are kept to an absolute minimum. Therefore, Shippers have no way to access the list of available shipments and thus pick and choose them: they are always given the available shipment which is closest to their current location.
There is no hijacking of user credentials here or tricking users to tap things in the app they shouldn't. The attack is much more subtle, because it is exploited by real trusted users of the system.
Recall in the explanation above ("The Service") that shipments are not created equally: some have a gratuity associated with them, but the Shippers have no way to pick and choose as they simply get the nearest available shipment to their location...or do they?
Enter: ShipRaider. And an evil pirate laugh I will resist imitating.
Since the ShipFast server API has an endpoint to acquire the nearest available shipment given a Shipper's location data (expressed by latitude and longitude), reverse engineering the server API tells us it is possible to drive out the backend server data and enumerate the list of available shipments by sweeping over a geographical area, sending multiple fake Shipper location values to the server endpoint.
Once we can enumerate this data, we can grab the shipments in turn with the highest associated gratuity and earn as much cash as possible, as real legitimate Shippers, but at the expense of the ShipFast company who will be hurt by the increased distances Shippers are travelling and the increased time it takes shipments to be delivered. Bad for profit. Bad for customers. Bad for business. Great for us Shippers though!
For this demonstration, where no Shippers will be hurt... much, we will show various stages to defend the ShipFast mobile app and server API, and the attacks used to work around these defences.
In the first stage, we will show how ShipFast secures access to their server by authenticating users and by providing an API key to identify what is talking to the service. The API key will be present in the app's metadata.
We will then show how easy it is to extract this from the app statically and use it against the server API.
In the second stage, we will show how ShipFast secure access to their server API by introducing a Keyed-Hash Message Authentication Code (HMAC) to authenticate API requests by digitally signing them and prevent hijacking and tampering.
HMACs use a secret and a message to produce a cryptographic signature and therefore tell you two things: the integrity of the message (has it been modified?) and the authenticity of the message (is the person who signed it in possession of the secret key?).
The secret key is embedded statically in-app code, and we will show how this can be extracted, along with the message components, statically from the app and used against the server API.
In the third stage, we will extend what we did in Stage 2 but instead of using a static secret we will use a dynamic secret, that is, a secret which is computed at app runtime and therefore is not known until the app is actually running, so cannot easily be extracted statically by looking at the app package on disk.
We will then show how an obfuscated and digitally-signed app can be repackaged to support debugging, then debug the app by introducing a breakpoint at the creation of the HMAC in order to steal the dynamic secret. We will use our knowledge from static analysis of the app in Stage 2 to guide the dynamic analysis in Stage 3.
Clearly, we need something stronger! Stepping back from the frontline of the battlefield for a moment, and gathering the troops and the strategic plans, we conclude that our server API must know reliably what is talking to it: is it ShipFast, or is it ShipRaider (or indeed something else). What we really need is a way of authenticating the running mobile application in addition to the user and the network channel. We need something which digitally fingerprints the app reliably and without using behavioral heuristics which aren't always accurate and take time to warm up to genuine and rogue app behavior, and then communicates that digital fingerprint to our API servers so we can verify it and decide how to respond.
If you want to play around with all stages of this demo as you go through it, then you just need to install the ShipFast APK for each demo stage, and during each of this blog series we will tell you when is time to do it.
The APKs for each demo stage can be found in the releases page for the Github repository.
If you want to understand how the ShipFast demo works under the hood, then please see the README of the Github repository.
Install in your mobile device the APK for the ShipFast API Key demo stage, then launch the ShipFast app and you will be presented with the home screen once the app has started:
Click the "SHIPFAST LOGIN" button to start the user authentication process using the Auth0 code grant flow (see Authorization Code PKCE for more details on the underlying process).
The Auth0 "Lock" screen appears (the UI component which allows you to log in), so either you use an existing social login or register with a new social login or email/password login.
If everything is set up correctly, you will now be presented with a screen asking to access the device location:
If you allow the SgipFast to access your mobile device location then a "Current Shipment" screen will be presented:
Wow, but hold on a minute, a lot just happened there. We used the Auth0 service to provide us with an industry-standard method of authenticating a user using the OAuth 2.0 and OpenID Connect (OIDC) protocols. There is a lot to cover there, so we will leave that for another tutorial. The result of logging in this way provides the app with a time-limited JSON Web Token (JWT) representing the authenticated user which we can use to communicate with the ShipFast server and prove we are who we say we are. A JWT is simply a cryptographically-signed carrier of information, about which you can find out more at JWT Introduction.
The ShipFast server validates the authenticated user using Node.js express middleware (a bit of code which plays a role in processing a network request). The piece of code which is responsible for this is at server/shipfast-api/api/middleware/auth0.js.
The current shipment screen shows the current active shipment, but there is no active shipment at the moment until you toggle the "I'm available!" switch to express that, as a Shipper, you are ready for deliveries. Go ahead and do that now and you will see the nearest available shipment, for example:
It is now possible to accept the shipment, pick up the shipment and mark it as collected, deliver it, then finally mark it as delivered and repeat the process. Go ahead and progress the shipment through the various states by clicking the button in the bottom-left which will change from "ACCEPT" to "COLLECT" to "DELIVER". Note that the shipment status also updates after this button is clicked. This state transition is achieved through authenticated API calls to our server. Finally, the shipment will be shown in the "Delivered Shipments" screen, for example:
In our case, this shipment had no gratuity. We would really like one with a bonus! You can hit the back button in the "Delivered Shipments" screen to go back to the "Current Shipment" screen to restart the process with a new shipment.
Now that we have some context, we’ll dive into the first attack of our fictional ShipFast product in my next post and how we can defend against it.
Thank you for reading and stay tuned!