This blog post is part 3 of our “A look at common mobile security issues on cross platform frameworks” series. Please see part 1: Introduction that lays the ground and part 2: Apache Cordova which considered the concepts presented in part 1 with regards to Apache Cordova.
Xamarin is a cross-platform framework that allows developers to create mobile apps for Android, iOS and WUP using .NET. In this post we will build a simple app to evaluate the security features provided by the framework in terms of SSL/TLS communication and local storage, just like we did in our previous post for the Apache Cordova framework.
Again, we will target the iOS and Android platforms but this time we will develop our app using C#. The app will download a secret code from a server and store it locally, so let’s begin!
Before we write the logic to make the request, let’s add a button in MainPage.xaml. Since we are not sure how to do things properly yet, we label the button “Insecure Request”, and we link it to the “InsecureRequest” function, just in case…
<?xml version="1.0" encoding="utf-8" ?> |
MainPage.xaml file
Time to write the actual code. Checking the documentation, HttpClient seems the way to go in order to perform our request:
... |
MainPage.xaml.cs
Ok so far so good. However, before giving the code a try, we need to mention an important detail about HttpClient: Xamarin allows us choose the specific implementation for each platform, i.e. on Android we can choose to translate HttpClient to AndroidClientHandler or Managed, while on iOS we’ve got Managed, CFNetwork and NSUrlSession. Despite AndroidClientHandler and NSUrlSession being strongly advised (this is due to support for the latest TLS versions), we are going to try them all to see if there is any difference in terms of our SSL Pinning implementation.
Let’s set up a local proxy and the related details on our devices and run the code!
The iOS version of the app
The request intercepted with a local proxy
As we can see, as soon as we click on the Insecure Request button of the iOS application, we are able to obtain the HTTPS request. The Android version won’t allow the request to be made, and this is because of the control introduced with the API Level 24 ((https://android-developers.googleblog.com/2016/07/changes-to-trusted-certificate.html).) discussed in the previous post. Effectively, this control stops our request because it’s not signed by one of the trusted system CAs embedded within the device. Just as in part 2 with Apache Cordova, we are able to bypass this restriction by adding BurpSuite’s, our intercepting proxy, custom CA certificate to Android’s system CAs. In realitythis simulates an attacker compromising a CA certificate, i.e. an attacker that managed to gain physical access to the device for a couple of minutes, a state actor that was issued a root CA certificate, etc.
Even selecting different implementations, the application sent out the request despite it being unable to verify the server identity (however, we noticed some differences, such as some implementations seem to ignore the system proxy settings by default, even if this wouldn’t prevent interception of the request).
We are now committed to find the best way to store our secret code on the device, since we really really need it.
The quickest way we find to store a small bit of information is by using Preferences:
private void InsecureStorage(string secret)
{
Preferences.Set("secret_code", secret);
Console.WriteLine("Secret Code Stored: "+ secret);
}
Quick and easy but let’s see the result of this when used in our application:
Not quite the result we wanted to see...our secret code is stored in cleartext in the application folders which, despite being in a private location of the file system, do not provide adequate security.
Also, many of you might have also noticed a problem with the information given away within the logs! Our secret code wouldn’t be too secret if it gets stored in the device logs..
So the application works, but in an insecure way. Let’s try to find out how we can fix this with the features provided by the framework.
The answer to HTTP interception lies in the HttpMessageHandler. Through this object we can define our own routine to validate the certificate, which is pretty cool along with what we need!
Since we are now expecting to have this working properly, we add a second, more optimistic button titled ‘Secure Request’:
<?xml version="1.0" encoding="utf-8" ?> |
MainPage.xaml
And we then start to code the SecureRequest function:
... |
MainPage.xaml.cs
We notice how things are not too different so far - we created a handler project, assigned the function SSLCertificatePinning as a custom validation callback, and then passed the handler to our HttpClient object. Now we just need to define SSLCertificatePinning:
private static bool SSLCertificatePinning(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
return CertificateKey == certificate?.GetPublicKeyString();
}
This could have some more advanced logic, e.g. validating the certificate chain, but for the purpose of this post, this will do. What’s happening exactly? The routine simply returns True if the public key of the certificate provided by the server during the SSL Handshake matches the one that we know, in this case stored in CertificateKey.
So let’s make sure that the right public key is assigned to CertificateKey before the validation:
|
Testing with the inline intercepting proxy gives us:
default 13:22:23.881408+0100 XamarinExample.iOS The SSL connection could not be established, see inner exception.
default 13:22:23.881408+0100 XamarinExample.iOS The SSL connection could not be established, see inner exception.
Same setup as before, but this time our local proxy cannot intercept the request...a quick look at the devices’ logs confirms that our implementation prevented the man-in-the-middle attack! Again, we tried the different HttpClient implementations on both Android and iOS and were happy to see that it worked nicely in every case.
Also, we made the application stop producing local logs!
Now let’s move to tackling the security of our secret code while at rest. Fortunately, a more robust option is just as easy as Preferences (the less secure one): by using SecureStorage instead of Preferences, we can obtain a much different result. Let’s take a look how that looks both in code and on each device:
private async Task SecureStorageAsync(string secret)
|
Android:
iOS KeyChain Entry:
{"Creation Time" : "Apr 08, 2020, 03:30:03 GMT+1",
"Account" : "secret_code",
"Service" : "com.companyname.XamarinExample.xamarinessentials",
"Access Group" : "F9P2F3WQSF.com.companyname.XamarinExample",
...}
Much better! We’ve now made use of the secure storage features provided by the OS.
Just like for the Cordova application, we’re going to briefly discuss source code obfuscation.
When we analyze the Android or iOS app packages, we notice that a number of DLL libraries reside within the assemblies folder. Within these DLLs we will find the actual code for our application logic.
This means that even if we apply an Android-specific obfuscation solution to our app, it won’t affect the code within the DLLs, which could be retrieved with a .NET decompiler:
Instead, in order to achieve proper source code obfuscation, a technology-specific obfuscator should be used, just like the Android Cordova framework needs a Javascript obfuscator, Xamarin requires a .NET one!
Just as we did for the Cordova application, in this post we quickly built a Xamarin app proving that cross-platform frameworks can indeed speed up the development of a mobile app for different platforms.
An insecure version of the app was intentionally created but we saw how the secure version of the same app only required a modest amount of extra effort:
This post concludes our “A look at common mobile security issues on cross platform frameworks” blog series. We hope you enjoyed reading it as much as we enjoyed compiling it.
Recent cybersecurity breaches demonstrate that solely relying on Penetration Testing when evaluating an organisation's cybersecurity posture is a thing of the past. OrionX offers the most comprehensive security services to stop adversaries disrupting your business.
Giuliano is an experienced penetration tester who has worked in the security consultancy field for more than 10 years across several countries in Europe on a wide range of systems and products. His expertise ranges from web applications to mobile and infrastructures, including specific hardware and software testing when collaborating on PCI P2PE assessments.
This blogpost is part 2 of our “A look at common mobile security issues on cross platform frameworks” blog post series. To read part 1 that lays the ground the rest 2 build upon please visit part 1: ...
In the last few years we have seen cross-platform development frameworks become more and more common, with a consequent increase in“hybrid” mobile applications submitted to App stores and to ...