OWASP 类别: MASVS-PLATFORM:平台互动
概览
原生桥(有时称为 JavaScript 桥)是一种机制,可使用 addJavascriptInterface 方法来促进 WebView 与原生 Android 代码之间的通信。这样一来,在 WebView 中运行的 JavaScript 代码与 Android 应用的 Java 代码之间便可进行双向通信。addJavascriptInterface 方法会将 Java 对象公开给 WebView 的所有框架,并且任何框架都可以访问该对象名称并调用其方法。不过,应用无法验证 WebView 中调用帧的来源,这会引发安全问题,因为内容的可信度仍不确定。
还可以使用 Android 的 WebViewCompat.postWebMessage 或 WebMessagePort.postMessage 通过 HTML 消息渠道实现原生桥,以与 JavaScript Window.postMessage 进行通信。WebViewCompat.postWebMessage 和 WebMessagePort.postMessage 可以接受通过 Window.postMessage 发送的 JavaScript 消息,这些消息将在 WebView 中执行。
原生桥接存在多种风险:
- 基于 JavascriptInterface 的桥接:
addJavascriptInterface方法会将提供的 Java 对象注入 WebView 的每个框架(包括 iframe),这意味着它容易受到恶意第三方通过将框架注入合法网站来发起的攻击。以 API 级别 16 或更低版本为目标平台的应用尤其容易受到攻击,因为此方法可用于允许 JavaScript 控制宿主应用。- 在启用原生桥接的 WebView 中反映不受信任的用户提供的内容会导致跨站脚本攻击 (XSS)。
- 基于 MessageChannel 的桥接:
- 消息通道端点缺少来源检查意味着,系统会接受来自任何发件人的消息,包括包含恶意代码的消息。
- 可能会意外地将 Java 暴露给任意 JavaScript。
影响
恶意行为者可以利用 addJavascriptInterface、postWebMessage 和 postMessage 方法来访问、操纵或将他们控制的代码注入 WebView。这可能会导致用户被重定向到恶意网站、加载恶意内容,或在设备上运行恶意代码,从而提取敏感数据或实现提权。
风险:addJavascriptInterface 风险
WebView 实现浏览器的基本功能,例如网页呈现、导航和 JavaScript 执行。WebView 可在应用内使用,以在 activity 布局中显示网页内容。在 WebView 中使用 addJavascriptInterface 方法实现原生桥接可能会导致跨站脚本攻击 (XSS) 等安全问题,或者允许攻击者通过接口注入加载不受信任的内容,并以意外的方式操纵宿主应用,从而以宿主应用的权限执行 Java 代码。
缓解措施
停用 JavaScript
在 WebView 不需要 JavaScript 的场景中,请勿在 WebSettings 内调用 setJavaScriptEnabled(例如,在显示静态 HTML 内容时)。默认情况下,WebView 中停用了 JavaScript 执行。
在加载不受信任的内容时移除 JavaScript 接口
确保在 WebView 加载不受信任的内容之前,通过调用 removeJavascriptInterface 移除 JavaScript 界面中的对象。例如,可以在对 shouldInterceptRequest 的调用中完成此操作。
Kotlin
webView.removeJavascriptInterface("myObject")
Java
webView.removeJavascriptInterface("myObject");
仅通过 HTTPS 加载网页内容
如果您需要加载不受信任的内容,请确保 WebView 通过加密连接加载网络内容(另请参阅有关明文通信的准则)。通过在 AndroidManifest 文件中将 android:usesCleartextTraffic 设置为 false 或在网络安全配置中禁止 HTTP 流量,防止在未加密的连接上执行初始网页加载。如需了解详情,请参阅 usesCleartextTraffic 文档。
Xml
<application
android:usesCleartextTraffic="false">
<!-- Other application elements -->
</application>
为确保重定向和进一步的应用浏览不会通过未加密的流量进行,请检查 loadUrl 或 shouldInterceptRequest 中是否存在 HTTP 方案:
Kotlin
fun loadSecureUrl(webView: WebView?, url: String?) {
webView?.let { wv -> // Ensure valid WebView and URL
url?.let {
try {
val uri = URI(url)
if (uri.scheme.equals("https", ignoreCase = true)) { // Enforce HTTPS scheme for security
wv.loadUrl(url)
} else {
// Log an error or handle the case where the URL is not secure
System.err.println("Attempted to load a non-HTTPS URL: $url")
}
} catch (e: Exception) {
// Handle exception for improper URL format
System.err.println("Invalid URL syntax: $url")
}
}
}
}
Java
public void loadSecureUrl(WebView webView, String url) {
if (webView != null && url != null) { // Ensure valid WebView and URL
try {
URI uri = new URI(url);
String scheme = uri.getScheme();
if ("https".equalsIgnoreCase(scheme)) { // Enforce HTTPS scheme for security
webView.loadUrl(url);
} else {
// Log an error or handle the case where the URL is not secure
System.err.println("Attempted to load a non-HTTPS URL: " + url);
}
} catch (URISyntaxException e) {
// Handle exception for improper URL format
System.err.println("Invalid URL syntax: " + url);
}
}
}
验证不受信任的内容
如果任何外部链接在 WebView 中加载,请验证方案和主机(许可名单网域)。不在许可名单中的任何网域都应由默认浏览器打开。
不加载不受信任的内容
如果可能,请仅将严格限定范围的网址和归应用开发者所有的内容加载到 WebView 中。
请勿公开敏感数据
如果您的应用通过 WebView 访问敏感数据,请考虑在使用 JavaScript 界面之前,先使用 clearCache 方法删除本地存储的所有文件。您还可以使用服务器端标头(例如 no-store)来指示应用不应缓存特定内容。
不要公开敏感功能
如果您的应用需要敏感权限或收集敏感数据,请确保通过应用内的代码调用相应权限或数据,并向用户提供醒目的披露声明。避免使用 JavaScript 接口进行任何敏感操作或处理用户数据。
以 API 级别 21 或更高级别为目标平台
使用 addJavascriptInterface 方法的一种安全方式是,以目标 API 级别 21 或更高级别为目标,确保仅在 API 级别为 21 或更高级别时调用该方法。在 API 21 之前,JavaScript 可以使用反射来访问注入对象的公共字段。
风险:MessageChannel 风险
postWebMessage() 和 postMessage() 中缺少来源控制,可能会导致攻击者拦截消息或向原生处理程序发送消息。
缓解措施
设置 postWebMessage() 或 postMessage() 时,请避免使用 * 作为目标来源,而是明确指定预期的发送网域,从而仅允许来自可信网域的消息。
资源
- postMessage() 最佳实践
- addJavascriptInterface 文档
- postMessage() 文档
- WebMessagePort.postMessage() 文档
- WebViewClient.shouldInterceptRequest 文档
- 有关 addJavascriptInterface 的安全建议的文档
- clearCache 文档
- removeJavascript 文档
- 在 WebView 中启用 JavaScript