MASTG-KNOW-0018: WebViews
On Android versions prior to 4.4, WebViews used the WebKit rendering engine to display web pages. Since Android 4.4, WebViews have been based on Chromium, providing improved performance and compatibility. However, the pages are still stripped down to minimal functions; for example, pages don't have address bars.
URL Loading in WebViews¶
WebViews are Android's embedded components which allow your app to open web pages within your application. In addition to mobile apps related threats, WebViews may expose your app to common web threats (e.g. XSS, Open Redirect, etc.).
One of the most important things to do when testing WebViews is to make sure that only trusted content can be loaded in it. Any newly loaded page could be potentially malicious, try to exploit any WebView bindings or try to phish the user. Unless you're developing a browser app, usually you'd like to restrict the pages being loaded to the domain of your app. A good practice is to prevent the user from even having the chance to input any URLs inside WebViews (which is the default on Android) nor navigate outside the trusted domains. Even when navigating on trusted domains there's still the risk that the user might encounter and click on other links to untrustworthy content (e.g. if the page allows for other users to post comments). In addition, some developers might even override some default behavior which can be potentially dangerous for the user.
SafeBrowsing API¶
To provide a safer web browsing experience, Android 8.1 (API level 27) introduces the SafeBrowsing API, which allows your application to detect URLs that Google has classified as a known threat.
By default, WebViews show a warning to users about the security risk with the option to load the URL or stop the page from loading. With the SafeBrowsing API you can customize your application's behavior by either reporting the threat to SafeBrowsing or performing a particular action such as returning back to safety each time it encounters a known threat. Please check the Android Developers documentation for usage examples.
You can use the SafeBrowsing API independently from WebViews using the SafetyNet library, which implements a client for Safe Browsing Network Protocol v4. SafetyNet allows you to analyze all the URLs that your app is supposed to load. You can check URLs with different schemes (e.g. http, file) since SafeBrowsing is agnostic to URL schemes, and against TYPE_POTENTIALLY_HARMFUL_APPLICATION and TYPE_SOCIAL_ENGINEERING threat types.
When sending URLs or files to be checked for known threats make sure they don't contain sensitive data which could compromise a user's privacy, or expose sensitive content from your application.
Virus Total API¶
Virus Total provides an API for analyzing URLs and local files for known threats. The API Reference is available on Virus Total developers page.
JavaScript Execution in WebViews¶
JavaScript can be injected into web applications via reflected, stored, or DOM-based Cross-Site Scripting (XSS). Mobile apps are executed in a sandboxed environment and don't have this vulnerability when implemented natively. Nevertheless, WebViews may be part of a native app to allow web page viewing.
Android WebViews can use setJavaScriptEnabled to enable JavaScript execution. This feature is disabled by default. When enabled, JavaScript code executes in the context of the loaded page, including any scripts provided by that content.
WebView-Native bridges¶
Android provides multiple mechanisms for communication between web content in a WebView and native app code, which differ in their design, capabilities, and security properties. Some are based on asynchronous message passing, while others use a synchronous injected object model.
For additional platform details, see Android's documentation on "Access native APIs with JavaScript bridge" and "WebView – Native bridges".
addWebMessageListener¶
addWebMessageListener is a message based bridge API provided through AndroidX WebKit. Android describes it as the most modern and recommended approach for communication between web content and native app code. The app registers a listener object name, such as myObject, together with a set of allowedOriginRules. Matching pages receive an injected JavaScript proxy object that can exchange messages with the app asynchronously. The callback also provides metadata such as the sender origin, whether the message came from the main frame, and a reply proxy for sending a response.
Android documents this mechanism as allowlist-based. The injected object is exposed only to origins that match the configured origin rules, and the same mechanism can work across matching frames. The native side typically uses WebViewCompat.addWebMessageListener(...), and the JavaScript side communicates through the injected proxy object's postMessage and onmessage APIs.
postWebMessage¶
postWebMessage is an asynchronous messaging API that Android documents as an alternative similar to the web platform's window.postMessage. The app sends a payload to the web page's main frame with WebViewCompat.postWebMessage(...). For bidirectional communication, the app can create a WebMessageChannel and transfer one of its ports to the page. Android characterizes this mechanism as origin aware and notes that it is limited to the main frame. The documentation also notes URI related constraints for content loaded with data:, file:, or loadData() unless * is used as the target origin.
addJavascriptInterface¶
addJavascriptInterface is the oldest bridge mechanism. Android describes it as a synchronous legacy model. The app creates a Java or Kotlin object, annotates exposed methods with @JavascriptInterface, and injects the object into the WebView with addJavascriptInterface(Object, String). JavaScript can then call the exposed methods through the injected object name.
Android also notes several implementation details that are specific to this mechanism. The injected object is available to every frame in the WebView, including iframes, and the mechanism does not provide origin based access control. The bridge documentation also states that methods such as WebView.getUrl() are not suitable for determining which frame invoked the interface. Android's security guidance also notes that before API level 21, JavaScript could use reflection to access the public fields of an injected object. This means that reflection based RCE payloads such as window.jsinterface.getClass().forName('java.lang.Runtime').getMethod('getRuntime',null).invoke(...).exec(...) were possible on older Android versions.
WebView Local File Access Settings¶
These APIs control how a WebView accesses files on the local device. They determine whether the WebView can load files (such as HTML, images, or scripts) from the file system and whether JavaScript running in a local context can access additional local files. Note that accessing assets and resources (via file:///android_asset or file:///android_res) is always allowed regardless of these settings.
| API | Purpose | Defaults to True (API Level) |
Defaults to False (API Level) |
Deprecated |
|---|---|---|---|---|
setAllowFileAccess |
Permits the WebView to load files from the local file system (using file:// URLs) |
<= 29 (Android 10) | >= 30 (Android 11) | No |
setAllowFileAccessFromFileURLs |
Allows JavaScript in a file:// context to access other local file:// URLs |
<= 15 (Android 4.0.3) | >= 16 (Android 4.1) | Yes (since API level 30, Android 11) |
setAllowUniversalAccessFromFileURLs |
Permits JavaScript in a file:// context to access resources from any origin, bypassing the same-origin policy |
<= 15 (Android 4.0.3) | >= 16 (Android 4.1) | Yes (since API level 30, Android 11) |
What files can be accessed by the WebView?:
The WebView can access any file that the app has permission to access via file:// URLs, including:
- Internal storage: the app's own internal storage.
- External storage
- Before Android 10:
- the entire external storage (SD card), if the app has the
READ_EXTERNAL_STORAGEpermission.
- the entire external storage (SD card), if the app has the
- Since Android 10:
- only the app-specific directories (due to scoped storage restrictions) without any special permissions.
- entire media folders (including data from other apps) if the app has the
READ_MEDIA_IMAGESor similar permissions. - the entire external storage if the app has the
MANAGE_EXTERNAL_STORAGEpermission.
- Before Android 10:
setAllowFileAccess¶
setAllowFileAccess enables the WebView to load local files using the file:// scheme. In this example, the WebView is configured to allow file access and then loads an HTML file from the external storage (sdcard).
webView.settings.apply {
allowFileAccess = true
}
webView.loadUrl("file:///sdcard/index.html");
setAllowFileAccessFromFileURLs¶
setAllowFileAccessFromFileURLs allows the local file (loaded via file://) to access additional local resources from its HTML or JavaScript.
Note that the value of this setting is ignored if the value of allowUniversalAccessFromFileURLs is true.
Chromium WebView Docs: With this relaxed origin rule, URLs starting with
content://andfile://can access resources that have the same relaxed origin overXMLHttpRequest. For instance,file://foocan make anXMLHttpRequesttofile://bar. Developers need to be careful so that a user provided data do not run incontent://as it will allow the user's code to access arbitrarycontent://URLs those are provided by other applications. It will cause a serious security issue.Regardless of this API call, the Fetch API does not allow accessing
content://andfile://URLs.
Example: In this example, the WebView is configured to allow file access and then loads an HTML file from the external storage (sdcard).
webView.settings.apply {
allowFileAccess = true
allowFileAccessFromFileURLs = true
}
webView.loadUrl("file:///sdcard/local_page.html");
The loaded HTML file contains an image that is loaded via a file:// URL:
<!-- In local_page.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Local Page</title>
</head>
<body>
<!-- This image is loaded via a file:// URL -->
<img src="file:///android_asset/images/logo.png" alt="Logo">
</body>
</html>
setAllowUniversalAccessFromFileURLs¶
setAllowUniversalAccessFromFileURLs allows JavaScript running in a local file (loaded via file://) to bypass the same-origin policy and access resources from any origin.
Chromium WebView Docs: When this API is called with true, URLs starting with
file://will have a scheme based origin, and can access other scheme based URLs overXMLHttpRequest. For instance,file://foocan make anXMLHttpRequesttocontent://bar,http://example.com/, andhttps://www.google.com/. So developers need to manage data running under thefile://scheme as it allows powerful permissions beyond the public web's CORS policy.Regardless of this API call, Fetch API does not allow to access
content://andfile://URLs.
Example: In this example, the local HTML file successfully makes a cross-origin request to fetch data from an HTTPS endpoint. This can be potentially abused by an attacker to exfiltrate sensitive data from the app.
webView.settings.apply {
javaScriptEnabled = true
allowFileAccess = true
allowUniversalAccessFromFileURLs = true
}
webView.loadUrl("file:///android_asset/local_page.html");
Contents of local_page.html (in the assets folder):
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Universal Access Demo</title>
<script>
// This AJAX call fetches data from a remote server despite being loaded via file://
fetch("https://api.example.com/data")
.then(response => response.text())
.then(data => document.getElementById("output").innerText = data)
.catch(err => console.error(err));
</script>
</head>
<body>
<div id="output">Loading...</div>
</body>
</html>
Note about accessing cookies:
Setting setAllowUniversalAccessFromFileURLs(true) allows JavaScript inside a local file:// to make cross-origin requests (XHR, Fetch, etc.). This bypasses the Same-Origin Policy (SOP) for network requests, but it does not grant access to cookies from remote websites.
- Cookies are managed by the WebView's CookieManager and cannot be accessed by a
file://origin unless explicitly allowed via document.cookie (which most modern sites prevent usingHttpOnlyandSecureflags). - Cross-origin requests also do not include cookies unless explicitly allowed by the server via CORS headers such as
Access-Control-Allow-Origin: *andAccess-Control-Allow-Credentials: true.
WebView Content Provider Access¶
WebViews can access content providers, which are used to share data between applications. Content providers can be accessed by other apps only if they are exported (android:exported attribute set to true), but even if the content provider is not exported, it can be accessed by a WebView in the application itself.
The setting setAllowContentAccess controls whether the WebView can access content providers using content:// URLs. This setting is enabled by default for Android 4.1 (API level 16) and above.
Chromium WebView Docs:
content://URLs are used to access resources provided via Android Content Providers. The access should be permitted viasetAllowContentAccessAPI beforehand.content://pages can contain iframes that loadcontent://pages, but the parent frame can not access into the iframe contents. Also onlycontent://pages can specifycontent://URLs for sub-resources.However, even pages loaded from
content://can not make any CORS-enabled requests such asXMLHttpRequestto othercontent://URLs as each one is assumed to belong to an opaque origin. See alsosetAllowFileAccessFromFileURLsandsetAllowUniversalAccessFromFileURLssections as they can relax this default rule.Pages loaded with any scheme other than
content://can't loadcontent://page in iframes and they can not specifycontent://URLs for sub-resources.
Example: In this example, the WebView's allowContentAccess property is enabled and a content:// URL is loaded.
webView.settings.apply {
allowContentAccess = true
}
webView.loadUrl("content://com.example.myapp.provider/data")
Which files can be accessed by the WebView?:
The WebView can access any data accessible via content providers (if the app has any) using content:// URLs. Unless otherwise further restricted by the content provider, this could include:
- Internal storage: the entire internal storage.
- External storage
- Before Android 10:
- the entire external storage (SD card), if the app has the
READ_EXTERNAL_STORAGEpermission.
- the entire external storage (SD card), if the app has the
- Since Android 10:
- only the app-specific directories (due to scoped storage restrictions) without any special permissions.
- entire media folders (including data from other apps) if the app has the
READ_MEDIA_IMAGESor similar permissions. - the entire external storage if the app has the
MANAGE_EXTERNAL_STORAGEpermission.
- Before Android 10:
Data from other apps accessible via content providers (if the app has any and they are exported) can also be accessed.
WebView Storage¶
Android WebView embeds a Chromium based browser engine. Every app has its own WebView cache, which isn't shared with the native Browser or other apps. Most web related data is stored inside the Chromium profile directory located at:
/data/data/<app_package>/app_webview/
Android WebView can persist several categories of data for each origin.
- Cached network resources created when a server sends cache permissive headers such as Cache Control or Expires. These resources are stored in memory and on disk inside the Chromium cache.
- DOM storage such as LocalStorage and SessionStorage
- WebSQL, removed in modern WebView versions
- IndexedDB
- Cookies including session and persistent cookies
- Files backed by the Origin Private File System (OPFS) including the SQLite Wasm database
OPFS and SQLite Wasm are internal to the Chromium storage layer. Their contents do not appear as ordinary files in the app sandbox.
Configuration and Defaults¶
Storage behavior can be influenced by calls on android.webkit.WebSettings such as:
WebSettings.setCacheModeWebSettings.setDomStorageEnabledWebSettings.setDatabaseEnableddeprecated with WebSQL in Android version 15 (API level 35)WebSettings.setAppCacheEnableddeprecated and removed in Android 13 (API level 33)
Network cache is enabled by default and obeys the HTTP cache headers sent by the server. DOM storage is enabled by default on all supported WebView versions. Database related flags are deprecated and no longer control IndexedDB or other modern storage. Cookies are accepted by default unless an app disables them through CookieManager.
Clearing Stored Data¶
Android does not provide a dedicated API to delete the Chromium profile under app_webview. Apps must not attempt to delete this directory directly. The only supported way to remove it is to clear the app's data, either through system settings or by calling ActivityManager.clearApplicationUserData(). However, this might not be desirable if the app wants to retain other user data.
A more adequate approach is to clear individual storage subsystems used by WebView. These include:
- Cached Resources:
WebView.clearCache(true) clears the memory and disk HTTP cache. It does not remove cookies, DOM storage, IndexedDB, OPFS, or other persistent data. - WebStorage APIs:
WebStorage.deleteAllDataclears DOM storage and legacy WebSQL. It does not clear IndexedDB or OPFS. - Cookies:
CookieManager.removeAllCookiesremoves all cookies for the app. - IndexedDB and OPFS: IndexedDB and OPFS are managed internally by Chromium and are not covered by the WebStorage API. They cannot be deleted with Java file APIs such as
java.io.File.deleteRecursively. Clearing requires deleting the entire WebView profile. - SQLite Wasm: SQLite Wasm databases live inside OPFS. They are not Android SQLite databases and cannot be controlled using Android APIs such as
SQLiteDatabase.deleteorSQLiteDatabase.deleteDatabase. Clearing requires deleting the entire WebView profile.
Example:
This example in Kotlin from the open source Firefox Focus app shows different cleanup steps:
override fun cleanup() {
clearFormData() // Removes the autocomplete popup from the currently focused form field, if present. Note that this only affects the display of the autocomplete popup. It does not remove any saved form data from this WebView's store. To do that, use WebViewDatabase#clearFormData.
clearHistory()
clearMatches()
clearSslPreferences()
clearCache(true)
CookieManager.getInstance().removeAllCookies(null)
WebStorage.getInstance().deleteAllData() // Clears all storage currently being used by the JavaScript storage APIs. This includes the Application Cache, Web SQL Database, and the HTML5 Web Storage APIs.
val webViewDatabase = WebViewDatabase.getInstance(context)
// It isn't entirely clear how this differs from WebView.clearFormData()
@Suppress("DEPRECATION")
webViewDatabase.clearFormData() // Clears any saved data for web forms.
webViewDatabase.clearHttpAuthUsernamePassword()
deleteContentFromKnownLocations(context) // Calls FileUtils.deleteWebViewDirectory(context) which deletes all content in "app_webview".
}
The function finishes with some extra manual file deletion in deleteContentFromKnownLocations which calls functions from FileUtils. These functions use the java.io.File.deleteRecursively method to delete files from the specified directories recursively.
private fun deleteContent(directory: File, doNotEraseWhitelist: Set<String> = emptySet()): Boolean {
val filesToDelete = directory.listFiles()?.filter { !doNotEraseWhitelist.contains(it.name) } ?: return false
return filesToDelete.all { it.deleteRecursively() }
}
Challenges of Testing WebView Cache Cleanup¶
Testing WebView cleanup is a complex task that requires extensive information gathering and has several challenges:
- Firstly, the tester needs to identify the number of WebView instances and their respective
WebSettings, and any potential correlation to ensure confinement of test results to only the tested WebView instance. - Secondly, for each WebView instance, the tester needs to identify how the storage areas are configured and how the data is actually being stored based on HTTP cache headers and web storage configuration.
- Thirdly, the tester must determine the lifecycle of every sensitive data item and its designated retention period.
- Finally, there is a lack of a guarantee that the clear methods will always be called, particularly if the app process is killed abruptly before those occur (e.g., due to a system process kill), and in sequence if mitigation measures exist for these scenarios.
Alternatives to WebView¶
Trusted Web Activities and Custom Tabs let your app display web content in the user's browser. With these options, JavaScript runs in the browser environment, and behavior follows the browser's security model and update cycle. For details on capabilities, limitations, and integration guidance, refer to the official Android and Chrome documentation.
Additional Resources¶
You can find more security best practices when using WebViews on Android Developers.