Blog: Android

Android Content Providers 101

David Lodge 13 Feb 2024

Introduction

Android has a number of different types of components that a program or app can instantiate to interact with the user or other programs. Recently I’ve been looking at exported as an interesting way to manipulate information that other apps have stored.

A content provider is what it sounds like – it creates a standard mechanism for allowing access to centralised data. An example may be a fitness tracker could allow a central database of activity which could be queried by other apps to pull out data.

It is accessed in a similar way than you would access a database. A ContentResolver is instantiated and passed a path to the content provider (in the form of a content:// URI) which can then be queried, have data inserted, deleted, updated or even allow programmatic commands. This makes it easy to supply a provider for data that may not be stored in a database or can be accessed through an API.

Interacting with Content Providers

Only providers that have been exported can be interacted with by other apps. Those that are can be identified from an app’s Manifest. The Android dumpsys command can provide a list of export content providers by querying Activity Manager using the dumpsys activity providers command.

A request to a content provider can be made on the command line using the content command, for example, this which would list all the system settings: content query –uri content://settings/system

The query can be tuned with a –where clause and can restrict the columns returned by using a projection (–projection) which is basically a list of column names, for example, the below would return the current value of the system volume.

Where this gets complex is working out the correct URI. The system classes are documented in the provider documentation, but what if there’s a custom app? In this case it requires a bit of reverse engineering. dumpsys will show the authority of a provider and the package name, then it’s a matter of exporting it and reverse engineering the app. For example, if we look at the package com.android.launcher3 we can see this (trimmed for brevity):

The authority gives us the first part of the URI. For anything else we need to do a bit of reverse engineering. The package apk can be found using the pm command: pm list packages -f com.android.launcher3. Then we can extract the apk using adb pull and load it into Android app decompiler of choice (e.g. jadx).

Looking first at the manifest and we can see there’s two exported providers as shown below:

The android:authorities attribute shows the authority and the name shows the class that is called to handle the content provider. Looking at the decompiled code for GridCustomizationsProvider allows us to reverse what happens.

A content provider can be interacted with through multiple channels, those familiar with SQL or REST API calls will recognise the types of requests, including query, update, insert, replace and call (calls a method). These are handled by overloading the specific function in the content provider class.

Looking at the code for GridCustomizationsProvider.query shows us that it accepts the paths /list_options, /icon_themed and /get_icon_themed. We can now construct a URI and use the content command to request data:

Actually working out what the URIs mean can be harder. Further analysis shows that this provider also overrides update, with the paths for /default_grid, /set_icon_themed and /icon_themed. /default_grid takes a name parameter, which is the same as that shown in list options. TL;DR we can now issue a command to change how icons are laid in in the launcher. From a 5×5 layout as below:

We can then change it through the content provider and we get a 4 by 4 grid

Obviously rearranging the grid isn’t much of a security risk; but this is intended as an example of what type of things can be done through a content provider. We have seen providers which can return all information within the app’s database, including credentials as well as overwriting the data.

Securing Content Providers

Some steps can be made around content providers to ensure that they are as secure as needed for their use.

Ensure that the content provider is actually needed and that its task can’t be performed through other components, or even an internal class. Content providers have uses, but does your app use it for the right reason?

Does the content provider need to be accessible by any app? Consider requiring permissions to make requests to the provider. Can the provider perform its function if it hasn’t been exported?

Check the data that can be viewed or edited by the provider. Confidential information should be segregated or filtered out programmatically.

Ensure that the permissions on the content provider are appropriate for the component and its contained information.

Some libraries, such as androidx and firebase will create their own providers for internal use if they are in the gradle file, which will often be the default from Android Studio. These are generally not exported and can be safely ignored.  They may also indicate that the app has been built in debug mode. Look for authorities with names such as:

Testing for insecure Content Providers

Testing an app’s content providers is a relatively simple process, but as explained above requires a bit of reverse engineering. The commands dumpsys activity providers (for all apps) and dumpsys activity provider (for a specific app) can provide [sic] the details of content providers for an app.

Further information can also be pulled from the app’s manifest. This will show the authority and the class that is called. Once the class is known the functions that the content provider supports and the URIs that it uses can be reverse engineered.

Using the content command allows making requests to the functions that have been identified and can be used to ensure that no confidential information can be extracted or altered. It should be noted that this command will be run under the context of the shell and the adb shell user has extra permissions from those that another app may have.

Testing can be performed from an app with a bit of simple programming. A content.android.ContentResolver interface can be instantiated from android.content.Context and this can be queried using a cursor, in a similar way to querying a database. A simple example would be: