PRACTICAL API SECURITY WALKTHROUGH — PART 3

Wednesday 17 January 2018 By Simon Rigg

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

Welcome back! This is the third part of a mini series which uses 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 post I'll dive into the second 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 API security defenses I’ll be demonstrating.

Enjoy! :-)

THE SECOND ATTACK

If we use a MitM proxy technique to view network activity between the app and the server, we can observer that a new "SF-HMAC" header is introduced:

MitM HMAC

In this case, the name of the request header is a bit of a giveaway, however, deeper analysis of the mobile app would also lead us to discover that API requests are now signed with an HMAC. Once that is discovered, we know there are three things to find to break this protection:

1 The HMAC algorithm

2 The HMAC secret

3 The HMAC message

Since we know the app must be computing this HMAC request header, we can attempt to decompile it using freely-available reverse engineering tools and perform static analysis. We know the result is added to an "SF-HMAC" header. We know an HMAC is used. We can hypothesise that the app must contain an embedded secret for the HMAC and probably uses whatever HMAC function comes as part of the standard libraries. We will now test our hypothesis.

We will decompile the app's APK using the tools "apktool", "dex2jar" and "JD-GUI". I will not cover the details here as other people have done that very well, but a good tutorial can be found on Bright Developers.

Looking through the decompiled code, we find something interesting:

ShipFast HMAC Static Secret

Eureka! My bath is overflowing, brb... false alarm. Well, we have found quite a nugget here! This method, first of all, has been obfuscated. So +1 for the developer for at least doing that. In our case this used ProGuard but other solutions are available.

However, it is very important to note that you typically cannot obfuscate public methods as it is not certain at compile time what will be invoking them, so it is not safe to obfuscate them as it could lead to a nasty runtime exception. This is especially true for standard libraries, for example, javax.crypto which provides HMAC support.

What we see in our decompiled code snippet is:

1 The HMAC algorithm (the "HmacSHA256" string)

2 The HMAC secret (the long base 64 string "4ymoo..." etc)

3 The HMAC message. This is harder to find, but we can follow through the decompiled Java code or even the byte code pretty easily and look for calls to "Mac.update()" as those calls make up the HMAC message. We know when things are finished, because the "Mac.doFinal()" method will be called which produces the result.

HINT: It is easy to find standard library cryptographic functions in obfuscated and signed mobile apps.

In this stage of the demo, the secret is in code which is much better than in a static text file, but is still easily retrievable. Armed with our new knowledge, we can update ShipRaider to compute the new HMAC header and therefore continue to authenticate our rogue API requests.

To enable this stage of the demo, modify the "currentDemoStage" variable in the ShipRaider's "shipraider.js" file server/shipraider-rogue-web/web/js/shipraider.js:

  // The current demo stage
  var currentDemoStage = DEMO_STAGE.HMAC_STATIC_SECRET_PROTECTION
view rawshipraider.js hosted with ❤ by GitHub

 

If things are configured correctly, you should now be able to run ShipRaider against the ShipFast server and grab those bonus shipments.

THE SECOND DEFENCE

It turns out that our previous approach is a really good starting point: signing the API requests in the app proving both who and what is making those requests which can then be verified by the server. This binds the requests to the user and to the running app.

The problem is our implementation. To begin with, we use a static secret in code in the form of a single string. You may think this is obvious now that I mention it, but you would be surprised, even shocked, to discover the number of cloud-based services which offer access through an API or SDK which involves initialization using an API key, an API secret and often a base URL in code. This makes it easy to adopt the service, but unfortunately also makes it easy to exploit the service.

We can obfuscate the HMAC secret by computing it at runtime which means regular static analysis will not yield the secret: the app must be run in order to generate the secret and store it in memory for use in the HMAC computation. The approach we use is kept simple for demonstration purposes, and the process is as follows:

1 Take our original HMAC secret embedded in the app code

2 Take the user's ID Token JWT fetched at runtime by the user logging in using Auth0

3 Perform an XOR operation on the two

4 Use the result as our new dynamic HMAC secret

In our Android app, the code looks like this:

  val secret = HMAC_SECRET
  var keySpec: SecretKeySpec
  ...
  ...
  val obfuscatedSecretData = Base64.decode(secret, Base64.DEFAULT)
  val shipFastAPIKeyData = loadShipFastAPIKey(context).toByteArray(Charsets.UTF_8)
  for (i in 0 until minOf(obfuscatedSecretData.size, shipFastAPIKeyData.size)) {
  obfuscatedSecretData[i] = (obfuscatedSecretData[i].toInt() xor shipFastAPIKeyData[i].toInt()).toByte()
  }
  val obfuscatedSecret = Base64.encode(obfuscatedSecretData, Base64.DEFAULT)
  keySpec = SecretKeySpec(Base64.decode(obfuscatedSecret, Base64.DEFAULT), "HmacSHA256")
  ...
  ...
  // Compute the request HMAC using the HMAC SHA-256 algorithm
  ...
  ...
view rawRestAPIUtils.kt hosted with ❤ by GitHub

 

And on the ShipFast server side, the code looks like this:

  var secret = SHIPFAST_HMAC_SECRET
  var hmac
  ...
  ...
  var obfuscatedSecretData = Buffer.from(secret, 'base64')
  var shipFastAPIKeyData = new Buffer("QXBwcm9vdidzIHRvdGFsbHkgYXdlc29tZSEh")
  for (var i = 0; i < Math.min(obfuscatedSecretData.length, shipFastAPIKeyData.length); i++) {
  obfuscatedSecretData[i] ^= shipFastAPIKeyData[i]
  }
  var obfuscatedSecret = new Buffer(obfuscatedSecretData).toString('base64')
  hmac = crypto.createHmac('sha256', Buffer.from(obfuscatedSecret, 'base64'))
  ...
  ...
  // Compute the request HMAC using the HMAC SHA-256 algorithm
  ...
  ...
  // 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

 

It is almost like some strange game of spot the difference, but as you can hopefully see, the app and server perform the same HMAC calculation using the same secret key and message, and the server ensures both components end up with the same answer before authenticating the API request.

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_DYNAMIC_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_DYNAMIC_SECRET_PROTECTION

 

Restart everything again, the app should continue to work normally, but it looks like we have blocked those pesky ShipRaider pirates for now!

NEXT TIME...

We need to get smarter now! In my next and final post in this series I will demonstrate a runtime attack on this second defense... but can we effectively defend against this?

Time to hack some kernels...

Thank you for reading and stay tuned!

 

 Learn More about Mobile API Security!