Skip to content

Port MASTG-TEST-0021: Testing Endpoint Identify Verification (android) #3255

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: master
Choose a base branch
from

Conversation

sydseter
Copy link
Collaborator

@sydseter sydseter commented Apr 16, 2025

This PR closes #2960.

Overview of additions:

Tests

  • MASTG-TEST-0234-1: Correct implementation of server certificate verification
    Summary: Verifies that any checkServerTrusted override in the app throws a CertificateException on invalid certificates rather than merely logging, ensuring proper TLS certificate validation to prevent MITM attacks.

  • MASTG-TEST-0234-2: Correct implementation of server hostname verification
    Summary: Ensures the app uses HostnameVerifier.verify() (or equivalent) rather than always returning true (e.g. via ALLOW_ALL_HOSTNAME_VERIFIER), so hostname mismatches are caught and connections aren’t silently accepted.

  • MASTG-TEST-0234-3: Correct use of SSL error handling for webviews
    Summary: Checks that WebViewClient.onReceivedSslError handlers do not simply call handler.proceed() without throwing or blocking on SSL errors, thereby avoiding the silent acceptance of invalid certificates in WebViews.

  • MASTG-TEST-0234-4: Correct use of SSL error handling for webviews
    Summary: Validates that the app’s targetSdk is 24 or higher; if it’s below 24 (e.g. 23), the system may allow user-added CAs and the presence of <certificates src="user" /> in the network security config is flagged as insecure.

Demos

  • MASTG-DEMO-0033: Improper use of checkServerTrusted
    Summary: A Kotlin sample that installs an all-trusting X509TrustManager whose checkServerTrusted method only logs warnings (never throws), demonstrating how certificate validation can be disabled.

  • MASTG-DEMO-0034: Improper use of the HostnameVerifier
    Summary: Shows a sample where a custom HostnameVerifier always returns true (and uses the deprecated ALLOW_ALL_HOSTNAME_VERIFIER), illustrating how hostname checks can be bypassed.

  • MASTG-DEMO-0035: Improper use of the onReceivedSslError handler
    Summary: Provides a WebViewClient example overriding onReceivedSslError to log SSL errors but then unconditionally calling handler.proceed(), thereby ignoring TLS errors.

  • MASTG-DEMO-0036: Targeting API versions that allow the user to use insecure CAs
    Summary: Uses a build.gradle.kts with targetSdk = 23 to illustrate that older API levels permit user-imported CAs, potentially trusting insecure certificates.

  • MASTG-DEMO-0037: Network security config allows certificates imported on the user's behalf
    Summary: Contains a network_security_config.xml that includes <certificates src="user" />, demonstrating how the app can be configured to trust user-added CAs.

@sydseter
Copy link
Collaborator Author

@cpholguera Have a look when you have the time.

@sydseter
Copy link
Collaborator Author

We need to do a renumbering of the tests. I wasn't sure which numbers to give them. These should completely replace MASTG-TEST-0021.

@cpholguera
Copy link
Collaborator

Awesome! I'll take a look when I'm back from vacation 🌴 Thanks a lot!

@cpholguera cpholguera changed the title MASTG-TEST-0021-2960 Adding tests to replace MSTG-TEST-0234. Adding app to reverse engineer and adding demos for the tests. Port MASTG-TEST-0021: Testing Endpoint Identify Verification (android) May 15, 2025
@cpholguera cpholguera self-requested a review May 15, 2025 14:29
Copy link
Collaborator

@cpholguera cpholguera left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you so much, @sydseter, this is really good! Please take a look at my feedback, and let me know if I can answer any questions.


Review Tip:

Please use the "Commit Suggestions" button to incorporate the suggestions as much as possible, as indicated here. This way, we can easily track the comments and suggestions, and they will be automatically resolved.


You will find two instances of `checkServerTrusted` within the `MainActivity` smali file. There are calls to null checks for each of the parameters `chain` and `authType` as required by the method signature and the invocation of a static log function `w` denoted by `invoke-static`, but there is no indication that a `CertificateException` can be thrown beside an annotation connected to the `checkServerTrusted` method signature.

## Evaluation
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Evaluation is missing, "The test fails if ..."

  • Here you should be specific mentioning the exact APIs, but nothing about smali etc. That's for the demo.
  • If "just because the app has calls to checkServerTrusted" is not enough to fail and the code must be inspected in some specific (MANUAL) way you can indicate that here. We would like to identify such cases. We have a lot of demos where we can clearly pass or fail them but in this case maybe we need to mark them as "review". So a human will have to look at the provided locations, and inspect the code according to the criteria that you write in here.


## Observation

You will find two instances of `checkServerTrusted` within the `MainActivity` smali file. There are calls to null checks for each of the parameters `chain` and `authType` as required by the method signature and the invocation of a static log function `w` denoted by `invoke-static`, but there is no indication that a `CertificateException` can be thrown beside an annotation connected to the `checkServerTrusted` method signature.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You will find two instances of checkServerTrusted within the MainActivity smali file. There are calls to null checks for each of the parameters chain and authType ...

Something like this sounds very specific to be at the test level. It almost reads as if you're already in a demo and you're looking at the code. We're at a higher (/ abstract) level here.

The observation here simply describes the output of the steps above and starts with "The output contains".

Suggested change
You will find two instances of `checkServerTrusted` within the `MainActivity` smali file. There are calls to null checks for each of the parameters `chain` and `authType` as required by the method signature and the invocation of a static log function `w` denoted by `invoke-static`, but there is no indication that a `CertificateException` can be thrown beside an annotation connected to the `checkServerTrusted` method signature.
The output contains a list of locations where `checkServerTrusted` is used.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove this for now.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove this, it isn't needed as each demo app is built automatically using the default base apps.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're not creating demos for older versions, we can remove this one completely. For now, we agreed to support "current version - 5":

  • Currently MASTG min supported Android version: Android 11, API Level 30.
  • If a demo requires a min/targetSdk version lower than that it won't be added.
  • The current app has minSdk = 29 so we'll soon update this to 30.

We can keep the test and specify the maximum API level to which it applies.

https://docs.google.com/document/d/1veyzE4cVTSnIsKB1DOPUSMhjXow_MtJOtgHeo5HVoho/edit?tab=t.0#heading=h.iiij6wmkrdl9

Comment on lines +27 to +29
Review each of the reported instances.

- Line 79-92 contains the `onReceivedSslError` method. At the end, on line 91, there is a `handler.proceed()`. There are no exceptions being thrown which means that TLS errors are being ignored.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could also include this as additional proof.

Add file logcat.txt:

2025-05-17 12:28:31.039  7213-7278  chromium                org.owasp.mastestapp                 E  [ERROR:ssl_client_socket_impl.cc(992)] handshake failed; returned -1, SSL error code 1, net_error -201
2025-05-17 12:28:31.048  7213-7213  MastgTestWebView        org.owasp.mastestapp                 E  SSL errors onReceivedSslError: The date of the certificate is invalid.
2025-05-17 12:28:31.048  7213-7213  MastgTestWebView        org.owasp.mastestapp                 E  primary error: 4 certificate: Issued to: OU=Insubordinate,O=Insecure CA,1.2.840.113549.1.9.1=#161a696e73656375726540746c737265766f636174696f6e2e6f7267,C=NO,ST=Buskerud,CN=tlsexpired.no;
                                                                                                    Issued by: 1.2.840.113549.1.9.1=#16126a6f68616e4073796473657465722e636f6d,CN=sydseter.com,OU=Sydseter,O=Sydseter,L=Drammen,ST=Drammen,C=NO;
                                                                                                     on URL: https://tlsexpired.no/
Suggested change
Review each of the reported instances.
- Line 79-92 contains the `onReceivedSslError` method. At the end, on line 91, there is a `handler.proceed()`. There are no exceptions being thrown which means that TLS errors are being ignored.
The test fails because of the presence of the `handler.proceed()` on line 91 in the `onReceivedSslError` method (lines 79-92), as well as the absence of exceptions being thrown.
By doing this, the app is effectively ignoring every TLS error even though we can see that the expired certificate error is logged (see @MASTG-TECH-0009):
{{ logcat.txt }}

Comment on lines +1 to +41
package org.owasp.mastestapp

import android.net.http.SslError
import android.util.Log
import android.webkit.SslErrorHandler
import android.webkit.WebView
import android.webkit.WebViewClient
import android.content.Context

class MastgTestWebView (private val context: Context,) {

fun mastgTest(webView: WebView) {
webView.apply {
webViewClient = object : WebViewClient() {
override fun onReceivedSslError(
view: WebView,
handler: SslErrorHandler,
error: SslError
) {
var message = "SSL Certificate error."
when (error.getPrimaryError()) {
SslError.SSL_UNTRUSTED -> message =
"The certificate authority is not trusted."

SslError.SSL_EXPIRED -> message = "The certificate has expired."
SslError.SSL_IDMISMATCH -> message = "The certificate Hostname mismatch."
SslError.SSL_NOTYETVALID -> message = "The certificate is not yet valid."
SslError.SSL_DATE_INVALID -> message =
"The date of the certificate is invalid"
}
Log.w(null, "SSL errors onReceivedSslError: ".plus(message))
Log.w(null, error.toString())

handler.proceed()
}

}
loadUrl("https://tlsexpired.no")
}
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some highlights:

  • Extracted the message logic into a getSslErrorMessage(error) helper for better testing output (after running semgrep on the reversed version).

  • Turned logs into errors with the tag ("MastgTestWebView") and used $ to access message.

  • Used Kotlin’s property access (error.primaryError) instead of the Java-style error.getPrimaryError().

Suggested change
package org.owasp.mastestapp
import android.net.http.SslError
import android.util.Log
import android.webkit.SslErrorHandler
import android.webkit.WebView
import android.webkit.WebViewClient
import android.content.Context
class MastgTestWebView (private val context: Context,) {
fun mastgTest(webView: WebView) {
webView.apply {
webViewClient = object : WebViewClient() {
override fun onReceivedSslError(
view: WebView,
handler: SslErrorHandler,
error: SslError
) {
var message = "SSL Certificate error."
when (error.getPrimaryError()) {
SslError.SSL_UNTRUSTED -> message =
"The certificate authority is not trusted."
SslError.SSL_EXPIRED -> message = "The certificate has expired."
SslError.SSL_IDMISMATCH -> message = "The certificate Hostname mismatch."
SslError.SSL_NOTYETVALID -> message = "The certificate is not yet valid."
SslError.SSL_DATE_INVALID -> message =
"The date of the certificate is invalid"
}
Log.w(null, "SSL errors onReceivedSslError: ".plus(message))
Log.w(null, error.toString())
handler.proceed()
}
}
loadUrl("https://tlsexpired.no")
}
}
}
package org.owasp.mastestapp
import android.content.Context
import android.net.http.SslError
import android.util.Log
import android.webkit.SslErrorHandler
import android.webkit.WebView
import android.webkit.WebViewClient
class MastgTestWebView(private val context: Context) {
fun mastgTest(webView: WebView) {
webView.apply {
webViewClient = object : WebViewClient() {
override fun onReceivedSslError(
view: WebView,
handler: SslErrorHandler,
error: SslError
) {
val message = getSslErrorMessage(error)
Log.e("MastgTestWebView", "SSL errors onReceivedSslError: $message")
Log.e("MastgTestWebView", error.toString())
handler.proceed()
}
}
loadUrl("https://tlsexpired.no")
}
}
private fun getSslErrorMessage(error: SslError): String = when (error.primaryError) {
SslError.SSL_UNTRUSTED -> "The certificate authority is not trusted."
SslError.SSL_EXPIRED -> "The certificate has expired."
SslError.SSL_IDMISMATCH -> "The certificate Hostname mismatch."
SslError.SSL_NOTYETVALID -> "The certificate is not yet valid."
SslError.SSL_DATE_INVALID -> "The date of the certificate is invalid."
else -> "SSL Certificate error."
}
}

Comment on lines +18 to +31
message: Improper use of onReceivedSslError handler
match:
any:
- all:
- not: |
override fun onReceivedSslError(...) {
...
throw $CLASS(...)
...
- inside: |
override fun onReceivedSslError(...) {
...
- handler.proceed()
- override fun onReceivedSslError(...) { handler.proceed() }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This way it works with the reversed code:

Suggested change
message: Improper use of onReceivedSslError handler
match:
any:
- all:
- not: |
override fun onReceivedSslError(...) {
...
throw $CLASS(...)
...
- inside: |
override fun onReceivedSslError(...) {
...
- handler.proceed()
- override fun onReceivedSslError(...) { handler.proceed() }
message: Improper use of onReceivedSslError handler
patterns:
- pattern: |
$RETURN onReceivedSslError(..., SslErrorHandler $HANDLER, ...) {
...
$HANDLER.proceed();
...
}
- pattern-not-inside: |
$RETURN onReceivedSslError(..., SslErrorHandler $HANDLER, ...) {
...
throw new $EXCEPTION(...);
...
}

- https://github.com/OWASP/owasp-mastg/blob/master/Document/0x05g-Testing-Network-Communication.md
summary: This rule looks for the use of checkServerTrusted and ensures it throws an exception instead of silently muting invalid server certificates
message: Improper Server Certificate verification detected.
match:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: update to work with the reversed code

- https://github.com/OWASP/owasp-mastg/blob/master/Document/0x05g-Testing-Network-Communication.md#pinning-using-custom-trustmanagers
summary: This rule looks for the use of HostnameVerifier and ensure it doesn't just return true which would mean that it mutes all server host name issues
message: Improper server hostname verification detected
match:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: update to work with the reversed code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

MASTG v1->v2 MASTG-TEST-0021: Testing Endpoint Identify Verification (android)
2 participants