android
demo
MSTG-TEST-0283
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.
MastgTest.kt MastgTest_reversed.java
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 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 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.