Skip to content

MASTG-DEMO-0055: Use of the HostnameVerifier that Allows Any Hostname

Download MASTG-DEMO-0055 APK Open MASTG-DEMO-0055 Folder Build MASTG-DEMO-0055 APK

Sample

This sample connects to a URL with an subject alternative name that does not match the hostname and configures a HostnameVerifier that allows any hostname.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package org.owasp.mastestapp

import android.content.Context
import android.util.Log
import java.net.URL
import java.security.SecureRandom
import java.security.cert.X509Certificate
import javax.net.ssl.HostnameVerifier
import javax.net.ssl.HttpsURLConnection
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager

class MastgTest(private val context: Context) {

    fun mastgTest(): String {
        val content = StringBuilder("Response:\n\n")
        val thread = Thread {
            content.append(fetchUrl("https://tlsbadsubjectaltname.no"))
        }
        thread.start()
        thread.join()
        return content.toString()
    }

    private fun fetchUrl(urlString: String): String {
        return try {
            val url = URL(urlString)
            val connection = url.openConnection() as HttpsURLConnection

            // Accept any certificate to bypass CA verification (not the weakness we're showing here)
            trustAllCertificates(connection)

            // ❌ Hostname verification disabled
            connection.hostnameVerifier = HostnameVerifier { hostname, _ ->
                Log.w("HOSTNAME_VERIFIER", "Insecurely allowing host: $hostname")
                true
            }

            connection.setRequestProperty("User-Agent", "OWASP MAS APP 9000")
            connection.connect()

            val response = connection.inputStream.bufferedReader().use { it.readText() }
            "\n[$urlString] Response OK\n$response\n"
        } catch (e: Exception) {
            "\n[$urlString] Error: ${e.message}\n"
        }
    }
    private fun trustAllCertificates(connection: HttpsURLConnection) {
        try {
            val trustAllCerts = arrayOf<TrustManager>(
                object : X509TrustManager {
                    override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {}
                    override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {}
                    override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
                }
            )

            val sslContext = SSLContext.getInstance("TLS")
            sslContext.init(null, trustAllCerts, SecureRandom())
            connection.sslSocketFactory = sslContext.socketFactory

        } catch (e: Exception) {
            Log.e("TRUST_MANAGER", "Failed to setup trust manager: ${e.message}")
        }
    }
}
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package org.owasp.mastestapp;

import android.content.Context;
import android.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.net.URLConnection;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import kotlin.Metadata;
import kotlin.io.CloseableKt;
import kotlin.io.TextStreamsKt;
import kotlin.jvm.internal.Intrinsics;
import kotlin.text.Charsets;

/* compiled from: MastgTest.kt */
@Metadata(d1 = {"\u0000&\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0003\n\u0002\u0010\u000e\n\u0002\b\u0003\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0000\b\u0007\u0018\u00002\u00020\u0001B\u000f\u0012\u0006\u0010\u0002\u001a\u00020\u0003¢\u0006\u0004\b\u0004\u0010\u0005J\u0006\u0010\u0006\u001a\u00020\u0007J\u0010\u0010\b\u001a\u00020\u00072\u0006\u0010\t\u001a\u00020\u0007H\u0002J\u0010\u0010\n\u001a\u00020\u000b2\u0006\u0010\f\u001a\u00020\rH\u0002R\u000e\u0010\u0002\u001a\u00020\u0003X\u0082\u0004¢\u0006\u0002\n\u0000¨\u0006\u000e"}, d2 = {"Lorg/owasp/mastestapp/MastgTest;", "", "context", "Landroid/content/Context;", "<init>", "(Landroid/content/Context;)V", "mastgTest", "", "fetchUrl", "urlString", "trustAllCertificates", "", "connection", "Ljavax/net/ssl/HttpsURLConnection;", "app_debug"}, k = 1, mv = {2, 0, 0}, xi = 48)
/* loaded from: classes3.dex */
public final class MastgTest {
    public static final int $stable = 8;
    private final Context context;

    public MastgTest(Context context) {
        Intrinsics.checkNotNullParameter(context, "context");
        this.context = context;
    }

    public final String mastgTest() throws InterruptedException {
        final StringBuilder content = new StringBuilder("Response:\n\n");
        Thread thread = new Thread(new Runnable() { // from class: org.owasp.mastestapp.MastgTest$$ExternalSyntheticLambda1
            @Override // java.lang.Runnable
            public final void run() {
                MastgTest.mastgTest$lambda$0(content, this);
            }
        });
        thread.start();
        thread.join();
        String string = content.toString();
        Intrinsics.checkNotNullExpressionValue(string, "toString(...)");
        return string;
    }

    /* JADX INFO: Access modifiers changed from: private */
    public static final void mastgTest$lambda$0(StringBuilder content, MastgTest this$0) {
        Intrinsics.checkNotNullParameter(content, "$content");
        Intrinsics.checkNotNullParameter(this$0, "this$0");
        content.append(this$0.fetchUrl("https://tlsbadsubjectaltname.no"));
    }

    private final String fetchUrl(String urlString) throws IOException {
        try {
            URL url = new URL(urlString);
            URLConnection uRLConnectionOpenConnection = url.openConnection();
            Intrinsics.checkNotNull(uRLConnectionOpenConnection, "null cannot be cast to non-null type javax.net.ssl.HttpsURLConnection");
            HttpsURLConnection connection = (HttpsURLConnection) uRLConnectionOpenConnection;
            trustAllCertificates(connection);
            connection.setHostnameVerifier(new HostnameVerifier() { // from class: org.owasp.mastestapp.MastgTest$$ExternalSyntheticLambda0
                @Override // javax.net.ssl.HostnameVerifier
                public final boolean verify(String str, SSLSession sSLSession) {
                    return MastgTest.fetchUrl$lambda$1(str, sSLSession);
                }
            });
            connection.setRequestProperty("User-Agent", "OWASP MAS APP 9000");
            connection.connect();
            InputStream inputStream = connection.getInputStream();
            Intrinsics.checkNotNullExpressionValue(inputStream, "getInputStream(...)");
            Reader inputStreamReader = new InputStreamReader(inputStream, Charsets.UTF_8);
            BufferedReader bufferedReader = inputStreamReader instanceof BufferedReader ? (BufferedReader) inputStreamReader : new BufferedReader(inputStreamReader, 8192);
            try {
                BufferedReader it = bufferedReader;
                String response = TextStreamsKt.readText(it);
                CloseableKt.closeFinally(bufferedReader, null);
                return "\n[" + urlString + "] Response OK\n" + response + "\n";
            } finally {
            }
        } catch (Exception e) {
            return "\n[" + urlString + "] Error: " + e.getMessage() + "\n";
        }
    }

    /* JADX INFO: Access modifiers changed from: private */
    public static final boolean fetchUrl$lambda$1(String hostname, SSLSession sSLSession) {
        Log.w("HOSTNAME_VERIFIER", "Insecurely allowing host: " + hostname);
        return true;
    }

    private final void trustAllCertificates(HttpsURLConnection connection) throws NoSuchAlgorithmException, KeyManagementException {
        try {
            TrustManager[] trustAllCerts = {new X509TrustManager() { // from class: org.owasp.mastestapp.MastgTest$trustAllCertificates$trustAllCerts$1
                @Override // javax.net.ssl.X509TrustManager
                public void checkClientTrusted(X509Certificate[] chain, String authType) {
                }

                @Override // javax.net.ssl.X509TrustManager
                public void checkServerTrusted(X509Certificate[] chain, String authType) {
                }

                @Override // javax.net.ssl.X509TrustManager
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
            }};
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, trustAllCerts, new SecureRandom());
            connection.setSSLSocketFactory(sslContext.getSocketFactory());
        } catch (Exception e) {
            Log.e("TRUST_MANAGER", "Failed to setup trust manager: " + e.getMessage());
        }
    }
}

Steps

Let's run our semgrep rule against the sample code.

../../../../rules/mastg-android-network-hostname-verification.yml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
rules:
  - id: mastg-android-network-hostname-verification
    severity: WARNING
    languages:
      - java
    metadata:
      summary: This rule looks for the use of HostnameVerifier in the app
    message: Improper server hostname verification detected
    match:
      any:
        - new HostnameVerifier() {...}
run.sh
1
NO_COLOR=true semgrep -c ../../../../rules/mastg-android-network-hostname-verification.yml ./MastgTest_reversed.java --text > output.txt

Observation

The rule identified one instance of the use of the HostnameVerifier in the code.

output.txt
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
┌────────────────┐
 1 Code Finding 
└────────────────┘

    MastgTest_reversed.java
    ❯❱ rules.mastg-android-network-hostname-verification
          Improper server hostname verification detected

           69 connection.setHostnameVerifier(new HostnameVerifier() { // from class:
               org.owasp.mastestapp.MastgTest$$ExternalSyntheticLambda0              
           70     @Override // javax.net.ssl.HostnameVerifier
           71     public final boolean verify(String str, SSLSession sSLSession) {
           72         return MastgTest.fetchUrl$lambda$1(str, sSLSession);
           73     }
           74 });

Evaluation

The test fails because the app uses a HostnameVerifier that allows any hostname.

In this case, since the rule only checks for the presence of a HostnameVerifier and does not validate the implementation of the verifier, you need to manually validate the app's reverse-engineered code and inspect the provided code locations.

The rule points to MastgTest_reversed.java, where we can see the following code:

            connection.setHostnameVerifier(new HostnameVerifier() { // from class: org.owasp.mastestapp.MastgTest$$ExternalSyntheticLambda0
                @Override // javax.net.ssl.HostnameVerifier
                public final boolean verify(String str, SSLSession sSLSession) {
                    return MastgTest.fetchUrl$lambda$1(str, sSLSession);
                }
            });
            ...

    /* JADX INFO: Access modifiers changed from: private */
    public static final boolean fetchUrl$lambda$1(String hostname, SSLSession sSLSession) {
        Log.w("HOSTNAME_VERIFIER", "Insecurely allowing host: " + hostname);
        return true;
    }

We can see how:

  • the app sets a custom HostnameVerifier on the HTTPS connection.
  • the verifier calls fetchUrl$lambda$1, which logs a warning and returns true.

This way we can conclude that the hostname verification does not properly validate that the server's hostname matches the certificate subject alternative name.