OrionX Blog

Mobile security issues on cross platform frameworks - Part 3: Xamarin

Written by Giuliano Fasto | 5/16/23 7:31 PM

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.

Take 1 - Insecure version

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!

HTTPS request

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" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                    xmlns:d="http://xamarin.com/schemas/2014/forms/design"
                    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                    mc:Ignorable="d"
                    x:Class="XamarinExample.MainPage">
<StackLayout>
<Button Text="Insecure Request" HorizontalOptions="Center" VerticalOptions="CenterAndExpand" Clicked="InsecureRequest"/>
</StackLayout>
</ContentPage>

MainPage.xaml file



Time to write the actual code. Checking the documentation, HttpClient seems the way to go in order to perform our request:

...
private async void InsecureRequest(object sender, EventArgs e)
{
   try
  {
       var client = new HttpClient();
       var request = new HttpRequestMessage()
             {
               RequestUri = new Uri("https://www.foregenix.com"),
               Method = HttpMethod.Get,
             };
       Console.WriteLine("About to make the request");
       var response = await client.SendAsync(request);
       var secretCode = (int) response.StatusCode;
       Console.WriteLine("Secret Code received: "+secretCode);
       if (response.IsSuccessStatusCode)
       {
         //store the secret code
       }
   }
   catch (Exception ex)
  {
    Console.WriteLine(ex.Message);
   }
}

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).

Storing the secret code

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

Take 2 : Secure version

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.

Dealing with HTTP interception

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" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                    xmlns:d="http://xamarin.com/schemas/2014/forms/design"
                    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                    mc:Ignorable="d"
                    x:Class="XamarinExample.MainPage">
<StackLayout>
<Button Text="Insecure Request" HorizontalOptions="Center" VerticalOptions="CenterAndExpand" Clicked="InsecureRequest"/>
<Button Text="Secure Request" HorizontalOptions="Center" VerticalOptions="CenterAndExpand" Clicked="SecureRequest"/>
</StackLayout>
</ContentPage>

MainPage.xaml

And we then start to code the SecureRequest function:

...
private async void SecureRequest(object sender, EventArgs e)
     {
        try
        {
             HttpMessageHandler handler = new HttpClientHandler
            {
              ServerCertificateCustomValidationCallback = SSLCertificatePinning
             };
        var client = new HttpClient(handler);
        var response = await client.SendAsync(request);
       if (response.IsSuccessStatusCode)
       {
         //store the secret code
       }
   }
   catch 
  {
   }
}

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:

static readonly String CertificateKey= "04C0EF749ECA3EA9855A02F409DE68B698CC2C208BC79A0FB82C499E4F3A47794C7FF0CBCFF7C25AA25523B3BC82075A3BD9A67155257038A9E9B440061176E58B";

 

Testing with the inline intercepting proxy gives us:

IOS Console:

default 13:22:23.881408+0100 XamarinExample.iOS The SSL connection could not be established, see inner exception.

Android logcat:

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!

Secure Storage

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)
{
    try
   {
      await SecureStorage.SetAsync("secret_code", secret);
   }
    catch
   {
     await DisplayAlert("Error", "Your device may not support secure storage", "OK");
    }
}

 

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.

Source Code Obfuscation

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!

Conclusion

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: 

  • A HttpMessageHandler can be used to define simple to more complex validation routines against the SSL Certificate of our server, effectively implementing SSL Certificate Pinning
  • SecureStorage allows us to encrypt stored information with a minimal effort
  • Logs can be generated and stored on the device only when actively created by the developers. A sound Secure Development Lifecycle process would make sure that no logs are stored on the device by production applications.

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.