WebView:不安全的 URI 加载
使用集合让一切井井有条
根据您的偏好保存内容并对其进行分类。
OWASP 类别:MASVS-CODE:代码质量
概览
当 Android 应用无法在 URI 加载到 WebView 中之前正确评估其有效性时,便会发生不安全的 URI 加载。
此类漏洞的根本原因在于 URI 由多个部分组成,其中至少 scheme 和(授权方部分的)主机是必须经验证(例如加入许可名单)的,然后 URI 才能加载到 WebView 或由应用在内部使用。
最常见的错误包括:
- 检查主机但未检查 scheme,让攻击者可通过经身份验证的主机使用
http://
、content://
或 javascript://
等 scheme。
- 未能正确解析 URI,尤其是在以字符串形式接收 URI 的情况中。
- 验证 scheme,但未验证主机(主机验证不充分)。
关于最后一种情况,它通常会在应用需要允许主域名的任意子网域时发生。因此,即使已正确提取主机名,应用也会使用 java.lang.String
类的 startsWith
、endsWith,
或 contains
等方法来验证在所提取的字符串部分中是否存在主域名。如果使用不当,这些方法可能会导致错误结果,并迫使应用错误地信任可能存在恶意的主机。
影响
具体影响可能会因主机的使用环境而异。在 WebView 中加载恶意 URI(即绕过过滤/许可名单的 URI)的情况下,可能会导致账号被盗用(例如使用钓鱼式攻击)、代码执行(例如加载恶意 JavaScript)或设备被破解(利用通过超链接提供的代码)。
缓解措施
处理字符串 URI 时,请务必将字符串解析为 URI 并验证 scheme 和主机:
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");
}
}
对于主机验证,在隔离相应的 URI 部分后,请务必对其进行完整验证(而不是部分验证),以准确确定主机是否可信。当无法避免使用 startsWith
或 endsWith
等方法时,请务必使用正确的语法,并且不要忽略必要的字符或符号(例如,endsWith
需要在域名之前有“.
”点字符才能准确匹配)。忽略这些字符可能导致匹配不准确,并降低安全性。由于子网域可以无限嵌套,因此我们不建议使用正则表达式匹配来验证主机名。
资源
本页面上的内容和代码示例受内容许可部分所述许可的限制。Java 和 OpenJDK 是 Oracle 和/或其关联公司的注册商标。
最后更新时间 (UTC):2023-12-13。
[null,null,["最后更新时间 (UTC):2023-12-13。"],[],[],null,["# Webviews – Unsafe URI Loading\n\n\u003cbr /\u003e\n\n**OWASP category:** [MASVS-CODE: Code Quality](https://mas.owasp.org/MASVS/10-MASVS-CODE)\n\n\nOverview\n--------\n\nAn unsafe URI Loading occurs when an Android application fails to correctly\nevaluate the validity of a URI before loading it into a WebView.\n\nThe underlying reason behind this type of vulnerability is that a\n[URI consists of multiple parts](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/net/URI.html), of which, at a minimum, the scheme and the\nhost (of the authority part) must be verified (e.g. allowlisted) before the URI\ngets loaded to a WebView or is used internally by the application.\n\nThe most common mistakes include:\n\n- Checking the host but not the scheme, allowing an attacker to use schemes like `http://`, `content://` or `javascript://` with an authenticated host.\n- Failing to parse the URI correctly, especially in cases where the URI is received as a string.\n- Validating the scheme but not the host (insufficient host validation).\n\nRegarding the last case, this usually occurs when the application needs to allow\narbitrary subdomains of a primary domain. So, even if the hostname has been\nextracted correctly, the app uses methods such as `startsWith`, `endsWith,` or\n`contains` of the `java.lang.String` class to validate the presence of a primary\ndomain in the extracted string section. Used incorrectly, these methods may lead\nto faulty results and force the application to improperly trust a potentially\nmalicious host.\n\nImpact\n------\n\nDepending on the context in which the host is used, the impact can vary. In\ncases where loading a malicious URI (i.e., one that bypassed\nfiltering/allowlist) in a WebView could potentially lead to account takeover\n(e.g. using phishing), code execution (e.g., loading malicious JavaScript), or\ndevice compromise (exploit code delivered using hyperlink).\n\nMitigations\n-----------\n\nWhen handling string URIs, it is important to parse the string as a URI and\nvalidate both the scheme and the host: \n\n### Kotlin\n\n fun isUriTrusted(incomingUri: String, trustedHostName: String): Boolean {\n try {\n val uri = Uri.parse(incomingUri)\n return uri.scheme == \"https\" && uri.host == trustedHostName\n } catch (e: NullPointerException) {\n throw NullPointerException(\"incomingUri is null or not well-formed\")\n }\n }\n\n### Java\n\n public static boolean isUriTrusted(String incomingUri, String trustedHostName)\n throws NullPointerException {\n try {\n Uri uri = Uri.parse(incomingUri);\n return uri.getScheme().equals(\"https\") &&\n uri.getHost().equals(trustedHostName);\n } catch (NullPointerException e) {\n throw new NullPointerException(\n \"incomingUri is null or not well-formed\");\n }\n }\n\nFor host validation, after isolating the corresponding URI part, it is important\nto validate it entirely (rather than partially) to accurately identify whether\nthe host is trusted or not. When using methods like `startsWith` or `endsWith`\ncan't be avoided, it is important to use the correct syntax and not overlook\nnecessary characters or symbols (for example, `endsWith` requires the \"`.`\" dot\ncharacter before the domain name for an accurate match). Neglecting these\ncharacters may lead to inaccurate matches and compromise security. Since\nsubdomains can be infinitely nested, regular expression matching is not a\nrecommended strategy for validating hostnames.\n\nContributors: Dimitrios Valsamaras and Michael Peck of Microsoft Threat\nIntelligence\n\nResources\n---------\n\n- [getHost() documentation](/reference/java/net/URI#getHost())\n- [getScheme() documentation](/reference/java/net/URI#getScheme())"]]