WebView - 不安全的檔案包含

OWASP 類別:MASVS-STORAGE:儲存空間

總覽

本文將說明幾個與檔案納入作業相關的問題,這些問題有類似的緩解方法。這些問題主要與 WebView 內檔案存取權相關的安全漏洞有關,從允許檔案存取權或啟用 JavaScript 的危險 WebSettings,到建立檔案選取要求的 WebKit 方法皆是安全漏洞。如果您想瞭解如何解決因使用 file:// 配置、本機檔案的無限制存取權和跨網站指令碼攻擊,而導致 WebView 發生問題的問題,這份文件應該會對您有所幫助。

具體來說,本文件涵蓋以下主題:

  • WebSettings 是包含管理 WebView 設定狀態方法的類別。這些方法可能會讓 WebView 遭受各種攻擊,我們會在後文說明。在本文件中,我們將探討與檔案存取方式相關的方法,以及允許執行 JavaScript 的設定:
  • setAllowFileAccesssetAllowFileAccessFromFileURLssetAllowUniversalAccessFromFileURLs 方法可用於使用檔案配置網址 (file://) 授予本機檔案存取權。不過,惡意指令碼可能會利用這些方法,存取應用程式可存取的任意本機檔案,例如自己的 /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、登入憑證或其他機密資料外洩,具體取決於應用程式設定。

因應措施

驗證檔案網址

如果您的應用程式需要透過 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 的應用程式。


風險:檔案型跨網站指令碼攻擊

setJavacriptEnabled 方法設為 TRUE 可讓 JavaScript 在 WebView 中執行,並結合先前所述的檔案存取權,透過執行任意檔案中的程式碼,或在 WebView 中開啟的惡意網站,執行檔案型 XSS。

因應措施

禁止 WebView 載入本機檔案

與先前的風險相同,如果將 setAllowFileAccess()setAllowFileAccessFromFileURLs()setAllowUniversalAccessFromFileURLs() 設為 FALSE,就可以避免檔案型跨網站指令碼攻擊。

禁止 WebView 執行 JavaScript

將方法 setJavascriptEnabled 設為 FALSE,這樣 JavaScript 就無法在 WebView 中執行。

確保 WebView 不會載入不受信任的內容

有時您必須在 WebView 中啟用這些設定。在這種情況下,請務必確保只載入可信任的內容。限制 JavaScript 執行作業,只執行您控制的作業,並禁止任意 JavaScript,是確保內容可信賴的好方法。否則,禁止載入明文流量可確保有危險設定的 WebView 至少無法載入 HTTP 網址。您可以透過資訊清單,將 android:usesCleartextTraffic 設為 False,或是將 Network Security Config 設為不允許 HTTP 流量,來執行這項操作。


資源