Androidアプリ実装におけるセキュリティ(1) – Androidコンポーネント編 -
はじめに
Androidのセキュリティについて考える際は、脆弱性が生じるレイヤであったり、対策を講じるレイヤをきちんと意識することが重要です。大雑把に分類すると、カーネルとミドルウェア、そしてアプリの3つのレイヤがあります。本記事ではアプリレイヤの、その中でもネイティブアプリを対象としたセキュリティについて解説します。
なお本記事で用いる用語を以下のように定義します。
脆弱性
アプリのデータにアクセスしたり、他のアプリの機能を不正に使用できるようなバグ、または仕様上の弱点のこと
脅威
脆弱性を利用した攻撃のこと。
想定される悪影響
脅威によって引き起こされる不利益のこと。
対策
悪影響の発生を防ぐために、脆弱性をなくしたり脅威を防ぐためにとる方法のこと。
Androidアプリにおけるセキュリティリスクの全体像
一口にセキュリティ対策といっても、脆弱性が生じる箇所は様々です。Androidアプリで生じうる主な脆弱性を表すと下図のようになります。
Androidアプリの構成要素とセキュリティリスクの発生箇所の対応関係
脆弱性が生じる原因について考えると、そのほとんどが以下の2つに分類できると考えられます。
- Android SDK部品(API)の誤った使用方法により生じる脆弱性…(1), (2), (3)
- Androidアプリの解析容易性…(4)
本記事では、(1)~(4)のそれぞれについて、脆弱性が生じる仕組みや対策方法を連載で解説していきます。
本記事はその第一回目として、「(1) 他アプリからのAndroidコンポーネントの不正利用の脆弱性」について解説します。
脆弱性と対策の概要
Androidコンポーネントの公開範囲の設定を誤り、意図せずに他のアプリから呼び出し可能な状態となってしまう場合があります。
Androidコンポーネントの公開に関わる属性やパーミッションの仕組み、注意点について理解した上で、必要最小限の公開範囲となるよう適切に設定する必要があります。
脆弱性
Androidコンポーネントの公開範囲に不備がある場合、他のアプリから自由に呼び出される。
脅威
悪意のあるアプリからアクティビティに対して不正なデータを送りつけることで、本来は権限が必要な処理を実行されるなど、不正な操作をされる。
想定される悪影響
アプリの不正操作、アプリの機能を利用した不正アクセス、情報漏洩など。
対策
AndroidコンポーネントをAndroidManifestに定義する際、公開設定やパーミッションを適切に設定する。特に以下の点に注意する。
(1) android:exported属性を省略しない
(2) 特定のアプリにのみ公開したいAndroidコンポーネントを定義する際は、パーミッションに頼った実装には問題があるため、android:sharedUserID
属性の使用または独自のチェック処理を実装する
解説
Androidアプリの主要部品であるAndroidコンポーネントの「公開」という仕組みと、公開範囲の設定方法および誤りが生じ易いポイントについて解説します。
Androidコンポーネントの公開範囲を決定する属性
Android SDKのうちアプリ開発において重要な役割を果たす以下の4つの部品をまとめてAndroidコンポーネントと呼びます。
- アクティビティ(
Activity
クラス) - サービス(
Service
クラス) - ブロードキャストレシーバ(
BroadcastReceiver
クラス) - コンテンツプロバイダ(
ContentProvider
クラス)
これらの部品は、他のアプリと容易に連携できるようにするため、インターフェースを公開できる仕組みが備わっています。
公開範囲も段階的に設定でき、AndroidManifestファイル内に記述するactivity要素やservice要素などに対して以下の属性を設定することで決定されます。
属性名 | 設定値 | 説明 |
---|---|---|
android:exported | true または false | 他のアプリに対してこのAndroidコンポーネントを公開するかどうかを決定する。 |
android:permission | 任意のパーミッション名を表す文字列 | 他のアプリからこのAndroidコンポーネントを使用する際に必要なパーミッションを設定する。 ここで定義されたパーミッションを与えられていないアプリからは、このAndroidコンポーネントを使用できない。 |
以下はAndroidManifest内で「インターネット接続を認められているアプリ(INTERNETパーミッションを持つアプリ)からのみ使用できるアクティビティ」を定義する記述例です。
<activity android:name='RequiresInternetActivity' android:exported='true' android:permission='android.permission.INTERNET' > </activity>
注意点(1) android:exported
属性を省略しない
ここで一つ目の注意点として、android:exported属性の設定は省略することもできますが、省略時のデフォルト値がAndroidコンポーネントによって異なるため、意図しない公開範囲が設定される恐れがあります。そのため、明示的に指定したほうがよいでしょう。
具体的には、以下のデフォルト値が設定されます。
Androidコンポーネント | デフォルト値 |
---|---|
アクティビティ | false |
サービス | false |
ブロードキャストレシーバ | false |
コンテンツプロバイダ | `true`(Android 4.1以前) `false`(Android 4.2) |
注意点(2) android:permission属性によるセキュリティ対策は無効化できるため使用しない
二つ目の注意点としては、android:permission属性に指定するパーミッションについてです。
パーミッションには保護レベルという属性が設定されており、パーミッションを要求するアプリをインストールする際には、インストール条件を満たすかどうかが以下のようにして確認されるようになります。
保護レベルの設定値 | パーミッションを要求するアプリのインストール条件 |
---|---|
normal | この保護レベルのパーミッションを要求するアプリは、インストール時に何の確認も行わない。 |
dangerous | この保護レベルのパーミッションを要求するアプリは、インストール時に使用するパーミッションの一覧を表示し、インストールを続行するかどうかをユーザに選択させる。 |
signature | この保護レベルのパーミッションを要求するアプリは、パーミッションの定義元のアプリと同じ証明書で署名されていない限りインストールすることができない。 |
signatureOrSystem | システムレベルで動作するアプリ専用のパーミッションに設定されており、サードパーティのアプリには使用できない。 |
例えばINTERNETパーミッションは、保護レベルがdangerousに設定されているパーミッションです。そのためこのパーミッションを要求するアプリをインストールする際には、「このアプリは自由にインターネット接続する処理を実行できますが、インストールしてもよいですか?」といった内容を確認する画面が表示されます(下図)。
インストール時のパーミッションの確認画面
この仕組みをうまく用いると、特定のアプリにのみAndrodコンポーネントを公開できるようにも思われます。
具体的には、保護レベルがsignatureの独自パーミッションを定義し、これを与えられたアプリにのみ公開されるAndroidコンポーネントを定義する、というアイデアです。<br上記対応を行うことで、共通の証明書で署名されたアプリ間でのみ連携できるようになると考えられます。
しかし、このパーミッションによるアプリのインストール制限は、実質的に無効化できてしまう問題があり、セキュリティ上の目的で使用するのは不適切です。
なぜならば、端末内に既に同名のパーミッションの定義がある場合には、定義情報が上書きされないためです。
すなわち、保護レベルがsignatureのパーミッションを定義する前に、同名のパーミッションを弱いレベル(例えばnormal)で定義することによって、共通の証明書を持たないアプリからもAndroidコンポーネントが呼び出せてしまう状態にできてしまいます。
属性値の組み合わせと公開範囲
以上を踏まえ、公開範囲の決定に関する属性値の組み合わせについて整理すると、以下の通りになります。
exported属性 | permission属性 | protectionLevel属性 | 決定される公開範囲 |
---|---|---|---|
true | 無 | - | 他のアプリから自由に呼び出し可能 |
true | 有 | normalまたはdangerous | 指定のパーミッションを持つアプリからのみ 呼び出し可能? |
true | 有 | signature | 指定のパーミッションを持ち、かつ同一の 証明書を持つアプリからのみ呼び出し可能? |
false | 無 | - | 自アプリからのみ呼び出し可能 |
ただし、背景が黄色の設定パターンにおいては、上述した問題によって、他のアプリから自由に呼び出し可能な状態にできてしまいます。
これを防ぎ、特定のアプリ間でのみAndroidコンポーネントのインターフェースを安全に共有するためには、次の節を参照してください。
Androidコンポーネントを安全に共有する方法
上述の通り、パーミッションのみを利用した方法では、Androidコンポーネントのインターフェースを安全に共有することはできません。
そのかわり、Androidコンポーネントを共有したい両アプリを、同じ証明書で署名することが可能であれば、アプリの所有者を同一ユーザに設定できるandroid:sharedUserId属性を使用することができます。
android:sharedUserId属性の設定値が同一かつ署名に使用する証明書が同一であるアプリは、その所有者が同一ユーザとなり、互いの全てのリソースを共有することができるようになります。
どうしても異なる証明書を利用しなくてはならず、android:sharedUserId属性が使用できない場合は、呼び出しを許可するアプリの証明書のハッシュ値を予めお互いのアプリ内に埋め込んでおきホワイトリストを作成する対策が有効です。
コンポーネントが呼び出された際は、呼び出したアプリがホワイトリストに登録されているかどうかをチェックすることで、動作の制限が可能です(下図参照)。
証明書のハッシュ値検証による呼び出し元アプリの制限
具体的な実装例は、日本スマートフォンセキュリティ協会(JSSEC)が公開している「Androidアプリのセキュア設計・セキュアコーディングガイド」を参照してください。