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

  1. Ensure that plain-text HTTP URLs are not used (Required)

  2. Ensure that sensitive information is transmitted over a secure channel (Required)

  3. Secure implementation using low-level API (Required)

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

  • HttpsURLConnection

    val url = URL("https://gmail.com:433/")
    val urlConnection = url.openConnection() as HttpsURLConnection
    urlConnection.connect();
    
  • SSLSocket

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

  1. Secure communication protocol (Required)

  2. Recommended cipher suites for TLS (Recommended)

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

  1. MITM attack potential depending on target SDK version (Required)

  2. Custom trust anchor analysis (Required)

  3. Verification by TrustManager (Required)

  4. Bad Practices for Validating Server Certificates in WebView (Required)

  5. Hostname verification (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.