From f0f82250f424e6d0e04d2bb7371a8ba5ab01d900 Mon Sep 17 00:00:00 2001 From: HackTricks News Bot Date: Tue, 21 Oct 2025 12:45:07 +0000 Subject: [PATCH] Add content from: Account takeover in Android app via JSB --- .../android-app-pentesting/webview-attacks.md | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/src/mobile-pentesting/android-app-pentesting/webview-attacks.md b/src/mobile-pentesting/android-app-pentesting/webview-attacks.md index f7b4dd29a33..1140b1751ce 100644 --- a/src/mobile-pentesting/android-app-pentesting/webview-attacks.md +++ b/src/mobile-pentesting/android-app-pentesting/webview-attacks.md @@ -337,6 +337,109 @@ webView.reload() - To mitigate risks, **restrict JavaScript bridge usage** to code shipped with the APK and prevent loading JavaScript from remote sources. For older devices, set the minimum API level to 17. +#### Abusing dispatcher-style JS bridges (invokeMethod/handlerName) + +A common pattern is a single exported method (e.g., `@JavascriptInterface void invokeMethod(String json)`) that deserializes attacker-controlled JSON into a generic object and dispatches based on a provided handler name. Typical JSON shape: + +```json +{ + "handlerName": "toBase64", + "callbackId": "cb_12345", + "asyncExecute": "true", + "data": { /* handler-specific fields */ } +} +``` + +Risk: if any registered handler performs privileged actions on attacker data (e.g., direct file reads), you can call it by setting `handlerName` accordingly. Results are usually posted back into the page context via `evaluateJavascript` and a callback/promise mechanism keyed by `callbackId`. + +Key hunting steps +- Decompile and grep for `addJavascriptInterface(` to learn the bridge object name (e.g., `xbridge`). +- In Chrome DevTools (chrome://inspect), type the bridge object name in the Console (e.g., `xbridge`) to enumerate exposed fields/methods; look for a generic dispatcher like `invokeMethod`. +- Enumerate handlers by searching for classes implementing `getModuleName()` or registration maps. + +#### Arbitrary file read via URI → File sinks (Base64 exfiltration) + +If a handler takes a URI, calls `Uri.parse(req.getUri()).getPath()`, builds `new File(...)` and reads it without allowlists or sandbox checks, you get an arbitrary file read in the app sandbox that bypasses WebView settings like `setAllowFileAccess(false)` (the read happens in native code, not via the WebView network stack). + +PoC to exfiltrate the Chromium WebView cookie DB (session hijack): + +```javascript +// Minimal callback sink so native can deliver the response +window.WebViewJavascriptBridge = { + _handleMessageFromObjC: function (data) { console.log(data) } +}; + +const payload = JSON.stringify({ + handlerName: 'toBase64', + callbackId: 'cb_' + Date.now(), + data: { uri: 'file:///data/data//app_webview/Default/Cookies' } +}); + +xbridge.invokeMethod(payload); +``` + +Notes +- Cookie DB paths vary across devices/providers. Common ones: + - `file:///data/data//app_webview/Default/Cookies` + - `file:///data/data//app_webview_/Default/Cookies` +- The handler returns Base64; decode to recover cookies and impersonate the user in the app’s WebView profile. + +Detection tips +- Watch for large Base64 strings returned via `evaluateJavascript` when using the app. +- Grep decompiled sources for handlers that accept `uri`/`path` and convert them to `new File(...)`. + +#### Bypassing WebView privilege gates – endsWith() host checks + +Privilege decisions (selecting a JSB-enabled Activity) often rely on host allowlists. A flawed pattern is: + +```java +String host = Uri.parse(url).getHost(); +boolean z = true; +if (!host.endsWith(".trusted.com")) { + if (!".trusted.com".endsWith(host)) { + z = false; + } +} +// z==true → open privileged WebView +``` + +Equivalent logic (De Morgan’s): + +```java +boolean z = host.endsWith(".trusted.com") || + ".trusted.com".endsWith(host); +``` + +This is not an origin check. Many unintended hosts satisfy the second clause, letting untrusted domains into the privileged Activity. Always verify scheme and host against a strict allowlist (exact match or a correct subdomain check with dot-boundaries), not `endsWith` tricks. + +#### javascript:// execution primitive via loadUrl + +Once inside a privileged WebView, apps sometimes execute inline JS via: + +```java +webView.loadUrl("javascript:" + jsPayload); +``` + +If an internal flow triggers `loadUrl("javascript:...")` in that context, injected JS executes with bridge access even if the external page wouldn’t normally be allowed. Pentest steps: +- Grep for `loadUrl("javascript:` and `evaluateJavascript(` in the app. +- Try to reach those code paths after forcing navigation to the privileged WebView (e.g., via a permissive deep link chooser). +- Use the primitive to call the dispatcher (`xbridge.invokeMethod(...)`) and reach sensitive handlers. + +Mitigations (developer checklist) +- Strict origin verification for privileged Activities: canonicalize and compare scheme/host against an explicit allowlist; avoid `endsWith`-based checks. Consider Digital Asset Links when applicable. +- Scope bridges to trusted pages only and re-check trust on every call (per-call authorization). +- Remove or tightly guard filesystem-capable handlers; prefer `content://` with allowlists/permissions over raw `file://` paths. +- Avoid `loadUrl("javascript:")` in privileged contexts or gate it behind strong checks. +- Remember `setAllowFileAccess(false)` doesn’t protect against native file reads via the bridge. + +#### JSB enumeration and debugging tips + +- Enable WebView remote debugging to use Chrome DevTools Console: + - App-side (debug builds): `WebView.setWebContentsDebuggingEnabled(true)` + - System-side: modules like [LSPosed](https://github.com/LSPosed/LSPosed) or Frida scripts can force-enable debugging even in release builds. Example Frida snippet for Cordova WebViews: [cordova enable webview debugging](http://codeshare.frida.re/@gameFace22/cordova---enable-webview-debugging/) +- In DevTools, type the bridge object name (e.g., `xbridge`) to see exposed members and probe the dispatcher. + + ### Reflection-based Remote Code Execution (RCE) - A documented method allows achieving RCE through reflection by executing a specific payload. However, the `@JavascriptInterface` annotation prevents unauthorized method access, limiting the attack surface. @@ -393,6 +496,9 @@ xhr.send(null) - [Samsung S24 Exploit Chain Pwn2Own 2024 Walkthrough](https://medium.com/@happyjester80/samsung-s24-exploit-chain-pwn2own-2024-walkthrough-c7a3da9a7a26) - [Pwn2Own Ireland 2024 – Samsung S24 attack chain (whitepaper)](https://maliciouserection.com/2025/05/13/pwn2own-ireland-2024-samsung-s24-attack-chain-whitepaper.html) - [Demonstration video](https://www.youtube.com/watch?v=LAIr2laU-So) +- [Account takeover in Android app via JSB – tuxplorer.com](https://tuxplorer.com/posts/account-takeover-via-jsb/) +- [LSPosed – systemless Xposed framework](https://github.com/LSPosed/LSPosed) +- [Frida codeshare: Cordova – enable WebView debugging](http://codeshare.frida.re/@gameFace22/cordova---enable-webview-debugging/) {{#include ../../banners/hacktricks-training.md}}