We're Hiring!

Practical API Security Walkthrough — Part 4

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 May 2021.

Welcome back! This is the fourth and final 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 third API security attack scenario and what is required to effectively defend against it.

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.

Enjoy! :-)

The Third Attack

If we MitM (man in the middle) the API traffic again, we still see the "HMAC" header.

Recall we require three things break this protection:

1 The HMAC algorithm

2 The HMAC secret

3 The HMAC message

Once again, we will decompile the app's APK using freely available open-source tools, like the Mobile Security Framework(MobSF). Looking around, we find the following:

shipfast_hmac_dynamic_secret

Okay, it looks a little different this time round. There is a lot more 'noise' between the static secret we discovered earlier and the initialisation of the HMAC. We added this obfuscation ourselves, but it should be noted that there are commercial tools available which achieve the same goal automatically.

Things were going so well for ShipFast, but alas, it is typically not possible to obfuscate public library methods and we still notice a couple of interesting points:

1 The algorithm is still "HmacSHA256"

2 The message appears to be the same

3 The secret key is computed using a combination of static and dynamic data.

Looking at the decompiled code, we see there are multiple variables involved in computing the secret, but the result still ends up being a single parameter to the "SecretKeySpec" object constructor. To break this, we need to run the app and find out what that first parameter is.

As an attacker, we have a number of options available to us. We can repackage the original signed APK and enable debugging of the app by adding the following string to the manifest file:

android:debuggable="true"

We can then resign the APK and run it on an emulator or device and debug it to derive the HMAC key.

There is another option available which avoids the need to modify the original APK to include a debug flag. We could use a dynamic instrumentation framework such as Frida and follow along the docs to create a script which can dump the HMAC key when required. Frida is capable of attaching to a running process, poking around that process, then detaching from it and leaving it in the original state. Ouch! To run this, however, we do need to be on a rooted/jailbroken device or emulator. You can learn how easy is to setup an emulator with a writable file-system to install a Frida server by following the steps on this Github gist, that is part of the necessary setup to follow along the blog post on How to Bypass Certificate Pinning with Frida on an Android App.

There are other such frameworks to modify apps on rooted/jailbroken devices such as the Xposed framework for Android. You can see how Xposed is used to break TLS certificate pinning in this video:

 

In this walkthrough though, we will choose the option to enable app debugging, unzip the APK, add the debug flag to the manifest, zip the APK and re-sign it before running it again.

In Android Studio, we add a method breakpoint to the "javax.crypto.spec.SecretKeySpec" constructor which accepts the key and algorithm as parameters. When we run our app and trigger an action to perform an authenticated API request, such as fetching the nearest available shipment, we will break on the constructor of SecretKeySpec and see the computed HMAC secret.

Using a debugger also makes it possible to step through the code and learn about the algorithm so we can replicate it in the rogue ShipRaider website.

To see this process in action, we have prepared a short video:

 

Now that the ShipRaider team has reverse engineered the computed HMAC code in the mobile app used to sign the API request, they can provide a new ShipRaider web interface. You will need to login again with the same user you logged into the ShipFast app with for the same stage, and then you are  good to click again on the Find my location button followed by another click on the Search for Shipments! button to grab those bonus shipments:

shipraider-dynamic-hmac-shipments-list

You can now repeat the same steps you have followed in the previous blog post and you will see the shipment you have selected showing in the mobile app:

shipfast-dynamic-hmac-with-gratuity-shipment-screen

You can see how ShipRaider mimics the ShipFast mobile app in the calculation of the HMAC in this code, or you can just open the developer tools on your browser and look for the same code:


const computeHMAC = function(url, idToken) {
currentDemoStage = getShipfastDemoStage()


if (currentDemoStage == SHIPFAST_DEMO_STAGE_HMAC_STATIC_SECRET_PROTECTION
|| currentDemoStage == SHIPFAST_DEMO_STAGE_HMAC_DYNAMIC_SECRET_PROTECTION) {
let hmacSecret
if (currentDemoStage == SHIPFAST_DEMO_STAGE_HMAC_STATIC_SECRET_PROTECTION) {
// Just use the static secret in the HMAC for this demo stage
hmacSecret = HMAC_SECRET
}
else if (currentDemoStage == SHIPFAST_DEMO_STAGE_HMAC_DYNAMIC_SECRET_PROTECTION) {
// Obfuscate the static secret to produce a dynamic secret to
// use in the HMAC for this demo stage
let staticSecret = HMAC_SECRET
let dynamicSecret = CryptoJS.enc.Base64.parse(staticSecret)
let shipFastAPIKey = CryptoJS.enc.Utf8.parse($("#shipfast-api-key-input").val())
for (let i = 0; i < Math.min(dynamicSecret.words.length, shipFastAPIKey.words.length); i++) {
dynamicSecret.words[i] ^= shipFastAPIKey.words[i]
}
dynamicSecret = CryptoJS.enc.Base64.stringify(dynamicSecret)
hmacSecret = dynamicSecret
}


if (hmacSecret) {
let parser = document.createElement('a')
parser.href = url
let msg = parser.protocol.substring(0, parser.protocol.length - 1)
+ parser.hostname + parser.pathname + idToken
let hmac = CryptoJS.HmacSHA256(msg, CryptoJS.enc.Base64.parse(hmacSecret)).toString(CryptoJS.enc.Hex)
return hmac
}
}
return null
}

The Final Defence?

It is possible to build on the previous defence by use of a more sophisticated dynamic key for the HMAC.

One option would be to introduce code to compute a signature of the app's APK at runtime, something similar to the V1 and V2 signatures already included.

We could also verify the signing authority of the APK to ensure the app is not repackaged and re-signed by someone else (I'm looking at you, ShipRaider pirates!).

As pointed out in the third attack, an attacker can debug the running app if they modify the original APK or use an instrumentation framework such as Frida to scrape data from a running app. To mitigate these, we would require:

1 A way of detecting app repackaging

2 A way of detecting app debugging

3 A way of detecting running an app on a rooted or jailbroken device or on an emulator

This information could all be tied to the API request HMAC to add further protection.

We need to be careful with root/jailbreak detection as there are methods of circumventing these on mobile platforms such as RootCloak and Hide my Root on Android, and tsProtector on iOS. A quick online search for "detect android root from app" or "detect ios jailbreak from app" yields results which developers typically adopt to protect these environments. This knowledge has been used recently by the online community to work around root/jailbreak detection in popular app store apps and unlock protected features.

If we step back for a moment and look at the previous defences objectively, there are several problems we can identify:

1 The use of static secrets or sensitive data embedded in the app, running in an untrusted environment (untrusted, as it is in control of a user rather than the company who provides the server and API).

2 The use of a dynamically-obfuscated secret in the app only known at runtime which, despite dispersion in code, still results in a single 'secret key' variable.

3 The lack of root/jailbreak, debug or instrumentation framework detection in our mobile app.

4 The lack of TLS certificate pinning implemented in such a way that it is not trivial for an attacker to simply replace the set of trusted certificates or certificate fingerprint 'pins' in order to enable a MitM proxy to steal sensitive data in-flight.

5 The use of standard, out-of-the-box, platform-provided cryptographic functions; the SHA256 HMAC in our case, which is typically not obfuscated as it is a public library API.

6 The implementation of 'secure' code in Kotlin (or indeed Java) rather than native C/C++ code. Parts where we deal with sensitive information, like API keys and HMACs are better implemented at this low level. This is because compiled Kotlin or Java results in a reasonably high-level bytecode representation that free tools do a good job of reversing back into their original source form, even with the lack of symbols.

For the ShipFast company to ensure its shipment service is protected from attacks such as those suggested in this walkthrough, the API server running in a trusted environment must ensure it can authenticate code running in untrusted environments such as mobile devices. It must authenticate the mobile app and its environment at runtime, in addition to authenticating the user and the network channel.

Although it is possible for ShipFast to develop a suitable solution themselves, this requires sophisticated mobile device and cloud server security knowledge and experience, incredibly creative and exhaustive penetration testing, the ability to analyse, identify and adapt vulnerabilities yesterday, and a great deal of time (I hear flux capacitors are good for this too). If ShipFast fails to act quickly and effectively, their profits will be hit badly and their reputation could be severely crippled.

Another option for ShipFast would be to invest in an existing solution which has solved these problems already and is prepared to protect their API and mobile app business. Approov by CriticalBlue is a product specifically designed to do just that in a unique way which:

1 Uses a unique software authentication approach to provide positive app integrity assurance without relying on historical data or suffering from false positives.

2 Does not require a static secret to be embedded in the app or the use of a system-provided cryptographic library, but instead performs a dynamic integrity check using a patented low-level approach based on many years of low-level software analysis experience.

3 Is easy to integrate and quick to deploy via a cloud service and mobile SDK for Android, iOS and hybrid mobile platforms without impacting customer experience. Nimses, a multi-million user base customer with API data-scraping problems went live with Approov in just over a week. Other use cases of customers can be seen here.

4 Is a constantly-monitored, enterprise-grade, highly-available and highly-performant proven cloud-based API and app protection service

For this defence, we will walk through the process of integrating Approov into the ShipFast mobile app and backend Node.js server and demonstrate its effectiveness in protecting the ShipFast web API from those pesky ShipRaider pirates!

Approov Integration 

The Approov integration includes adding the Approov SDK into your mobile app, registering the mobile app with the Approov cloud service, and integrating a check for the Approov token in the API server. Optionally you can also tailor the configuration defaults used by the Approov cloud service to better match your needs, for example by changing the security policy that is used to determine when the Approov cloud service can issue a valid Approov token for the mobile app.

The following steps for the Approov Integration are not required to be followed by you in in the context of this demo, because you will use the APK provided by Approov which also uses an API server provided by us. We still recommend you to familiarize yourself with them, because they will be the steps you need to follow when integrating Approov in your mobile app. So you can skip any instructions to download tools and to run commands with the Approov CLI tool.

Approov CLI Tool

During the Approov implementation we will need to use the Approov CLI tool which can be downloaded and installed by following these instructions in the Approov docs.

Mobile APP Integration

The Approov integration in the Shipfast mobile app was done in three simple steps:

  1. Approov SDK Integration.
  2. Approov SDK Usage.
  3. Mobile App Release.

Approov SDK Integration

In the repository we have already added a new Android module to our Android Studio project so we can include the Approov SDK. If you want to see how this is done, then please read the official documentation for Approov, and follow these instructions to add the Approov SDK for Android.

Now we need to download the Approov SDK from the Approov cloud service by running this command from the root of this repo:

approov sdk -getLibrary app/android/kotlin/ShipFast/approov/approov.aar

Next we will follow the Approov docs to download the Approov initial configuration:

approov sdk -getConfig approov-initial.config

NOTE: The initial configuration is not built in the SDK to provide downstream flexibility. The Approov configuration is also dynamic, because we support Over The Air (OTA) updates, enabling us to configure the Approov SDK without the need to release a new mobile app version. Even though OTA updates are possible, the initial configuration should be updated for each mobile app release by retrieving the latest one via the Approov CLI tool.

Time to add the content of the file `approov-initial.config` to the environment variable `APPROOV_INITIAL_CONFIG`. The variable is retrieved during the build for the `approov` product flavour, and you can define from where by searching the `build.gradle` file for the following line:

resValue "string", "APPROOV_INITIAL_CONFIG", "$System.env.APPROOV_INITIAL_CONFIG"

The `APPROOV_INITIAL_CONFIG` is then used as a string resource at `app/android/kotlin/ShipFast/app/src/main/res/values/strings.xml`:

<resources>
<string name="approov_config">@string/APPROOV_INITIAL_CONFIG</string
</resources>

Approov SDK Usage

Now that the Approov SDK is installed and ready we will use an Approov Framework to define its usage for the specific HTTP stack being used by the mobile app, in this case OkHttp4. The Approov Framework is retrieved from this quick start, with this command:

curl -o ./app/android/kotlin/ShipFast/app/src/main/java/io/approov/framework/okhttp/ApproovSerice.kt https://raw.githubusercontent.com/approov/quickstart-android-kotlin-okhttp/a83281885252977583b075576ad39591b87c9479/framework/src/main/java/io/approov/framework/okhttp/ApproovService.kt

To use the Approov Framework in the ShipFast app we just need a few lines of code as seen these files:

  • `app/android/kotlin/ShipFast/app/build.gradle`
  • `app/android/kotlin/ShipFast/app/src/main/java/com/criticalblue/shipfast/ShipFastApp.kt`

Find the lines of code for the Approov implementation after the comment:

// *** APPROOV IMPLEMENTATION ***

Dynamic Certificate Pinning

approov-dynamic-certificate-pinningWe also need a solution to mitigate stealing of the Approov JWT by MitM attacks. This is done by the Approov SDK which tracks the TLS connection used by the app to perform authenticated server API requests. Essentially, if the connection to the Approov Cloud Service or any protected API endpoint is proxied, the Approov authentication process can detect that and provide an invalid Approov Token. 

It is worth noting here that Approov achieves this dynamic TLS certificate pinning without you having to embed any certificate data in the app, and worry about updating it when a certificate expires or the certificate's private key is compromised. This is possible because Approov uses pins that are retrieved from the Approov Dynamic configuration as we can see in the Approov service which wraps the Approov SDK usage. Find the relevant code here:

// build the pinning configuration
var pinBuilder = CertificatePinner.Builder()
val pins = Approov.getPins("public-key-sha256")

for (entry in pins.entries) {
for (pin in entry.value)
pinBuilder = pinBuilder.add(entry.key, "sha256/$pin")
}

And afterwards used to build the OkHttp stack, as seen here:

okHttpClient = okHttpBuilder!!.certificatePinner(pinBuilder.build())

The Approov SDK will keep up with changes to your API certificate, because you can use the Approov CLI tool to change the pins where you have a compromised private key. When needed, in the next Approov Token fetch by the Approov SDK it will receive an OTA update for the Approov configuration, which in turn triggers the OkHttp stack to be rebuilt with a new set of pins, so no need to release a new version of your app, and wait for all of your customers to update it.

So why don't we use the Android network security config file to set the pins we want to trust and let the Android OS use them for allowing or not the API requests to go through? That is why it is dynamic pinning. We avoid the common challenge of other solutions where they need to play catch-up when a certificate expires or in the unexpected case when the private key used to sign the certificate is compromised and a rapid emergency fix is required. Dynamic pinning allows you to adapt to any eventuality without notice and without requiring a new release of the app.

Mobile App Release

When we are done with Approov integration and the mobile app development, we need to build the APK, sign it, and register it with the Approov Cloud Service by following the Approov documentation. On this demo, you don't need to perform any of these steps because you are using a pre-built and registered APK, that you can install on your mobile device.

For example, a debug build can be registered with Approov with this command:

approov registration -add app/android/kotlin/ShipFast/app/build/outputs/apk/debug/app-debug.apk

While a release build is registered with:

approov registration -add app/android/kotlin/ShipFast/app/build/outputs/apk/release/app-release.apk

NOTE: If we forget to register the release, the app will only get invalid Approov tokens, but we don’t need to do a new release to fix this. We just need to register the same exact APK we have released, and/or add the API domain, and within seconds all installed apps will be able to get valid Approov tokens, provided they pass the integrity checks.

API Server Integration

In this demo the ShipFast API server is running at https://shipfast.demo.approov.io/v4 so that you don't need to deploy it yourself, but if you would like to understand how Approov fits into it, keep reading. Otherwise you can skip to the next section to test the ShipFast app with the Approov Integration. The ShipFast API server code is available in the server/shipfast-api folder.

The first step is to register the API server domain with the Approov cloud service, and this is done only once per API server used by the mobile app. This is necessary to inform Approov about what API server(s) the mobile is using, so that the dynamic certificate pinning can work. Without this step the Approov cloud service doesn’t issue tokens even if you have registered the mobile app. For this demo you don't need to register the API domain because you are using the one provided by us, but just for your information the step is:

approov api -add shipfast.demo.approov.io

Implementing Approov in the API server just requires the addition of a simple JWT token check, and for peace of mind you can implement the Approov token check without blocking the requests on invalid tokens or in the absence of them initially. After you are confident that your are only blocking unauthorized traffic, you can switch to block the invalid requests.

In the Shipfast API NodeJS server we perform the Approov Token check with the help of a JWT library which is invoked here:


// Callback that performs the Approov token check using the express-jwt library
const checkApproovToken = jwt({
secret: Buffer.from(config.approov.base64Secret, 'base64'), // decodes the Approov secret
requestProperty: 'approovTokenDecoded',
getToken: function fromApproovTokenHeader(req, res) {
req.approovTokenError = false
const approovToken = req.get('Approov-Token')

if (isEmptyString(approovToken)) {
req.approovTokenError = true
throw new Error('Approov Token empty or missing in the header of the request.')
}

return approovToken
},
algorithms: ['HS256']
})

And we invoke this Approov Token check via middleware, as we can see here, and add it before the routes we want to protect, like here.

The Approov Token Secret is the base 64-encoded HS256 symmetric secret you are given when signing up for the Approov service. It is what the Approov Cloud Service uses to sign short-lived Approov JWTs, and what your API server will use to verify these tokens for protected API requests. The app does not and should not know whether these tokens are valid (i.e. signed correctly). If your app is genuine and untampered and passes the Approov authentication process, you will be issued with a valid Approov JWT; otherwise it will be an invalid one. The mobile app is simply a carrier of this token.

HINT: Do not put secrets in the app! Please! :-)

Full Approov documentation can be found in our docs.

Testing the Approov Integration

For this stage you need to install the APK for the Approov demo stage on your mobile device (no need to uninstall the previous one). Now launch the ShipFast app to see the initial screen with the Approov color scheme (green), and the mobile app should work as it did before, but this time it is authenticated by Approov:

shipfast-zero-gratuity-shipment-screen-1

Now if you now go back to try ShipRaider web interface and edit the ShipFast url to point it to the ShipFast API version v4 (https://shipfast.demo.approov.io/v4), the same one now used by the ShipFast app, you will see that ShipRaider no longer appears to work:

shipraider-dynamic-hmac-with-approov-endpoint

If we take a sneak peek into the ShipFast API server logs we can see the error message for the request Id b51d46bfe14918e (the same Id as on the alert popup):

shipfast-api-server-logs

As we can see in the ShipFast API server logs the request was rejected due to a missing Approov token header in the ShipRaider request.

The evil pirate even tried to prototype a new ShipRaider web interface to bypass the Approov integration in the ShipFast App, but all the efforts made to reverse engineer the ShipFast App with the Approov integration were unhelpful. Therefore for the first time ShipRaider was forced to leave the ShipFast drivers without a working solution to gain a business advantage at the expense of the ShipFast company and their customers.

shipraider-approov-home-screen

The Challenge (AKA Homework)

Since we have an Approov-authenticated ShipFast app now, there are a number of tests that should be performed to test the app's resilience to attack. You can perform these tests and try to request the nearest shipment, update the current shipment, etc, all of which is now protected by Approov, therefore an invalid Approov Token is issued each time you try to make any request that is not from the same APK you have registered with the Approov cloud service, thus the API server will be able to verify the request as untrusted.

Some tests you can try out:

1 Try modifying ShipRaider to include a fake Approov token, or use the Linux cURL command or equivalent to perform fake API requests to the ShipFast API server protected by Approov. In other words, try using the ShipFast API without the genuine Approov-registered app. Be as sneaky as you can - attackers will be!

2 Take the Approov-registered APK, unzip it, try changing something such as adding the debug flag to the Android manifest as we did in a previous attack, re-sign it, install it and run it on an unrooted device. Even without debugging, the app modification of the APK will be detected by Approov as the signature has changed.

3 Follow the previous step, and attach a debugger to the app. Approov detects the presence of the debugger.

4 Try running the original Approov-registered app, unaltered, on a rooted device, with the security policy properly configured to not allow rooted devices.

5 Try running the original Approov-registered app, unaltered, on an emulator, with the security policy properly configured to not allow emulators.

6 Try proxying network traffic from a device to the ShipFast API server. Use an SSL proxy (MitM attack) such as Charles or mitmproxy to snoop the API requests.

7 Try attaching an instrumentation framework such as Frida or Xposed to the Approov-registered app. Install a TLS certificate 'unpinning' framework to try to circumvent MitM mitigation.

IMPORTANT: Approov's app and API protection features can be enabled and disabled by changing the security policies with the Approov CLI tool. These policies cannot be changed when running a demo, just when using a trial or production account. So this means that in a demo the policies may not necessarily all be running during your penetration tests, so please check the status of your securities policies before attempting to elevate our engineering team's heart rate considerably. In all seriousness though, we are confident that Approov provides highly-competitive app and API protection (and we have the customers to back that statement up) so if you do have any concerns or queries, please get in touch using the contact us page.

Wrap Up

I sincerely hope that you have enjoyed this walkthrough and found it useful, and thank you for reading it!

I hope that it is clear that API protection needs not only to authenticate the user with techniques such as OAuth 2.0 and Open ID Connect, and authenticate network traffic using Transport Layer Security and certificate pinning; but also authenticate the running mobile application. The combination of these provide a crucial synergy of API protection techniques.

Now in ShipFast you know who is talking with the API server, and you also know what is talking with it, but can you say the same for your own API server and Mobile App?

Simon Rigg