WebView - 不安全的檔案包含

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

總覽

本文涵蓋多個與檔案納入相關的問題,這些問題的解決方法類似。這些問題主要與存取 WebView 內檔案時產生的安全漏洞有關,包括危險的 WebSettings 允許檔案存取或啟用 JavaScript,以及建立檔案選取要求的 WebKit 方法。如果您想瞭解如何解決因使用 file:// 配置、無限制存取本機檔案和跨網站指令碼,而導致 WebView 發生問題,這份文件應該能提供協助。

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

  • WebSettings 類別包含管理 WebView 設定狀態的方法。這些方法可能會開啟 WebView,導致發生不同類型的攻擊,詳情請見下文。本文將探討與檔案存取方式相關的方法,以及允許執行 JavaScript 的設定:
  • setAllowFileAccesssetAllowFileAccessFromFileURLssetAllowUniversalAccessFromFileURLs 方法可用於使用檔案配置網址 (file://) 授予本機檔案的存取權。不過,惡意指令碼可能會利用這些方法存取應用程式可存取的任意本機檔案,例如應用程式本身的 /data/ 資料夾。因此,這些方法已標示為不安全,並在 API 30 中淘汰,改用更安全的替代方案,例如 WebViewAssetLoader
  • setJavascriptEnabled 方法可用於啟用在 WebView 中執行 JavaScript。這會導致應用程式容易受到檔案型跨網站指令碼攻擊。尤其是設定為允許載入本機檔案或可能含有可執行程式碼的不受信任網頁內容,設定為允許存取可由外部來源建立或變更的檔案,或是允許 WebView 執行 JavaScript 時,使用者和他們的資料就會面臨風險。
  • WebChromeClient.onShowFileChooserandroid.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,即可在 WebView 中執行 JavaScript,並搭配先前說明的檔案存取權啟用設定,透過在任意檔案中執行程式碼,或在 WebView 中開啟惡意網站,即可進行檔案型 XSS 攻擊。

因應措施

禁止 WebView 載入本機檔案

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

禁止 WebView 執行 JavaScript

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

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

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


資源