How to force Android devices to communicate securely?
Secure network communication is one of the most critical aspects of mobile application development. In this article, you will find tips on how to do it the right way.
TL;DR
- Avoid using plaintext HTTP, use HTTPS instead.
- Network Security Configuration is a great and simple tool, do not hesitate to use it.
- Remember about Android fragmentation, take security measures to protect all supported APIs.
- For security-sensitive applications, use certificate pinning.
- Always follow best practices while developing mobile apps. Take a look at Guidelines on mobile application security – Android edition
About Networking
The vast majority of Android applications need to communicate with the backend. This is obviously a very important security requirement that such communication is properly protected. Otherwise, attackers can easily intercept the communication, which usually includes secrets like credentials or session identifiers. In both cases, a user account is compromised.
Having said that, during penetration tests I am still finding some applications which do not use TLS or do not properly verify server certificates, which results in the MITM attack.
Below, an example of intercepted credentials using monitor mode in Wireshark:
Disable HTTP, for your own peace of mind
Android 7+ supports Network Security Config (NSC), which is an ultimate tool to manage networking configuration. By default, for Android 9+ cleartext communication is disabled, but for older versions of Android developers need to explicitly disable insecure communication. First of all, NSC must be created, otherwise, default settings will be applied.
Typically, NSC is located under the below-mentioned path:
res/xml/network_security_config.xml
And also needs to be defined in the Android Manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.lbobrek.secretskeeper">
<application
android:networkSecurityConfig="@xml/network_security_config"
android:allowBackup="false"
Now we can easily create global policies for applications – the great thing is that those policies will work for any library we use to establish HTTP/TLS connection.
The first one we should define is disabling HTTP connection and allowing only system CAs.
Android 8+ by default installs user certificates into separate userspace. If for some reason we want to also trust such certificates, we can add <certificate src=”user”/> entry, although it is not recommended for security-sensitive applications.
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
</network-security-config>
In case you really badly need to use HTTP, you can override global configuration for a single domain, which can be done as follows:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">securing.biz</domain>
</domain-config>
</network-security-config>
Unfortunately, the above-mentioned technique won’t work for API 23 and lower, but it is definitely worth implementing. As of August 2020, over 75% support NSC, according to Google’s platform distribution dashboard.
HTTPS communication on Android
TLS protocol provides secure, encrypted communication. In most cases libraries for establishing HTTPS connection properly verify CAs validity. For instance, if you use the NSC or OkHTTP client you can rest assured that the certificate will be validated without programming any additional code. Despite the fact that default configuration, in this case, is always the best solution, sometimes developers get over-creative and produce completely insecure code.
During one of the penetration tests, I encountered a really impressive example of developer’s creativity. Instead of using default TrustManager, developers initialized an empty TrustManager and then used it to establish TLS connection:
SSLContext localSSLContext = SSLContext.getInstance("TLS");
localSSLContext.init(null, new TrustManager[]
{ new EasyX509TrustManager(null) }, null);
return localSSLContext;
As a result, each certificate was accepted, so intercepting cleartext communication was almost as easy as without implementing TLS at all.
The lesson from this chapter is that when implementing TLS always go for mature solutions, for example:
URLConnection class
URL url = new URL("https://securing.biz");
URLConnection urlConnection = url.openConnection();
InputStream in = urlConnection.getInputStream();
copyInputStreamToOutputStream(in, System.out);
OkHTTP client
Request request = new Request.Builder()
.url("https://securing.biz")
.build();
client.newCall(request).execute();
Certificate Pinning on Android devices
Certificate pinning is an additional mechanism, which prevents establishing a communication channel with an untrusted server. So far, we have only verified whether the server has a valid certificate, meaning that it is signed by a trusted CA. Nowadays, it is fairly easy to obtain a valid certificate, for instance by using Let’s Encrypt (https://letsencrypt.org/). Another risk is that the attacker somehow manages to add his certificate into the Android Trust store. In such a case, the TLS connection also will be identified as secure.
Certificate Pinning is a technique that prevents the above-mentioned risk. It relies on “pinning” certificate characteristics directly into the application source code. Then, during a TLS connection attempt, the application may check if the server presents the expected (“pinned”) certificate. There are several variants of certificate pinning, it may verify the whole certificate or its public key. More info regarding certificate pinning can be found here: https://owasp.org/www-community/controls/Certificate_and_Public_Key_Pinning.
Android allows a really straightforward and effective way of implementing certificate pinning, which involves previously described Network Security Config. The implementation looks as follows:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
<domain-config>
<domain includeSubdomains="true">securing.biz</domain>
<pin digest="SHA-256"><Certificate signature></pin>
</pin-set>
</domain-config>
</network-security-config>
The only pitfall here is that NSC is supported only by Android 7+, so if your target includes lower Android versions, then you need to implement pinning in a different way. I would strongly advise you to follow guidelines provided by the library you use for TLS. For instance, OkHTTP you should use CertificatePinner class:
CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add(“securing.biz”, "sha256/<Certificate signature>")
.build();
OkHttpClient client = OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build();
Request request = new Request.Builder()
.url("https://securing.biz")
.build();
client.newCall(request).execute()
Properly implemented certificate pinning will prevent from establishing a TLS session with a given domain, unless the certificate “pin” matches the one predefined in the code.
Note that the certificate pinning mechanism works locally, on the device which cannot be considered as trusted. There are several ways of bypassing certificate pinning mechanism on rooted devices.
Nevertheless, I strongly encourage all the developers to implement this mechanism for their application and follow all the other good practices regarding secure communication.
Summary
Secure network communication is one of the most critical aspect of mobile application development. Fortunately, more and more developers are aware of that and the general level of security regarding networking is constantly rising. Another really positive thing is that Android introduced a very powerful and simple tool, which allows managing TLS connection, which is Network Security Config. Do not hesitate to use it! At the same time, remember that it is supported only from Android 7 onwards, so in case your target is below that threshold, you need to use different measures alongside NSC. Also, remember about certificate pinning if you are developing high-risk applications.
Last but not least, always remember to follow good practices in development of secure mobile applications. To make it easier, we had prepared a guide which gathers in one place the most significant challenges and recommendations:
If you have any questions or suggestions regarding the article of the guide, feel free to reach me out. You can find me on Twitter on LinkedIn.
Head of Cloud Security