はじめに
Androidにはデータを読み書き/共有するための仕組みとしてContentProviderがあります。データの読み書きを簡単に実装するためのインターフェースも備えていて便利なものです。また、Androidでは基本的に他のアプリケーションとデータをやり取りすることはできません。自分が作成したアプリケーションのデータを他のアプリケーションで利用してもらう場合、データを共有する方法の一つとして、ContentProviderを活用できます。例えば、Android端末に標準でインストールされているContacts(連絡先)のデータはContentProviderを使用して取得することができます。
アプリケーション内でのデータの読み書きを簡単に実装することができたり、他のアプリケーションとデータを共有することができたりと便利な仕組みですが、そのアクセス範囲を意識しておかないと、本来は公開したくない情報まで外部に公開してしまい、他のアプリケーションから自由にアクセスされてしまいます。
アクセス制限方法
では実際にアクセスを制限する方法を見ていきましょう。独自のContentProviderを実装していく上でマニフェストファイル(AndroidManifest.xml)に登録を行いますが、その部分でパーミッションを設定し、ContentProviderを非公開にするか他のアプリケーションにも公開するのかアクセスできる権限を指定することになります。
他のアプリケーションに公開
例えば、ContentProviderを継承してSampleContentProviderを実装したとし、マニフェストファイルに次のように記述したとします。
<provider android:name="SampleContentProvider" android:authorities="com.example.app.Provider" />
パーミッションを指定していないこのような場合は、他のアプリケーションからSampleContentProviderにアクセスすることができます。
非公開
このContentProviderを持っているアプリケーションだけがアクセスできるように制限するには、android:exportedプロパティを明示的にfalseにします。こうすると、他のアプリケーションからは一切アクセスできなくなります。
<provider android:name="SampleContentProvider" android:authorities="com.example.app.Provider" android:exported="false" />
特定のパーミッションで制限
指定されたパーミッションを宣言しているアプリケーションのみアクセスできるようにするには、android:permissionプロパティにパーミッション名を記述し、permission要素で宣言します。
<provider android:name="SampleContentProvider" android:authorities="com.example.app.Provider" android:permission="com.example.app.permission.Provider" /> <permission android:name="com.example.app.permission.Provider" />
こうすることで、uses-permission要素でそのパーミッションを要求しないと、アプリケーションではSampleContentProviderにはアクセスできなくなります。
<uses-permission android:name="com.example.app.permission.Provider" />
同じ署名を持ったアプリケーションのみ
同じ署名を持ったアプリケーションのみSampleContentProviderにアクセスできるようにするには、permission要素のandroid:protectionLevelプロパティにsignatureと記述します。
<provider android:name="SampleContentProvider" android:authorities="com.example.app.Provider" android:permission="com.example.app.permission.Provider" /> <permission android:protectionLevel="signature" android:name="com.example.app.permission.Provider"> </permission>
こうすることで、SampleContentProviderにアクセスできるアプリケーションを、特定のパーミッションを許可されていることに加えて、元のアプリケーションと同じ署名を持っているもののみに制限することができます。
実際にあった例
DropboxのAndroid用クライアントアプリケーションであるDropbox for AndroidでこのContentProvider絡みの脆弱性が実際にありました。
この脆弱性があったバージョンのうち、1.1.3のマニフェストファイルを確認すると、ContentProviderを継承したDropboxProviderに対して、
<provider android:name=".provider.DropboxProvider" android:authorities="com.dropbox.android.Dropbox"> <grant-uri-permission android:pathPrefix="/" /> </provider>
このように特にアクセス制限をかけていなかったため、他のアプリケーションからも読み書きなどが自由にできる状態になっており、例えば、他のアプリケーションからデータベースに変更を加え、Dropboxの公開用フォルダである「Public」フォルダにDropboxのアカウント情報が含まれている設定ファイルをアップロードさせたりすることが可能でした。
現在の最新バージョン(執筆時点で1.2.4)では、この問題に対して以下のように対策されています。
<provider android:name=".provider.DropboxProvider" android:exported="false" android:authorities="com.dropbox.android.Dropbox"> <grant-uri-permission android:pathPrefix="/" /> </provider>
このようにandroid:exportedプロパティでfalseを指定し、他のアプリケーションからはアクセスできないようになっています。
まとめ
ContentProviderはデータの読み書き/共有を実装する上で便利なものですが、そのデータは本当に他のアプリケーションに公開する必要があるのか、また、公開する場合もContentProviderにアクセスできる範囲をしっかり意識して設定する必要があります。