-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
base: master
Are you sure you want to change the base?
Conversation
…pp to reverse engineer and adding demos for the tests.
@cpholguera Have a look when you have the time. |
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. |
Awesome! I'll take a look when I'm back from vacation 🌴 Thanks a lot! |
There was a problem hiding this 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 |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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 theMainActivity
smali file. There are calls to null checks for each of the parameterschain
andauthType
...
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".
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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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. |
There was a problem hiding this comment.
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/
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 }} |
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") | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
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 accessmessage
. -
Used Kotlin’s property access (
error.primaryError
) instead of the Java-styleerror.getPrimaryError()
.
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." | |
} | |
} |
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() } |
There was a problem hiding this comment.
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:
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: |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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
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 aCertificateException
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 returningtrue
(e.g. viaALLOW_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 callhandler.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
whosecheckServerTrusted
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 returnstrue
(and uses the deprecatedALLOW_ALL_HOSTNAME_VERIFIER
), illustrating how hostname checks can be bypassed.MASTG-DEMO-0035: Improper use of the onReceivedSslError handler
Summary: Provides a
WebViewClient
example overridingonReceivedSslError
to log SSL errors but then unconditionally callinghandler.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
withtargetSdk = 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.