WebViews – 不安全的文件包含

OWASP 类别: MASVS-STORAGE: 存储

概览

本文档涵盖了几个与文件包含相关且具有相似缓解措施的问题。这些问题围绕着 WebViews 中文件访问所产生的漏洞,从允许文件访问或启用 JavaScript 的危险 WebSettings,到创建文件选择请求的 WebKit 方法。如果您正在寻找针对 WebView 中因使用 file:// scheme、不受限制地访问本地文件以及跨站脚本攻击而产生的问题的修复指南,本文档应该会有所帮助。

更具体地说,本文档涵盖以下主题

  • WebSettings 是一个包含管理 WebView 设置状态的方法的类。这些方法会使 WebView 容易受到各种攻击,下文将对此进行概述。在本文档中,我们将研究与文件访问方式相关的方法以及允许执行 JavaScript 的设置。
  • 通过使用文件 scheme URL (file://),setAllowFileAccesssetAllowFileAccessFromFileURLssetAllowUniversalAccessFromFileURLs 方法可用于授予对本地文件的访问权限。然而,恶意脚本可能会利用这些方法访问应用有权访问的任意本地文件,例如它们自己的 /data/ 文件夹。因此,这些方法已被标记为不安全,并在 API 30 中弃用,取而代之的是更安全的替代方案,例如 WebViewAssetLoader
  • setJavascriptEnabled 方法可用于在 WebView 中启用 JavaScript 的执行。这使得应用容易受到基于文件的 XSS 攻击。特别是当配置为允许加载本地文件或可能包含可执行代码的不可信网络内容,配置为允许访问可由外部来源创建或更改的文件,或允许 WebView 执行 JavaScript 时,用户及其数据面临风险。
  • WebChromeClient.onShowFileChooser 是一个属于 android.webkit 包的方法,该包提供了网页浏览工具。此方法可用于允许用户在 WebView 中选择文件。然而,此功能可能会被滥用,因为 WebView 不会强制限制所选的文件。

影响

文件包含的影响取决于在 WebView 中配置的 WebSettings。过于宽泛的文件权限可能允许攻击者访问本地文件并窃取敏感数据、PII(个人身份信息)或应用私有数据。启用 JavaScript 执行可能允许攻击者在 WebView 内或在用户的设备上运行 JavaScript。onShowFileChooser 方法选择的文件可能会损害用户安全,因为该方法或 WebView 无法确保文件来源是可信的。

风险:通过 file:// 访问文件存在风险

启用 setAllowFileAccesssetAllowFileAccessFromFileURLssetAllowUniversalAccessFromFileURLs 可能允许恶意意图和带有 file:// 上下文的 WebView 请求访问任意本地文件,包括 WebView cookie 和应用私有数据。此外,使用 onShowFileChooser 方法可能允许用户从不可信的来源选择和下载文件。

根据应用配置,这些方法都可能导致 PII、登录凭据或其他敏感数据被泄露。

缓解措施

验证文件 URL

如果您的应用需要通过 file:// URL 访问文件,重要的是仅允许列表中的特定 URL,这些 URL 是已知的合法 URL,同时避免 常见错误

使用 WebViewAssetLoader

使用 WebViewAssetLoader 而不是上述方法。此方法使用 http(s)//: scheme 而非 file:// scheme 来访问本地文件系统资源,并且不受所述攻击的影响。

Kotlin

val assetLoader: WebViewAssetLoader = Builder()
  .addPathHandler("/assets/", AssetsPathHandler(this))
  .build()

webView.setWebViewClient(object : WebViewClientCompat() {
  @RequiresApi(21)
  override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest): WebResourceResponse {
    return assetLoader.shouldInterceptRequest(request.url)
  }

  @Suppress("deprecation") // for API < 21
  override fun shouldInterceptRequest(view: WebView?, url: String?): WebResourceResponse {
    return assetLoader.shouldInterceptRequest(Uri.parse(url))
  }
})

val webViewSettings: WebSettings = webView.getSettings()
// Setting this off for security. Off by default for SDK versions >= 16.
webViewSettings.allowFileAccessFromFileURLs = false
// Off by default, deprecated for SDK versions >= 30.
webViewSettings.allowUniversalAccessFromFileURLs = false
// Keeping these off is less critical but still a good idea, especially if your app is not
// using file:// or content:// URLs.
webViewSettings.allowFileAccess = false
webViewSettings.allowContentAccess = false

// Assets are hosted under http(s)://appassets.androidplatform.net/assets/... .
// If the application's assets are in the "main/assets" folder this will read the file
// from "main/assets/www/index.html" and load it as if it were hosted on:
// https://appassets.androidplatform.net/assets/www/index.html
webView.loadUrl("https://appassets.androidplatform.net/assets/www/index.html")

Java

final WebViewAssetLoader assetLoader = new WebViewAssetLoader.Builder()
         .addPathHandler("/assets/", new AssetsPathHandler(this))
         .build();

webView.setWebViewClient(new WebViewClientCompat() {
    @Override
    @RequiresApi(21)
    public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
        return assetLoader.shouldInterceptRequest(request.getUrl());
    }

    @Override
    @SuppressWarnings("deprecation") // for API < 21
    public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
        return assetLoader.shouldInterceptRequest(Uri.parse(url));
    }
});

WebSettings webViewSettings = webView.getSettings();
// Setting this off for security. Off by default for SDK versions >= 16.
webViewSettings.setAllowFileAccessFromFileURLs(false);
// Off by default, deprecated for SDK versions >= 30.
webViewSettings.setAllowUniversalAccessFromFileURLs(false);
// Keeping these off is less critical but still a good idea, especially if your app is not
// using file:// or content:// URLs.
webViewSettings.setAllowFileAccess(false);
webViewSettings.setAllowContentAccess(false);

// Assets are hosted under http(s)://appassets.androidplatform.net/assets/... .
// If the application's assets are in the "main/assets" folder this will read the file
// from "main/assets/www/index.html" and load it as if it were hosted on:
// https://appassets.androidplatform.net/assets/www/index.html
webview.loadUrl("https://appassets.androidplatform.net/assets/www/index.html");

禁用危险的 WebSettings 方法

在 API 级别 29 及以下,方法 setAllowFileAccess()setAllowFileAccessFromFileURLs()setAllowUniversalAccessFromFileURLs() 的值默认设置为 TRUE;在 API 级别 30 及以上,则默认设置为 FALSE

如果需要配置其他 WebSettings,最好明确禁用这些方法,特别是对于目标 API 级别小于或等于 29 的应用。


风险:基于文件的 XSS

setJavacriptEnabled 方法设置为 TRUE 允许在 WebView 中执行 JavaScript,并且结合前面提到的文件访问权限,可以通过在任意文件中执行代码或在 WebView 中打开恶意网站来实现基于文件的 XSS 攻击。

缓解措施

阻止 WebView 加载本地文件

与前面的风险一样,如果将 setAllowFileAccess()setAllowFileAccessFromFileURLs()setAllowUniversalAccessFromFileURLs() 设置为 FALSE,则可以避免基于文件的 XSS 攻击。

阻止 WebView 执行 JavaScript

将方法 setJavascriptEnabled 设置为 FALSE,以便 WebView 中无法执行 JavaScript。

确保 WebView 不加载不可信内容

有时在 WebView 中启用这些设置是必要的。在这种情况下,确保只加载可信内容非常重要。限制 JavaScript 的执行,使其仅限于您控制的代码,并禁止任意 JavaScript,这是确保内容可信的一种好方法。否则,阻止加载明文流量可以确保具有危险设置的 WebView 至少无法加载 HTTP URL。这可以通过清单文件将 android:usesCleartextTraffic 设置为 False 来实现,或者通过设置禁止 HTTP 流量的 Network Security Config 来实现。


资源