How to Protect Against Certificate Pinning Bypassing
Paulo Renato
Posted on February 6, 2020
In my previous article, we saw how to bypass certificate pinning within a device you control and, as promised, we will now see how you can protect yourself against such an attack.
In this article you will learn how to use a mobile app attestation service to protect your API server from accepting requests that come from a mobile app where certificate pinning has been bypassed. This means that even though the attacker has bypassed the certificate pinning, he will not be able to receive successful responses from the API server. Instead, the server will always return 401 responses, thus protecting your valuable data from getting into the wrong hands.
To demonstrate how to protect against certificate pinning bypassing we will use the same Currency Converter Demo mobile app and API server that was used in the previous article, and we will enhance the security of both the mobile app and the API server, by adding a Mobile App Attestation service to them.
Other ways to break Certificate Pinning
Before I go into details of how the Mobile App Attestation works, I would like to remind you that in the previous article I repackaged the mobile app to bypass certificate pinning. However, other tools exist, such as Frida or xPosed, which can be used to bypass certificate pinning during runtime, therefore not requiring repackaging the mobile app.
If you are interested in those alternative methods, you can see how it’s done with xPosed in this video:
The Role of Mobile App Attestation
Before we dive into the role of a Mobile App Attestation service, we first need to understand the difference between what and who is accessing the API server. This is discussed in more detail in this article, where we can read:
The what is the thing making the request to the API server. Is it really a genuine instance of your mobile app, or is it a bot, an automated script or an attacker manually poking around your API server with a tool like Postman?
The who is the user of the mobile app that we can authenticate, authorize and identify in several ways, like using OpenID Connect or OAUTH2 flows.
The role of a Mobile App Attestation service is to authenticate what is sending the requests, thus only responding to requests coming from genuine mobile app instances and rejecting all other requests from unauthorized sources.
In order to know what is sending the requests to the API server, a Mobile App Attestation service, at run-time, will identify with high confidence that your mobile app is present, has not been tampered/repackaged, is not running in a rooted device, has not been hooked into by an instrumentation framework(Frida, xPosed, Cydia, etc.), and is not the object of a Man in the Middle Attack (MitM). This is achieved by running an SDK in the background that will communicate with a service running in the cloud to attest the integrity of the mobile app and device it is running on.
On a successful attestation of the mobile app integrity, a short time lived JWT token is issued and signed with a secret that only the API server and the Mobile App Attestation service in the cloud know. In the case that attestation fails the JWT token is signed with an incorrect secret. Since the secret used by the Mobile App Attestation service is not known by the mobile app, it is not possible to reverse engineer it at run-time even when the app has been tampered with, is running in a rooted device or communicating over a connection that is the target of a MitM attack.
The mobile app must send the JWT token in the header of every API request. This allows the API server to only serve requests when it can verify that the JWT token was signed with the shared secret and that it has not expired. All other requests will be refused. In other words a valid JWT token tells the API server that what is making the request is the genuine mobile app uploaded to the Google or Apple store, while an invalid or missing JWT token means that what is making the request is not authorized to do so, because it may be a bot, a repackaged app or an attacker making a MitM attack.
A great benefit of using a Mobile App Attestation service is its proactive and positive authentication model, which does not create false positives, and thus does not block legitimate users while it keeps the bad guys at bay.
The Mobile App Attestation service already exists as a SaaS solution at Approov . The solution supports SDKs for several platforms, including iOS, Android, React Native and Cordova. To deploy, a small check in the API server code to verify the JWT token issued by the Approov cloud service is needed. This check is how the API server authenticates what is making the request.
So let’s see how we can introduce Approov into the Currency Converter Demo mobile app in order that the API server knows which requests it should allow and those it should deny.
Implementing Approov
In order to implement Approov into the Currency Converter Demo mobile app and its API server, you need to clone the project from Github:
git clone --branch 0.4.0 https://github.com/approov/currency-converter-demo.git && cd currency-converter-demo
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.
During the Approov implementation we will need to use the Approov CLI tool which can be downloaded and installed by following these instructions, and an admin and developer token will be provided so that you can use it.
API Server
Implementing Approov in the API server just requires the addition of a simple JWT token check.
For a smooth transition of your mobile app and API server to Approov we will create a V2 for each endpoint we want to protect, where we will check the Approov Token. We recommend that after you release the mobile app with Approov, the previous versions of it should be deprecated as soon as possible, and the API server endpoints supporting them removed.
For peace of mind you can start to implement the Approov token check without blocking the requests on invalid tokens or in the absence of them, but after you are confident that your are only blocking unauthorized traffic, you can switch to block the invalid requests.
To implement Approov in a Python Flask server you just need to create a decorator, like the approov_token_decorator.py, and afterwards in each endpoint you want to protect with Approov you just add the @approov_token_decorator.check_approov_token annotation.
Mobile App
The Approov integration in your mobile app can be done in three simple steps:
- Approov SDK Integration.
- Approov SDK Usage.
- Mobile App Release.
Approov SDK Integration
First we need to download the Approov SDK from the Approov cloud service with this command:
approov sdk -getLibrary approov\_sdk.aar
Now, from the official documentation for Approov, we can follow these instructions to add the Approov SDK for Android.
Next we will follow the Approov docs to download the Approov initial configuration:
approov sdk -getConfig app/src/main/assets/approov-initial.config
The initial configuration is not built in the SDK in order to provide downstream flexibility. The Approov configuration is also dynamic, because we support Over The Air (OTA) updates, which enables us to configure the Approov SDK without the need to release a new mobile app version. Every app will include an initial Approov configuration which is obtained and updated as necessary via the Approov CLI tool.
Approov SDK Usage
The Approov SDK needs to be initialized when your App starts, and this same instance reused until the app is closed. The Approov tokens must be added to every request made to the API server, and they can be fetched asynchronously or synchronously.
The OTA updates to the Approov SDK Dynamic Configuration will be done via the response for each Approov Token fetch request, and you can see how its done here.
For a complete overview of the code used to implement Approov in the Currency Converter demo app we can take a look at the Approov package. You can use this as inspiration or as a starting point to integrate Approov into your own mobile app.
Mobile App Release
Let's build a release:
./bin/build-release.bash
Now that we have an APK file it is time to install it on our device:
adb install app/build/outputs/apk/release/app-release.apk
If we launch the mobile app now and try to make a conversion we will get an error:
This happens because the APK has not been registered yet with the Approov cloud service. As a result, each time we try to do an attestation an invalid Approov token is issued, thus the API server rejects the request with a 401 response.
Let’s register the APK in the Approov cloud service:
$ approov registration -add app/build/outputs/apk/release/app-release.apk -expireAfter 600s
registering app Currency Converter Demo
LokZ+7MJdkHGINCwXAHo9JBwIKXE7UqtikeDbuhDt7I=com.criticalblue.currencyconverterdemo-1.0\[1\]-1212 SDK:Android(2.0.5)
registration successful, expires 2019-09-13 17:48:21
So if you try to repeat the currency conversion immediately you may see the same error, just because a short time is needed to propagate the changes. You can either wait or proactively relaunch the mobile app and the changes will be updated. If you then retry the currency conversion you will get a successful response:
You may have noticed the -expireAfter
flag which ensures that APK registration will only be recognised for 600 seconds after it is first registered. This is very useful when you are in the development phase and you don’t want to manage the de-registration of temporary versions of the APK with the Approov cloud service. Of course, in production you should not use the -expireAfter
flag.
As you can see implementing Approov is very easy and it delivers a frictionless experience for your end customers.
Approov Certificate Pinning Protection in Action
Now that we have implemented Approov in the mobile app and API server we are ready to see how Approov protects us against attempts to bypass certificate pinning. For consistency with the previous article, we will take the same approach, that consists to unpack the APK, change the code, repackage it, and reinstall it on our device. Always remember that this is not the only way of bypassing certificate pinning, thus I challenge you to also try Frida or Xposed as a homework assignment.
Unpack and Modify the APK
To unpack the APK we can use this helper script:
$ ./bin/unpack-apk.sh
… omitted output
---> APK decode into: .local/apktool/decoded-apk
Now that the APK is decompiled, its possible to inspect what it does and to modify its behavior. The decompiled code is in Smali code, not Java, and therefore requires you to be familiar with Smali to be able to figure out what is going on.
Attackers of Android apps are usually fluent in Smali code and they can snoop around it and eventually find what they need to change in order to disable certificate pinning. But wait, as you may remember in the previous article, certificate pinning was disabled just by editing the network security config file. So why can’t it be done in the same way now? Well, that’s because Approov handles certificate pinning dynamically, and the initial pin is shipped with the Approov initial configuration, thus the network security file is not required for this purpose.
Aproov Dynamic Pinning overview:
Given that targeting the network security config file is not an option for the attacker any more, the next approach they might take is to find the Smali code that handles certificate pinning, remove it, repackage the app, and then launch a MitM attack to intercept the traffic between the mobile and the API server.
The process for finding the Smali code is out of scope for this article, but I can tell you that the attacker would eventually end up removing this line of Smali code:
invoke-virtual {p1, v0}, Ljavax/net/ssl/HttpsURLConnection;->setHostnameVerifier(Ljavax/net/ssl/HostnameVerifier;)V
If you are curious to know what code it relates to in the Java side, then look it up here:
urlConnection.setHostnameVerifier(pinningHostnameVerifier);
Repackage and Reinstall the APK
Now that the Smali code has been modified to remove the part that handles certificate pinning, it's time to repackage the mobile app and reinstall on the device.
We repackage the mobile app with the help of this helper script:
./bin/repackage-apk.sh
And then we install it on our device with:
adb install .local/apktool/decoded-apk/repackaged-and-signed.apk
Approov Protection in Action
Now that he APK have been repackaged and reinstalled on the device we can launch it to try a new currency conversion, and we can see that the API server is refusing to fulfill the request by returning a 401 response to the mobile app:
If we inspect the Logcat in Android Studio we will be able to see an entry that shows us the Approov Token which was sent to the API server:
2019-09-17 12:18:48.196 11446-11512/? I/VOLLEY\_HTTP\_REQUEST: APPROOV TOKEN: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkaWQiOiJWcGJ2YUh1Rnp0eGhzeDQ3Mlc0VmdnPT0iLCJleHAiOjE1Njg3MTk0MjYsImlwIjoiODAuMjI5LjExMy4yMjAifQ.xS-iNbfVlveTiZ\_H8E2ZT2GF8suKvziauUoD79pYpoM
If you now copy and paste the above Approov Token and use the Approov CLI tool to inspect it, you will find that it is an invalid one:
$ approov token -check eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkaWQiOiJWcGJ2YUh1Rnp0eGhzeDQ3Mlc0VmdnPT0iLCJleHAiOjE1Njg3MTk0MjYsImlwIjoiODAuMjI5LjExMy4yMjAifQ.xS-iNbfVlveTiZ\_H8E2ZT2GF8suKvziauUoD79pYpoM 1 ↵
invalid: {"did":"VpbvaHuFztxhsx472W4Vgg==","exp":1568719426,"ip":"80.229.113.220"}
The API server saw an invalid Approov token in the request, thus it returned a 401 response, instead of returning the 200 response with its respective data. The token is invalid because the APK has been modified, and this is how Approov protects your data from being breached. Put another way, only genuine mobile app instances can communicate with your API endpoints, not repackaged apps, bots, or to someone trying to probe the API server from a tool like Postman.
To illustrate the depth of security of your API server, if you had just repackaged the APK without actually changing anything in it, the Approov token issued by the Approov cloud service would still have been invalid. This is because the repackaged app will have a different DNA signature to the original app you registered with the Approov cloud and would therefore not pass the Approov authentication process. It should also be noted that if you had attached Frida, Xposed or some other instrumentation framework to disable certificate pinning at runtime, this scenario would also result in a failed attestation from the Approov cloud service.
You might consider a couple of other ways to beat Approov. Firstly you could imagine that the attacker could take Approov out of the app, or perhaps write a script to generate correctly formed API requests. However, in both cases the attacker would not be able to provide an Approov token to the API server and so the API requests would be refused. Secondly, you might think that the attacker could spoof the Approov tokens, but unfortunately this is not possible because they are signed JWT tokens with a very short lifetime, and the secret used to sign them is only known by the Approov cloud service and by the API server.
Summary
Protecting against certificate pinning bypass is done by implementing the Mobile App Attestation concept which allows the API server to detect with high confidence if what is making the request is a genuine mobile app instance or not. This approach will block attackers from accessing data they are not meant to have. The data is protected, regardless if the attempt is made via a script, a bot, a tampered mobile app, or by attaching a run-time instrumentation framework, like xPosed or Frida.
The Mobile App Attestation approach used here is a proactive one, since what is making the request is authenticated before any requests to the API server have been made. Also, because the authentication decision is taken outside of the mobile app itself, it cannot be reverse engineered or circumvented without being detected. This contrasts with traditional API server defenses that either parse requests in realtime and try to decide if they can be trusted or not, or they search for patterns or signatures which have previously been found to be bad. Either way, these approaches are prone to false positives and false negatives, thus requiring constant monitoring in order to relax or increase the severity of the policies being used. The Mobile App Attestation concept used by Approov removes both the doubt about the authenticity of the API traffic and the burden of constant monitoring and fine tuning of policies being used.
Implementing Approov in your mobile app and API server is a straightforward process which does not impact your development and deployment processes, illustrated by the words of one of our fintech customers:
“Approov was a natural choice at the end of our research because of the extensive capabilities of the product. It required minimal integration work while providing maximum security and flexibility. The similar solutions we found were too rigid and required too much initial integration work.”
Posted on February 6, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.