Webviews – Unsafe URI Loading

OWASP category: MASVS-CODE: Code Quality

Overview

An unsafe URI Loading occurs when an Android application fails to correctly evaluate the validity of a URI before loading it into a WebView.

The underlying reason behind this type of vulnerability is that a URI consists of multiple parts, of which, at a minimum, the scheme and the host (of the authority part) must be verified (e.g. allowlisted) before the URI gets loaded to a WebView or is used internally by the application.

The most common mistakes include:

  • Checking the host but not the scheme, allowing an attacker to use schemes like http://, content:// or javascript:// with an authenticated host.
  • Failing to parse the URI correctly, especially in cases where the URI is received as a string.
  • Validating the scheme but not the host (insufficient host validation).

Regarding the last case, this usually occurs when the application needs to allow arbitrary subdomains of a primary domain. So, even if the hostname has been extracted correctly, the app uses methods such as startsWith, endsWith, or contains of the java.lang.String class to validate the presence of a primary domain in the extracted string section. Used incorrectly, these methods may lead to faulty results and force the application to improperly trust a potentially malicious host.

Impact

Depending on the context in which the host is used, the impact can vary. In cases where loading a malicious URI (i.e., one that bypassed filtering/allowlist) in a WebView could potentially lead to account takeover (e.g. using phishing), code execution (e.g., loading malicious JavaScript), or device compromise (exploit code delivered using hyperlink).

Mitigations

When handling string URIs, it is important to parse the string as a URI and validate both the scheme and the host:

Kotlin

fun isUriTrusted(incomingUri: String, trustedHostName: String): Boolean {
    try {
        val uri = Uri.parse(incomingUri)
        return uri.scheme == "https" && uri.host == trustedHostName
    } catch (e: NullPointerException) {
        throw NullPointerException("incomingUri is null or not well-formed")
    }
}

Java

public static boolean isUriTrusted(String incomingUri, String trustedHostName)
    throws NullPointerException {
        try {
            Uri uri = Uri.parse(incomingUri);
            return uri.getScheme().equals("https") &&
            uri.getHost().equals(trustedHostName);
        } catch (NullPointerException e) {
            throw new NullPointerException(
                "incomingUri is null or not well-formed");
        }
    }

For host validation, after isolating the corresponding URI part, it is important to validate it entirely (rather than partially) to accurately identify whether the host is trusted or not. When using methods like startsWith or endsWith can't be avoided, it is important to use the correct syntax and not overlook necessary characters or symbols (for example, endsWith requires the "." dot character before the domain name for an accurate match). Neglecting these characters may lead to inaccurate matches and compromise security. Since subdomains can be infinitely nested, regular expression matching is not a recommended strategy for validating hostnames.

Contributors: Dimitrios Valsamaras and Michael Peck of Microsoft Threat Intelligence

Resources