脆弱性対策方法

開発者の不注意や確認不足が、脆弱性につながることが理解できたと思います。
次は、サンプルコードを基に対策方法を学んでいきましょう。

脆弱性の原因

本シナリオでの脆弱性の原因は以下となります。
  • ContentProviderが、アクセス制限の不備により、全てのアプリで読み込み、書き込み可能な状態で公開されている
    このアプリは、読み取り専用でContentProviderを公開することを想定しているので、ContentProviderのアクセス制限の設定を変更する事が対策となります。

対策方法の概要

実際に脆弱性のあるソースコードを確認し、修正してみましょう。
まず、「ソースコードを開く」ボタンをクリックし、AndroidManifest.xmlを開いてください。

ソースコードを開く

修正前:
プロジェクト ComponentContentProvider
ソースファイル AndroidManifest.xml
<uses-sdk
    android:minSdkVersion="10"
    android:targetSdkVersion="10"/>

<application
    android:allowBackup="true"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme" >

    <!-- ▼▼▼脆弱性のあるソースコード▼▼▼ -->
    <provider android:name=".PictureProvider"
              android:authorities="jp.go.ipa.sample.component.contentprovider">
    </provider>
    <!-- ▲▲▲脆弱性のあるソースコード▲▲▲ -->


このアプリには脆弱性対策を行うべき箇所が2箇所あります。修正例は以下の通りです。
  • 10行目
    ContentProviderを保護するために、Permissionの使用を宣言します。また、使用するPermissionも同時に定義します。
    修正前コードの10行目と11行目の間に、以下のコードを追加します。
    <uses-permission android:name="jp.go.ipa.sample.component.contentprovider.permission.write"/>
    次に書き込みをする際に必要となるPermissionを宣言します。
    <permission 
    	  android:name="jp.go.ipa.sample.component.contentprovider.permission.write"
    	  android:protectionLevel="signature"/> 
    修正後のソースコードの12行目〜15行目にあたる部分です。

  • 18~20行目
    読み込みは許可する必要があるので、android:exported属性は設定せず、初期値のtrueを設定しておいて構いません。
    Android4.1以降のバージョンで開発を行う場合は、android:exported属性を設定し、trueを明示的に設定するようにしましょう。
    ContentProviderは読み込み、書き込みのそれぞれに対してPermissionを設定することができます。
    本シナリオでは、「読み込みは全てのアプリに対して許可」「書き込みは許可しない」ですので、書き込み用のPermissionを定義すれば良いことになります。

    修正前コード19行目の「>」を削除します。それから、修正前のソースコードの19行目と20行目の間に、 上で定義したPermissionをwritePermissionとして追加します(赤文字部分)。
    <provider android:name=".PictureProvider"
              android:authorities="jp.go.ipa.sample.component.contentprovider"
              android:writePermission="jp.go.ipa.sample.component.contentprovider.permission.write">
    </provider>
    このように、保護レベルsignatureのPermissionを使用してContentProviderを保護することで、このPermissionの利用を宣言していないアプリからの書き込みを防ぐことができます。
    この例では、Permissionの保護レベルをsignatureとして、署名が一致しているアプリだけが書き込みを行えるように保護しています。

修正後:
プロジェクト ComponentContentProvider
ソースファイル AndroidManifest.xml
<uses-sdk
    android:minSdkVersion="10"
    android:targetSdkVersion="10"/>

    <!-- ▼▼▼脆弱性を修正したソースコード▼▼▼ -->
    <uses-permission android:name="jp.go.ipa.sample.component.contentprovider.permission.write"/>
    <permission 
        android:name="jp.go.ipa.sample.component.contentprovider.permission.write"
        android:protectionLevel="signature"/>
    <!-- ▲▲▲脆弱性を修正したソースコード▲▲▲ -->

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme">

        <!-- ▼▼▼脆弱性を修正したソースコード▼▼▼ -->
        <provider android:name=".PictureProvider"
            android:authorities="jp.go.ipa.sample.component.contentprovider"
            android:writePermission="jp.go.ipa.sample.component.contentprovider.permission.write">
        </provider>
        <!-- ▲▲▲脆弱性を修正したソースコード▲▲▲ -->

対策のまとめ

「ContentProviderのアクセス制限不備」による脆弱性について、対策は以下のとおりです。
  • 対策
    ContentProviderに対するデータ操作それぞれについて適切なPermissionを明示的に指定する。
  • 修正方法
    readPermission, writePermissionなどの指定を行う。
参考情報
ContentProviderは、他のアプリとデータを共有するためのコンポーネントであるため、データに対するアクセス制御を細かくできるようになっています。
他のコンポーネントがandroid:exportedによる公開、非公開の設定だけであるのにContentProviderは6種類のPermissionを指定できるようになっています。
Permission属性 用途
Permission アクセスするために必要なPermissionを指定する。
readPermission 読込みのために必要なPermissionを指定する。<Permission>より優先
writePermission 書き込みのために必要なPermissionを指定する。<Permission>より優先
path-permission 特定のパスにアクセスするためのPermissionを指定する。子要素として<readPermission>、<writePermission>を持つ
grantUriPermissions 一時的に特定のアプリに対してアクセスを許可する時に指定する。falseの時、<grant-uri-permission>で詳細を指定できる
grant-uri-permission 特定のパスについて、一時的に特定のアプリに対してアクセスを許可する時に指定する。


AndroidManifest.xml上での定義は以下のような形で行います。
赤文字部分がPermissionの定義にあたります。
<providerandroid:authorities="list"
    android:enabled=["true" | "false"]
    android:exported=["true" | "false"]
    android:grantUriPermissions=["true" | "false"]
    android:icon="drawable resource"
    android:initOrder="integer"
    android:label="string resource"
    android:multiprocess=["true" | "false"]
    android:name="string"
    android:permission="string"
    android:process="string"
    android:readPermission="string"
    android:syncable=["true" | "false"]
    android:writePermission="string">
    . . .
</provider>
それぞれのPermissionの使用例として、Androidが提供する連絡先のContentProviderの定義を挙げます。
<providerandroid:name="ContactsProvider2"
    android:authorities="contacts;com.android.contacts"
    android:label="@string/provider_label"
    android:readPermission="android.permission.READ_CONTACTS"
    android:writePermission="android.permission.WRITE_CONTACTS">
    <path-permission android:pathPrefix="/search_suggest_query"
        android:readPermission="android.permission.GLOBAL_SEARCH" />
    <path-permission android:pathPrefix="/search_suggest_shortcut"
        android:readPermission="android.permission.GLOBAL_SEARCH" />
    <path-permission android:pathPattern="/contacts/.*/photo"
        android:readPermission="android.permission.GLOBAL_SEARCH" />
    <grant-uri-permission android:pathPattern=".*" />
</provider>
この例を見ると、全体に対して読み込み、書き込みに必要なreadPermission、writePermissionが定義されていることが分かります。
また、path-permissionによって、android.permission.GLOBAL.SERACH Permissionを持っているアプリは、読み込みに必要なandroid.permission.READ_CONTACTSを持っていなくても、
  • search_suggest_query の呼び出し
  • search_suggest_shortcut の呼び出し
  • /contacts/.*/photo (連絡先の写真の情報)の読み込み
を行うことができるように設定されています。

  • grant-uri-permissionについて
    grant-uri-permissionは、他のPermissionとは機能の仕方が異なります。
    grant-uri-permissionは、特定のタイミングにおいて一時的なアクセス許可を与える際に使用します。
    以下に代表的な用例を挙げます。
    下の図は、メールアプリが添付ファイルを処理する際の処理の流れを示したものです。
    利用者がメールアプリ上でメールに添付されていた画像ファイルを開こうとしたときを想定しています。
    • メールアプリから暗黙的Intentが発行される
      メールアプリは添付ファイルを開く操作が行われたことを検知し、画像を表示できるアプリを探すために暗黙的Intentを発行します。 この時、Intentには画像の場所(メールアプリのDB内)を示す情報が格納されています。
    • 画像表示アプリが添付ファイルを開こうとする
      画像表示アプリは受け取ったIntentの情報に格納された画像の場所にアクセスし、画像を開こうとしますが、 画像表示アプリは他アプリであるメールアプリのDBにアクセスすることはできません。


    grant-uri-permissionを使用することで、メールアプリは画像表示アプリに対して一時的にDBへのアクセス許可を与えることができます。 ContentProviderにgrant-uri-permissionを定義し、かつIntentを発行する際にIntent#FAG_GRANT_READ_URI_PERMISSIONやIntent#FLAG_GRANT_WRITE_URI_PERMISSIONフラグを設定することでIntentを受け取ったアプリが一時的にContentProviderへアクセスすることができるようになります。
    以下はIntentを発行する際のコード例です。
    赤文字部分で、IntentにFLAG_GRANT_WRITE_URI_PERMISSIONフラグを設定しています。
    //Intentを作成する
    Intent intent = new Intent();
    //処理要求を指定する
    intent.setAction(Intent.ACTION_VIEW);
    // 一時的にアクセスを許可するURI をIntent に指定する
    intent.setData(TestMailProvider.Atachment.CONTENT_URI);
    // 一時的に許可するアクセス権限をIntent に指定する
    intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    // 暗黙的Intent を送信する
    startActivity(intent);