This app performs various tests to explore the attack surface and exploit strategies related to Android WebView and CustomTabs. It was developed as part of the Hextree Android app security courses (sponsored by Google).
Signed Builds:
- Debug Build: io.hextree.webview_debug.apk
- Release Build: io.hextree.webview.apk
Table of contents:
The app can load different test cases in the WebView. Each test consists of a single .html file with JavaScript to test access on various origins.
Additionally, the app also explores features of CustomTabs:
- CustomTab without session
- CustomTab with session
- CustomTab with verified origin (oak.hackstree.io)
Depending on the WebView settings and the origin of the loaded .html file, the test case results vary. Settings can be changed on the fly to observe their impact:
setJavaScriptEnabled(boolean)setAllowContentAccess(boolean)setAllowFileAccess(boolean)setAllowFileAccessFromFileURLs(boolean)setAllowUniversalAccessFromFileURLs(boolean)
The test cases can also be loaded from various sources (origins):
- Website (
https://oak.hextree.io/android/webview/...) - APK assets (
file:///android_asset/...) - data URI (
data:text/html;base64,PGgxPmhleHRyZWUuaW88L2gxPgpMZW...) - FileProvider (
content://io.hextree.webprovider/internal/...) - Absolute path (
file:///data/data/io.hextree.webviews/files/...) - Native libs trick (
file:///data/app/~~pUnr.../lib/arm64/...)
Below are some notable results from the tests conducted with this app. Understanding these results is important for developers and bug hunters looking into WebView security.
By default, WebViews can load resources from content:// URIs. This allows an app to expose a malicious .html file through a FileProvider and Intent.FLAG_GRANT_READ_URI_PERMISSION.
- If
setAllowFileAccessFromFileURLs(true)is enabled, JavaScript can leak data from other accessible FileProviders. Depending on the configured file paths (see example below), internal app files could be exposed.
// Example AndroidManifest.xml
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="io.hextree.webview"
android:exported="false" android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
</provider>
// FileProvider config: xml/filepaths.xml
<paths>
<files-path name="internal" path="." />
</paths>- If
setAllowUniversalAccessFromFileURLs(true)is enabled, thecontent://origin is partially treated as a file URL. While XHR requests are blocked, file leaks through<iframe>are possible.
FYI: We have also reported this behavior in case this is a security bug. But Google said this is working as intended.
With scoped storage introduced in Android 11, it is nearly impossible to create a file:// URL readable by other apps:
"On Android 11, apps can no longer access files in any other app's dedicated, app-specific directory within external storage." - Storage updates in Android 11
This makes it difficult to attack WebViews by loading a malicious page from the file:// origin without additional permissions like MANAGE_EXTERNAL_STORAGE. However, we discovered a trick that leverages the extractNativeLibs feature to bypass this restriction and write a malicious .html file into a publicly accessible folder. The steps are as follows:
- Set the
extractNativeLibsattribute in the Android manifest to true.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
<application
android:extractNativeLibs="true"
...- Create the
resources/libfolder in your Android app project. Then create folders for each possible architecture (arm64,arm64-v8a,armeabi,armeabi-v7a,mips,mips64,riscv64,x86,x86_64) and copy a malicious .html into them. - Build the app as an APK in debug mode:
Build > Generate Signed App Bundle / APK > APK > ... > debug - Upon installation the malicious .html will be written into the
getApplicationInfo().nativeLibraryDirdirectory and can be loaded into any WebView withsetAllowFileAccess(true)
Custom Tabs differ from WebViews in that they send an intent to the default browser, which renders the webpage as an overlay on top of the app. Apps can also establish a service connection with the browser to for example exchange postMessages.
A potential issue arises if a malicious app is set as the default browser. In this scenario, any app using Custom Tabs may unintentionally send sensitive data, such as OAuth tokens included in custom headers, to the malicious browser. For example:
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
CustomTabsIntent customTabsIntent = builder.build();
Bundle headers = new Bundle();
headers.putString("Authorization", "Bearer <token>");
customTabsIntent.intent.putExtra(Browser.EXTRA_HEADERS, headers);
customTabsIntent.launchUrl(this, Uri.parse("https://hextree.io"));Though it's not clear yet whether this is a reasonable threat model. On one hand, relying on the default browser for secure operations, such as OAuth flows, can expose sensitive data if a malicious browser is set as default. On the other hand, a malicious default browser is already a significant compromise, making it unclear whether this constitutes a vulnerability in the app or simply an issue arising from user error.


