WebView - 不安全的文件包含

OWASP 类别:MASVS-STORAGE:存储

概览

本文档介绍了与文件包含相关的几个问题,这些问题具有类似的缓解措施。这些问题主要涉及因访问 WebView 中的文件而产生的漏洞,从允许访问文件或启用 JavaScript 的危险 WebSettings,到用于创建文件选择请求的 WebKit 方法。如果您需要有关如何解决因使用 file:// 架构、对本地文件的无限制访问权限和跨站脚本攻击而导致的 WebView 问题的指导,本文档应该会有所帮助。

具体而言,本文档介绍了以下主题:

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

影响

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

风险:通过 file:// 冒险访问文件

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

这些方法都可能会导致个人身份信息、登录凭据或其他敏感数据渗漏,具体取决于应用配置。

缓解措施

验证文件网址

如果您的应用需要通过 file:// 网址访问文件,请务必仅将已知合法的特定网址列入许可名单,以免犯下常见错误

使用 WebViewAssetLoader

请改用 WebViewAssetLoader,而不是上述方法。此方法使用 http(s)//: 架构(而非 file:// 架构)来访问本地文件系统资源,因此不易受到上述攻击。

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 网址。您可以通过清单将 android:usesCleartextTraffic 设置为 False 来实现此目的,也可以通过设置禁止 HTTP 流量的 Network Security Config 来实现此目的。


资源