5. Network Communication Requirements
5.1. MSTG-NETWORK-1
Data is encrypted on the network using TLS. The secure channel is used consistently throughout the app.
5.1.1. Secure Network Requests
5.1.1.1. Recommended Network APIs
First, you should identify all network requests in the source code and ensure that no plain HTTP URLs are used. Make sure that sensitive information is sent over secure channels by using HttpsURLConnection or SSLSocket (for socket-level communication using TLS).
Next, even when using a low-level API which is supposed to make secure connections (such as SSLSocket), be aware that it has to be securely implemented. For instance, SSLSocket doesn’t verify the hostname. Use getDefaultHostnameVerifier to verify the hostname. The Android developer documentation includes a code example.
Reference
Rulebook
5.1.1.2. Configure plain-text HTTP Traffic
Next, you should ensure that the app is not allowing cleartext HTTP traffic. Since Android 9 (API level 28) cleartext HTTP traffic is blocked by default (thanks to the default Network Security Configuration) but there are multiple ways in which an application can still send it:
Setting the android:usesCleartextTraffic attribute of the <application> tag in the AndroidManifest.xml file. Note that this flag is ignored in case the Network Security Configuration is configured.
Configuring the Network Security Configuration to enable cleartext traffic by setting the cleartextTrafficPermitted attribute to true on <domain-config> elements.
Using low-level APIs (e.g. Socket) to set up a custom HTTP connection.
Using a cross-platform framework (e.g. Flutter, Xamarin, …), as these typically have their own implementations for HTTP libraries.
All of the above cases must be carefully analyzed as a whole. For example, even if the app does not permit cleartext traffic in its Android Manifest or Network Security Configuration, it might actually still be sending HTTP traffic. That could be the case if it’s using a low-level API (for which Network Security Configuration is ignored) or a badly configured cross-platform framework.
For more information refer to the article “Security with HTTPS and SSL”.
Reference
Rulebook
5.1.2. Rulebook
Ensure that sensitive information is transmitted over a secure channel (Required)
Ensure that the app does not allow plain-text HTTP traffic (Required)
5.1.2.1. Ensure that plain-text HTTP URLs are not used (Required)
It is necessary to identify all network requests in the source code and ensure that no plain-text HTTP URLs are used.
If this is violated, the following may occur.
Leakage of plain-text information to third parties.
5.1.2.2. Ensure that sensitive information is transmitted over a secure channel (Required)
If confidential information is sent through a dangerous channel (HTTP), it may be leaked to a third party because it is sent in plain text. Therefore, when sending confidential information, it must be sent over a secure channel (HTTPS, SSL, etc.).
The following is a sample code for transmission over a secure channel.
-
val url = URL("https://gmail.com:433/") val urlConnection = url.openConnection() as HttpsURLConnection urlConnection.connect();
-
val socket: SSLSocket = SSLSocketFactory.getDefault().run { createSocket("gmail.com", 443) as SSLSocket }
If this is violated, the following may occur.
Confidential information is leaked to a third party.
5.1.2.3. Secure implementation using low-level API (Required)
Even when using low-level APIs, secure implementations are required. SSLSocket does not validate hostnames. To verify the host name, use getDefaultHostnameVerifier.
The following is an example of sample code for host name verification when using SSLSocket.
// Open SSLSocket directly to gmail.com
val socket: SSLSocket = SSLSocketFactory.getDefault().run {
createSocket("gmail.com", 443) as SSLSocket
}
val session = socket.session
// Verify that the certicate hostname is for mail.google.com
// This is due to lack of SNI support in the current SSLSocket.
HttpsURLConnection.getDefaultHostnameVerifier().run {
if (!verify("mail.google.com", session)) {
throw SSLHandshakeException("Expected mail.google.com, found ${session.peerPrincipal} ")
}
}
// At this point SSLSocket performed certificate verification and
// we have performed hostname verification, so it is safe to proceed.
// ... use socket ...
socket.close()
If this is violated, the following may occur.
The host to which you are communicating may not be trusted or guaranteed.
5.1.2.4. Ensure that the app does not allow plain-text HTTP traffic (Required)
Ensure that the app does not allow plaintext HTTP traffic. Since Android 9 ( API level 28 ), plain-text HTTP traffic is blocked by default, but there are multiple ways for apps to send plain text.
The following is an example of how an app can send plain text.
In the AndroidManifest.xml file, the <application> tag Set the android:usesCleartextTraffic attribute in the AndroidManifest.xml file. Note that this flag is ignored if Network Security Configuration is set.
<application android:usesCleartextTraffic="true"> </application>
Set Network Security Configuration to enable CleartextTraffic by setting the cleartextTrafficPermitted attribute to true in the <domain-config> element.
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <base-config cleartextTrafficPermitted="false" /> <domain-config cleartextTrafficPermitted="true"> <domain includeSubdomains="true">secure.example.com</domain> </domain-config> </network-security-config>
Set up a custom HTTP connection using a low-level API (e.g., Socket).
val address = InetSocketAddress(ip, port) val socket = Socket() try { socket.connect(address) } catch (e: Exception) { }
Use a cross-platform framework (Flutter, Xamarin, etc.). These usually have their own implementations of HTTP libraries.
If this is violated, the following may occur.
Send plaintext over HTTP traffic.
5.2. MSTG-NETWORK-2
The TLS settings are in line with current best practices, or as close as possible if the mobile operating system does not support the recommended standards.
5.2.1. Recommended TLS Settings
Ensuring proper TLS configuration on the server side is also important. The SSL protocol is deprecated and should no longer be used. Also TLS v1.0 and TLS v1.1 have known vulnerabilities and their usage is deprecated in all major browsers by 2020. TLS v1.2 and TLS v1.3 are considered best practice for secure transmission of data. Starting with Android 10 (API level 29) TLS v1.3 will be enabled by default for faster and secure communication. The major change with TLS v1.3 is that customizing cipher suites is no longer possible and that all of them are enabled when TLS v1.3 is enabled, whereas Zero Round Trip (0-RTT) mode isn’t supported.
When both the client and server are controlled by the same organization and used only for communicating with one another, you can increase security by hardening the configuration.
If a mobile application connects to a specific server, its networking stack can be tuned to ensure the highest possible security level for the server’s configuration. Lack of support in the underlying operating system may force the mobile application to use a weaker configuration.
Reference
Rulebook
5.2.2. Recommended Cipher Suites
Cipher suites have the following structure:
Protocol_KeyExchangeAlgorithm_WITH_BlockCipher_IntegrityCheckAlgorithm
This structure includes:
A Protocol used by the cipher
A Key Exchange Algorithm used by the server and the client to authenticate during the TLS handshake
A Block Cipher used to encrypt the message stream
A Integrity Check Algorithm used to authenticate messages
Example: TLS_RSA_WITH_3DES_EDE_CBC_SHA
In the example above the cipher suites uses:
TLS as protocol
RSA Asymmetric encryption for Authentication
3DES for Symmetric encryption with EDE_CBC mode
SHA Hash algorithm for integrity
Note that in TLSv1.3 the Key Exchange Algorithm is not part of the cipher suite, instead it is determined during the TLS handshake.
In the following listing, we’ll present the different algorithms of each part of the cipher suite.
Protocols:
SSLv1
SSLv2 - RFC 6176
SSLv3 - RFC 6101
TLSv1.0 - RFC 2246
TLSv1.1 - RFC 4346
TLSv1.2 - RFC 5246
TLSv1.3 - RFC 8446
Key Exchange Algorithms:
DSA - RFC 6979
ECDSA - RFC 6979
RSA - RFC 8017
ECDHE - RFC 4492
PSK - RFC 4279
DSS - FIPS186-4
ECDHE_ECDSA - RFC 8422
ECDHE_RSA - RFC 8422
Block Ciphers:
DES - RFC 4772
DES_CBC - RFC 1829
3DES - RFC 2420
3DES_EDE_CBC - RFC 2420
AES_128_CBC - RFC 3268
AES_128_GCM - RFC 5288
AES_256_CBC - RFC 3268
AES_256_GCM - RFC 5288
RC4_40 - RFC 7465
RC4_128 - RFC 7465
Integrity Check Algorithms:
Note that the efficiency of a cipher suite depends on the efficiency of its algorithms.
The following resources contain the latest recommended cipher suites to use with TLS:
IANA recommended cipher suites can be found in TLS Cipher Suites.
OWASP recommended cipher suites can be found in the TLS Cipher String Cheat Sheet.
Some Android versions do not support some of the recommended cipher suites, so for compatibility purposes you can check the supported cipher suites for Android versions and choose the top supported cipher suites.
If you want to verify whether your server supports the right cipher suites, there are various tools you can use:
testssl.sh which “is a free command line tool which checks a server’s service on any port for the support of TLS/SSL ciphers, protocols as well as some cryptographic flaws”.
Finally, verify that the server or termination proxy at which the HTTPS connection terminates is configured according to best practices. See also the OWASP Transport Layer Protection cheat sheet and the Qualys SSL/TLS Deployment Best Practices.
Reference
Rulebook
5.2.3. Rulebook
5.2.3.1. Secure communication protocol (Required)
Ensuring proper TLS configuration on the server side is also important. The SSL protocol is deprecated and should no longer be used.
Deprecated Protocols
SSL
TLS v1.0
TLS v1.1
TLS v1.0 and TLS v1.1 have been deprecated in all major browsers by 2020.
Recommended Protocols
TLS v1.2
TLS v1.3
Starting with Android 10 (API level 29), TLS v1.3 is enabled by default for faster and more secure communication. While enabling TLS v1.3 enables all cipher suites, 0-RTT (Zero Round Trip) mode is not supported.
If this is violated, the following may occur.
Vulnerable to security exploits.
5.2.3.2. Recommended cipher suites for TLS (Recommended)
The following is an example of a recommended cipher suite. (Cipher suites recommended by TLS Cipher Suites that are not deprecated by Android that are not deprecated).
TLS_DHE_PSK_WITH_AES_128_GCM_SHA256
TLS_DHE_PSK_WITH_AES_256_GCM_SHA384
TLS_AES_128_GCM_SHA256
TLS_AES_256_GCM_SHA384
TLS_CHACHA20_POLY1305_SHA256
TLS_AES_128_CCM_SHA256
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
TLS_DHE_RSA_WITH_AES_128_CCM
TLS_DHE_RSA_WITH_AES_256_CCM
TLS_DHE_PSK_WITH_AES_128_CCM
TLS_DHE_PSK_WITH_AES_256_CCM
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256
TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256
TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256
TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256
TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384
TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256
If this is not noted, the following may occur.
Potential use of weak cipher suites.
5.3. MSTG-NETWORK-3
The app verifies the X.509 certificate of the remote endpoint when the secure channel is established. Only certificates signed by a trusted CA are accepted.
5.3.1. Configuring Trusted Certificates
5.3.1.1. Default settings per target SDK version
Applications targeting Android 7.0 (API level 24) or higher will use a default Network Security Configuration that doesn’t trust any user supplied CAs, reducing the possibility of MITM attacks by luring users to install malicious CAs.
Decode the app using apktool and verify that the targetSdkVersion in apktool.yml is equal to or higher than 24.
grep targetSdkVersion UnCrackable-Level3/apktool.yml
targetSdkVersion: '28'
However, even if targetSdkVersion >=24, the developer can disable default protections by using a custom Network Security Configuration defining a custom trust anchor forcing the app to trust user supplied CAs. See “Analyzing Custom Trust Anchors”.
Reference
Rulebook
5.3.1.2. Analyzing Custom Trust Anchors
Search for the Network Security Configuration file and inspect any custom <trust-anchors> defining <certificates src=”user”> (which should be avoided).
You should carefully analyze the precedence of entries:
If a value is not set in a <domain-config> entry or in a parent <domain-config>, the configurations in place will be based on the <base-config>
If not defined in this entry, the default configurations will be used.
Take a look at this example of a Network Security Configuration for an app targeting Android 9 (API level 28):
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="false">owasp.org</domain>
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</domain-config>
</network-security-config>
Some observations:
There’s no <base-config>, meaning that the default configuration for Android 9 (API level 28) or higher will be used for all other connections (only system CA will be trusted in principle).
However, the <domain-config> overrides the default configuration allowing the app to trust both system and user CAs for the indicated <domain> (owasp.org).
This doesn’t affect subdomains because of includeSubdomains=”false”.
Putting all together we can translate the above Network Security Configuration to: “the app trusts system and user CAs for the owasp.org domain, excluding its subdomains. For any other domains the app will trust the system CAs only”.
Reference
Rulebook
5.3.2. Server Certificate Verification
5.3.2.1. Verification with TrustManager
TrustManager is a means of verifying conditions necessary for establishing a trusted connection in Android. The following conditions should be checked at this point:
Has the certificate been signed by a trusted CA?
Has the certificate expired?
Is the certificate self-signed?
The following code snippet is sometimes used during development and will accept any certificate, overwriting the functions checkClientTrusted, checkServerTrusted, and getAcceptedIssuers. Such implementations should be avoided, and, if they are necessary, they should be clearly separated from production builds to avoid built-in security flaws.
TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[] {};
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
}
};
// SSLContext context
context.init(null, trustAllCerts, new SecureRandom());
Reference
Rulebook
5.3.2.2. WebView Server Certificate Verification
Sometimes applications use a WebView to render the website associated with the application. This is true of HTML/JavaScript-based frameworks such as Apache Cordova, which uses an internal WebView for application interaction. When a WebView is used, the mobile browser performs the server certificate validation. Ignoring any TLS error that occurs when the WebView tries to connect to the remote website is a bad practice.
The following code will ignore TLS issues, exactly like the WebViewClient custom implementation provided to the WebView:
WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.setWebViewClient(new WebViewClient(){
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
//Ignore TLS certificate errors and instruct the WebViewClient to load the website
handler.proceed();
}
});
Implementation of the Apache Cordova framework’s internal WebView usage will ignore TLS errors in the method onReceivedSslError if the flag android:debuggable is enabled in the application manifest. Therefore, make sure that the app is not debuggable. See the test case “Testing If the App is Debuggable”.
Reference
Rulebook
5.3.3. Hostname Verification
Another security flaw in client-side TLS implementations is the lack of hostname verification. Development environments usually use internal addresses instead of valid domain names, so developers often disable hostname verification (or force an application to allow any hostname) and simply forget to change it when their application goes to production. The following code disables hostname verification:
final static HostnameVerifier NO_VERIFY = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
With a built-in HostnameVerifier, accepting any hostname is possible:
HostnameVerifier NO_VERIFY = org.apache.http.conn.ssl.SSLSocketFactory
.ALLOW_ALL_HOSTNAME_VERIFIER;
Make sure that your application verifies a hostname before setting a trusted connection.
Reference
Rulebook
5.3.4. Rulebook
MITM attack potential depending on target SDK version (Required)
Bad Practices for Validating Server Certificates in WebView (Required)
5.3.4.1. MITM attack potential depending on target SDK version (Required)
Applications targeting Android 7.0 (API level 24) or higher will use a default Network Security Configuration that doesn’t trust any user supplied CAs, reducing the possibility of MITM attacks by luring users to install malicious CAs.
Decode the app using apktool and verify that the targetSdkVersion in apktool.yml is equal to or higher than 24.
If this is violated, the following may occur.
Increased likelihood of MITM attack to install malicious CA.
5.3.4.2. Custom trust anchor analysis (Required)
Even with targetSdkVersion >=24, developers can use a custom network security configuration to disable the default protection and define a custom trust anchor to force the app to trust the CA provided by the user.
The android:networkSecurityConfig setting in AndroidManifest.xml should be checked.
<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
<application android:networkSecurityConfig="@xml/network_security_config"
... >
...
</application>
</manifest>
The Network Security Configuration file set in android:networkSecurityConfig should be checked to verify the status of the following tags.
<base-config>
<trust-anchors>
<certificates>
* <certificates src=”user”> setting should be avoided.
Tags not set with a unique configuration inherit the setting at <base-config>, and if <base-config> is not set, the platform default is set.
You should carefully analyze the precedence of entries:
If a value is not set in a <domain-config> entry or in a parent <domain-config>, the configurations in place will be based on the <base-config>
If not defined in this entry, the default configurations will be used.
Take a look at this example of a Network Security Configuration for an app targeting Android 9 (API level 28):
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="false">owasp.org</domain>
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</domain-config>
</network-security-config>
Some observations:
There’s no <base-config>, meaning that the default configuration for Android 9 (API level 28) or higher will be used for all other connections (only system CA will be trusted in principle).
However, the <domain-config> overrides the default configuration allowing the app to trust both system and user CAs for the indicated <domain> (owasp.org).
This doesn’t affect subdomains because of includeSubdomains=”false”.
Putting all together we can translate the above Network Security Configuration to: “the app trusts system and user CAs for the owasp.org domain, excluding its subdomains. For any other domains the app will trust the system CAs only”.
If this is violated, the following may occur.
Increased likelihood of MITM attacks that force the installation of malicious CAs.
5.3.4.3. Verification by TrustManager (Required)
When the functions checkClientTrusted, checkServerTrusted, and getAcceptedIssuers are overridden using TrustManager, If all certificates are accepted without verifying client certificates, as in the sample code below, secure communication cannot be guaranteed. In the case of development, the following sample code is convenient for checking the operation with a self-certified certificate, but the process should be separated to prevent accidental incorporation into the production version.
TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[] {};
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
}
};
// SSLContext context
context.init(null, trustAllCerts, new SecureRandom());
The sample code below is the process of initializing TrustManager and setting HttpsURLConnection in order to trust a set of specific CAs.
// Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStream or ...)
CertificateFactory cf = CertificateFactory.getInstance("X.509");
// From https://www.washington.edu/itconnect/security/ca/load-der.crt
InputStream caInput = new BufferedInputStream(new FileInputStream("load-der.crt"));
Certificate ca;
try {
ca = cf.generateCertificate(caInput);
System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
} finally {
caInput.close();
}
// Create a KeyStore containing our trusted CAs
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
// Create an SSLContext that uses our TrustManager
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);
// Tell the URLConnection to use a SocketFactory from our SSLContext
URL url = new URL("https://certs.cac.washington.edu/CAtest/");
HttpsURLConnection urlConnection =
(HttpsURLConnection)url.openConnection();
urlConnection.setSSLSocketFactory(context.getSocketFactory());
InputStream in = urlConnection.getInputStream();
copyInputStreamToOutputStream(in, System.out);
Reference
If this is violated, the following may occur.
If verification with a self-certificate is included, it is not possible to determine if the certificate is trustworthy.
5.3.4.4. Bad Practices for Validating Server Certificates in WebView (Required)
Sometimes applications use a WebView to render the website associated with the application. This is true of HTML/JavaScript-based frameworks such as Apache Cordova, which uses an internal WebView for application interaction. When a WebView is used, the mobile browser performs the server certificate validation. Ignoring any TLS error that occurs when the WebView tries to connect to the remote website is a bad practice.
The sample code below is an example of how to ignore TLS errors and load a website into WebViewClient.
WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.setWebViewClient(new WebViewClient(){
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
//Ignore TLS certificate errors and instruct the WebViewClient to load the website
handler.proceed();
}
});
Implementation of the Apache Cordova framework’s internal WebView usage will ignore TLS errors in the method onReceivedSslError if the flag android:debuggable is enabled in the application manifest. Therefore, make sure that the app is not debuggable.
If this is violated, the following may occur.
Vulnerable to man-in-the-middle attacks.
5.3.4.5. Hostname verification (Required)
During the development phase, the developer may have disabled hostname validation (or allowed arbitrary hostnames in the application). In some cases, the validation is disabled without making any changes when the production environment goes live.
The following is a case where this is disabled.
final static HostnameVerifier NO_VERIFY = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
The following is a list of arbitrary host names that are accepted.
HostnameVerifier NO_VERIFY = org.apache.http.conn.ssl.SSLSocketFactory
.ALLOW_ALL_HOSTNAME_VERIFIER;
Host name verification should be performed when connecting to the production environment.
If this is violated, the following may occur.
It is possible to communicate with a host that is not a trusted destination host.