PRACTICAL API SECURITY WALKTHROUGH — PART 2

Tuesday 16 January 2018 By Simon Rigg

Topics: Mobile App Authentication, Integration, Repackaged Apps, TLS, A Series - ShipFast

Welcome back! This is the second part of a mini series which uses a fictional product, “ShipFast”, to walk you through the process of defending against various API security 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 post I'll dive into a first attack scenario and look at how we can defend against it.

Full source code for the demo is available on github if you want to play around and try things out for yourself, but it’s not necessary to gain an understanding of the exploits and defences I’ll be demonstrating.

Enjoy! :-)

THE WALKTHROUGH

Ensure the ShipFast server is running and accessible (see previous post) then launch the ShipFast app in the Android Emulator. You will be presented with the home screen once the app has launched:

ShipFast Home Screen

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 use an existing social login or register a new social login or email/password login.

If everything is set up correctly, you will now be presented with a "Current Shipment" screen. Woaw, 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 so much to cover there, 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, which you can find more about 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) in the file server/shipfast-api/auth.js. The piece of code which is responsible for this is:

  // Create middleware for checking the JWT
  const checkJwt = jwt({
  // Dynamically provide a signing key based on the kid in the header and the singing keys provided by the JWKS endpoint
  secret: jwksRsa.expressJwtSecret({
  cache: true,
  rateLimit: true,
  jwksRequestsPerMinute: 5,
  jwksUri: "https://" + config.auth0Domain + "/.well-known/jwks.json"
  }),
   
  // Validate the audience and the issuer.
  audience: process.env.AUTH0_AUDIENCE,
  issuer: "https://" + config.auth0Domain + "/",
  algorithms: ['RS256']
  })
  router.use(checkJwt)
view rawauth.js hosted with ❤ by GitHub

 

Back to the app running in the emulator, you should see the "Current Shipment" screen:

ShipFast Current Shipment

This 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:

ShipFast First Shipment

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:

ShipFast Delivered Shipment

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.

THE FIRST ATTACK

We know the ShipFast app communicates with the ShipFast server to make API calls, so we will now intercept the network traffic using a Man in the Middle (MitM) proxy such as mitmproxy or Charles proxy. We will use mitmproxy in this example which is free. This tutorial assumes you have configured the proxy on a host machine and the emulator.

If we request the nearest available shipment and look at the traffic through the MitM proxy, this is what we see:

ShipFast MitM Nearest Shipment

Wow! The authorization bearer token (from OIDC), nice. The ShipFast API key, great. Some location data and of course the actual URL for the API request. We can also take a peek at what comes back from the server:

ShipFast MitM Nearest Shipment Response

And now we have the basis of reverse engineering an API. Also note that the API key for ShipFast, like many API keys for various cloud-based services, is contained in the app manifest:

<meta-data android:name="com.criticalblue.shipfast.API_KEY" android:value="QXBwcm9vdidzIHRvdGFsbHkgYXdlc29tZSEh"/>

But it is just an API key, right? I mean, it is behind user authentication so that is just fine.

API keys are generally used for identifying what is using the API and are often accompanied with a secret. They are a means for a server to perform a keyed lookup and proceed from there. The problem is, that in many cases these API keys are tied to services which are either free but rate-limited, or become associated with a cost depending on usage. So even if they are treated as "not hiding particularly sensitive data", they could be misused to gain unauthorized access to services and rack up an unexpected bill for somebody. Mental note to self: remove my Google API key from this demo before committing!

If we spend a little time analyzing API traffic and the contents of the ShipFast app we gain an understanding of how the private API works and thus use that information to our advantage. Note that this is a private API, as in, undocumented to the public. I would humbly suggest that there is no such thing. All APIs are vulnerable to reverse engineering and must be protected.

With our knowledge, we now build a rogue ShipFast 'app' named "ShipRaider" which is actually a simple web server using a combination of Node.js, bootstrap, jQuery and AJAX. Most of the logic runs client-side because we wish to minimize server resources and can therefore get the clients (browsers) to do the processing. The ShipRaider website is shown below:

ShipRaider Configuration

For demonstration purposes we show the various configuration data, but this could easily be cleaned up to make this rogue service very attractive to Shippers in search for an extra bonus.

As indicated by our MitM API analysis we are able to view user authorization bearer tokens and can therefore include them in ShipRaider, however, we have made the process even easier for Shippers by providing a "Login" button which uses the Auth0 service and configuration data we extracted by reverse engineering the ShipFast app such as the Auth0 Client ID and domain.

Recall that there is no way for Shippers to enumerate available shipments in the app: location data is provided internally and the ShipFast server gives out the nearest available shipment which may or may not have gratuity associated with it. The four location fields in ShipRaider allow Shippers to specify a location of their choosing as an origin point and a radius to 'sweep' over with a 'step' granularity. This is used to construct a virtual geographical area and fire authenticated API requests for nearest shipments at various points in this area in a brute-force fashion in order to drive out the list of shipments in the backend server via API scripting. The code which performs this task is located in shipraider-rogue-web/web/js/shipraider.js:

  for (var lat = latStart; lat <= latEnd; lat += locStep) {
  for (var lon = lonStart; lon <= lonEnd; lon += locStep) {
  fetchNearestShipment(lat, lon)
  }
  }
view rawshipraider.js hosted with ❤ by GitHub

 

In practice, we would probably need to use a more unpredictable method to avoid any server Web Application Firewall (WAF) behavioral analysis, but this is outside the scope of this walkthrough.

Also recall that the ShipFast server generates sample data on first request for a shipment, so we should ensure the emulator running the ShipFast app and ShipRaider are reasonably synchronized in terms of initial location. If in doubt, restart the ShipFast server.

Click the "Search for Shipments!" button in ShipRaider and if everything is set up correctly the rogue website will begin enumerating available shipments, for example:

ShipRaider Results

We can now choose the shipment with the highest gratuity (or any shipment for that matter) and click "Grab It!" which will perform an authenticated API request to modify the state of the shipment from "READY" to "ACCEPTED" as if we had clicked the "ACCEPT" button in the app.

When we go back into the genuine ShipFast app and mark ourselves as available for the next shipment, the app first requests any pending shipment, so we will be presented with the shipment we grabbed using ShipRaider. Go ahead and try it out!

Shippers are happy, ShipFast is not. A defense is needed urgently.

THE FIRST DEFENCE

It is clear from the first attack that ShipFast must provide better protection of their API to ensure that only the genuine app is using it, and not a rogue alternative such as ShipRaider. Some API requests are from the app, others are from the rogue website. The only way to distinguish these is by the ShipFast API key, but that has already been stolen!

A simple strategy to avoid leakage of third-party API keys in the mobile app would be to, well, not include these API keys in the mobile app in the first place! They can be hoisted out of the app and instead stored on an intermediate server between the app and the ShipFast backend server, the intermediate server acting as an API key proxy. The app would then access the proxy instead of the backend server through a single API key and unified API to reduce the attack surface. This strategy is covered in more detail in another tutorial at Hands On API Proxy which I recommend checking out.

Another strategy to avoid leakage of or tampering with sensitive data originating from the app which is visible through a MitM attack as demonstrated is to configure Transport Layer Security (TLS) certificate pinning to ensure the app knows it is talking to the correct server. In practice, this is often difficult to get right and there are various tools such as TrustKiller for Android and SSL Kill Switch for iOS which circumvent this protection. There is a great video explaining this in more detail which I recommend checking out on YouTube.

For the purposes of this walkthrough, we will focus on a different strategy of defense.

An initial improvement would be to move sensitive data into app code rather than the manifest. That will at least make it slightly harder for an attacker to find the data.

A further improvement would be to bind the API requests to a particular client and ensure that modification of these requests through MitM attacks is detected.

A common method used to digitally sign API requests involves using a Keyed-Hash Message Authentication Code (HMAC) which is designed to prevent hijacking and tampering.

We will generate a shared symmetric key for the HMAC, include it in the ShipFast app and server, and construct the message from the API request URL and user authentication bearer token. The result of this HMAC can be transmitted as part of API requests from the app and verified by the server.

To enable this stage of the demo, modify the "currentDemoStage" variable in the app's "DemoConfiguration.kt" file app/android/kotlin/ShipFast/app/src/main/java/com/criticalblue/shipfast/DemoConfiguration.kt:

  /** The current demo stage */
  val currentDemoStage = DemoStage.HMAC_STATIC_SECRET_PROTECTION

Also modify the "config.currentDemoStage" variable in the server's "demo-configuration.js" file server/shipfast-api/demo-configuration.js:

  // The current demo stage
  config.currentDemoStage = DEMO_STAGE.HMAC_STATIC_SECRET_PROTECTION

 

Restart the ShipFast server, ShipRaider web page and ShipFast mobile app for these changes to take affect. The app should work as it did before, but ShipRaider no longer appears to work. We will take a look at the changes in more detail.

If we turn our attention to the ShipFast API server code, and in particular the "auth.js" Node.js express router server/shipfast-api/auth.jswe can observer the changes made to validate the client API requests by use of an HMAC.

First, we have the HMAC secret:

  // The ShipFast HMAC secret used to sign API requests
  const SHIPFAST_HMAC_SECRET = '4ymoofRe0l87QbGoR0YH+/tqBN933nKAGxzvh5z2aXr5XlsYzlwQ6pVArGweqb7cN56khD/FvY0b6rWc4PFOPw=='
view rawauth.js hosted with ❤ by GitHub

Second, we have a new API request header "SF-HMAC":

  // Retrieve the ShipFast HMAC used to sign the API request from the request header
  var requestShipFastHMAC = req.get('SF-HMAC')
view rawauth.js hosted with ❤ by GitHub

Finally, we can see the HMAC computation which uses the static HMAC secret and a message composed of the request URL and user authorization bearer token:

  // Just use the static secret during HMAC verification for this demo stage
  hmac = crypto.createHmac('sha256', Buffer.from(secret, 'base64'))
  ...
  ...
  // Compute the request HMAC using the HMAC SHA-256 algorithm
  hmac.update(req.protocol)
  hmac.update(req.host)
  hmac.update(req.originalUrl)
  hmac.update(req.get('Authorization'))
  var ourShipFastHMAC = hmac.digest('hex')
   
  // Check to see if our HMAC matches the one sent in the request header
  // and send an error response if it doesn't
  if (ourShipFastHMAC != requestShipFastHMAC) {
  ...
view rawauth.js hosted with ❤ by GitHub

 

This Node.js express router is added to all our authenticated API requests which means that it will verify the HMAC signature in the "SF-HMAC" header and respond with 403 "Forbidden" if things are not right.

NEXT TIME...

In my next post I will demonstrate an attack on this first defense as things start to get more sophisticated, but I'll also show how we can increase our defensive strategy and try to scupper those ShipRaiders aarrrgh!

Thank you for reading and stay tuned!

 

 Learn More about Mobile API Security!