Thu 18 January 2018 By Simon Rigg
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 attack scenario and what is required to effectively 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.
The Third Attack
If we MitM the API traffic again, we still see the "SF-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 the tools "apktool", "dex2jar" and "JD-GUI". Looking around, we find the following:
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. In order 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:
We can then resign the APK and run it on an emulator or device and debug it in order 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.
There are other such frameworks to modify apps on rooted/jailbroken devices such as the Xposed framework for Android. I recommend you check out the video on YouTube to see how Xposed is used to break TLS certificate pinning.
In this walkthrough though, we will choose the option to enable app debugging, unzipping the APK, adding the debug flag to the manifest, zipping the APK and resigning it before running it on an emulator.
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 replicated it in the rogue ShipRaider website.
To see this process in action, we have prepared a short video:
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:
If things are configured correctly, you should now be able to run ShipRaider against the ShipFast server and grab those bonus shipments, even although we have migrated to using a dynamic secret key for the HMAC used to sign API requests.
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 repacked and resigned by someone else (I'm looking at you, ShipRaider pirates!).
As pointed out in the third attack, it is possible for an attacker to 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 required:
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 on popular app store apps and unlock protected features.
If we step back for a moment and look at the previous defenses 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 cumulates to 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' and 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 public library API
6 The implementation of 'secure' code in Kotlin (or indeed Java) rather than native C/C++ or assembly which results in a reasonably high-level bytecode representation 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, incredible creative and persevering 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 fail 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.
4 Is a constantly-monitored, enterprise-grade, highly-available and highly-performant proven cloud-based API and app protection service
For this defense, 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!
The steps to integrate Approov into a mobile app and backend API are really simple. If you do not believe me, check out our recent press release and you will see a real case of this in action! The steps required are:
1 Sign up for an Approov 30-day free trial at https://approov.io/demo-reg.html where you will receive everything you require for this service
2 Download the necessary mobile app SDK and add it to your app
3 Add some code to your mobile app to trigger the app authentication process which will communicate with the Approov Cloud Service and issue you with an authentication JSON Web Token (JWT) similar to the one used by Open ID Connect (OIDC) for authenticating users
4 Send that token along with any web server API calls which require your app to be authenticated
5 Register your mobile app with the Approov Cloud Service so we can verify it at runtime and ensure it is present and unaltered
6 Add a simple JWT check to your server code to verify the Approov JWT in authenticated requests, using one of the many available industry-standard JWT libraries
In this walkthrough we have already added a new Android module to our Android Studio project so we can include the Approov aar SDK. The steps to achieve this can be found in the Approov User Guide
First, we add the code to the ShipFast app in the form of an OkHttpClient request "Interceptor" app/android/kotlin/ShipFast/app/src/main/java/com/criticalblue/shipfast/ApproovRequestInterceptor.kt. This makes Approov pluggable so, if we include this interceptor, the Approov authentication process will be performed for API requests and the resulting Approov JWT will be added to an "Approov-Token" request header as follows:
This request interceptor can be included in our OkHttpClient as follows:
We also need a solution to mitigate stealing of the Approov JWT by MitM attacks, so we provide a custom Hostname Verifier which ensures the TLS connection used by the app to perform authenticated server API requests is tracked by Approov. Essentially, if the connection to the Approov Cloud Service or any protected API endpoints is proxied, the Approov authentication process can track that and provide an invalid 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. Approov will keep up with changes to your API certificate.
The custom Hostname Verifier is provided in the app/android/kotlin/ShipFast/app/src/main/java/com/criticalblue/shipfast/ApproovHostnameVerifier.kt file.
This hostname verifier can be included in our OkHttpClient as follows:
If you are unfamiliar with the concept of hostname verification this file may look a little daunting, but I will attempt to explain it a little here. A hostname verifier verifies a particular host used in a particular HTTPS connection. Before the "HTTP" part, layer 7 of the OSI model, the "S" part, layer 4 of the OSI model, must first be established. That is, Transport Layer Security must be applied.
You can read many documents about how this uses asymmetric cryptography and X.509 certificates in a handshake to establish a symmetric shared key between the client and server used to encrypt, decrypt, sign and verify network traffic; but prior to this phase, the hostname verifier allows apps to customise the validation process of the host server and we use that to synchronise the current network session for an API request with Approov so the Approov token's validity can reflect the validity of the host server certificate. If we are talking to the real thing, we should get a valid Approov token. If API traffic is intercepted by a proxy decrypting and re-encrypting API traffic so it can spy as a MitM, then we should get an invalid Approov token.
So why use a hostname verifier and not just set all this up front as a 'pin set' in the trust manager and then use that for API requests? That is why it is dynamic pinning, and how we avoid the challenge of playing catch-up as many certificate pinning solutions are faced with 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. In addition to this, dynamic pinning is more secure as it doesn't suffer from the fact that static in-app certificate files can be replaced and the app repackaged to use a rogue certificate pin set.
Second, when we are done with the app, we need to build the APK, sign it, and register it with the Approov Cloud Service by following the Approov documentation
Finally, we need to add the server side changes which will be performed using our request authentication express middleware server/shipfast-api/auth.js:
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 web service 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 and not expired). 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! :-)
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: Also modify the "config.currentDemoStage" variable and the "config.approovTokenSecret" variable in the server's "demo-configuration.js" file server/shipfast-api/demo-configuration.js:
Restart everything again, the app should continue to work normally, but this time it is authenticated by Approov. If it fails to work, check the following:
1 You have included your Approov token secret in the ShipFast server
2 The ShipFast app has been signed and registered with the Approov cloud service and it is the registered app you are running in the emulator (or on a device) and not some variant
3 The emulator (or device) has not been rooted and does not have instrumentation frameworks such as Xposed or Frida installed
4 The network connection from the emulator (or device) to the ShipFast server and Approov Cloud Service is not running through a MitM proxy
5 The ShipFast app is not being debugged (it is okay to build for debug rather than release, but a debugger must not be attached when the app is running)
Full Approov documentation can be found at https://approov.io/docs/
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 should now be authenticated by Approov:
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 server. 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.
5 Try running the original Approov-registered app, unaltered, on an emulator.
6 Try proxying network traffic from a device to the ShipFast 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 contacting us. These features will not all necessarily be running during your penetration tests, so please check the status of your Approov Cloud Service first 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 form at https://approov.io/contact-us.html or, depending on the situation, https://www.criticalblue.com/careers.html.
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.
You know who is talking to you. You know how they are talking to you. Do you know what is talking to you?
We are CriticalBlue: https://www.criticalblue.com
We protect mobile API businesses with Approov: https://approov.io