ユーザー資格情報やプライバシー情報などの機密データを保護することはモバイルセキュリティの重要な焦点です。この章では、Android がローカルデータストレージ用に提供する API と、それらの API を使用するためのベストプラクティスについて学びます。
「機密データ」はそれぞれ特定のアプリのコンテキストで識別する必要があることに注意します。データの分類については「テストプロセスと技法」の章で詳しく説明しています。
通念として、永続的なローカルストレージには可能な限り機密データを保存しないことを推奨しています。しかし、ほとんどの実際のシナリオでは、少なくともいくつかのタイプのユーザー関連データを格納する必要があります。例えば、アプリを起動するごとに非常に複雑なパスワードを入力するようにユーザーに依頼することは、ユーザビリティの観点からよい考えとはいえません。その結果、ほとんどのアプリではある種のセッショントークンをローカルにキャッシュする必要があります。個人識別情報などの他の種類の機密データも、特定のシナリオで必要とされる場合には保存されることもあります。
機密データがアプリにより適切に保護されていない場合に永続的にそれを格納すると脆弱性が発生します。デバイスのローカルや外部 SD カードなどのさまざまな場所にアプリは機密データを格納する可能性があります。この種の問題を悪用しようとする場合には、さまざまな場所に処理および格納された多くの情報がある可能性を考慮します。重要なのは、どのような種類の情報がそのモバイルアプリケーションにより処理されユーザーにより入力されるか、また、何が攻撃者にとって興味深く価値のあるものであるか (パスワード、クレジットカード情報、PII など) を最初から特定することです。
機密情報を開示することによる結果はさまざまです。例えば暗号鍵の開示は攻撃者により使用され情報を解読されます。より一般的に言えば、攻撃者はこの情報を特定して、他の攻撃の基礎として使用できます。例えば、ソーシャルエンジニアリング (PII が開示されている場合) 、セッションハイジャック (セッション情報やトークンが開示されている場合) があります。また、支払オプションを持つアプリから情報を収集して、それを攻撃および悪用することもあります。
データの格納 [1] は多くのモバイルアプリケーションで不可欠です。例えば、ユーザー設定やユーザーが入力したデータを記録するために、ローカルやオフラインで保存する必要があります。データはさまざまな方法で永続的に格納されます。以下のリストは Android プラットフォームで利用可能なこれらのメカニズムを示しています。
- Shared Preferences
- 内部ストレージ
- 外部ストレージ
- SQLite データベース
以下のコードスニペットは機密情報を開示するバッドプラクティスを示していますが、Android のさまざまなストレージメカニズムも詳細に示しています。
SharedPreferences [2] は XML 構造を使用してキー/値のペアをファイルシステムに永続的に格納する一般的なアプローチです。アクティビティ内では、以下のコードを使用してユーザー名やパスワードなどの機密情報を格納できます。
SharedPreferences sharedPref = getSharedPreferences("key", MODE_WORLD_READABLE);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putString("username", "administrator");
editor.putString("password", "supersecret");
editor.commit();
アクティビティが呼ばれると、ファイル key.xml が提供されたデータで作成されます。このコードはいくつかのベストプラクティスに違反しています。
- ユーザー名とパスワードが平文で
/data/data/<PackageName>/shared_prefs/key.xml
に格納されています
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="username">administrator</string>
<string name="password">supersecret</string>
</map>
MODE_WORLD_READABLE
はすべてのアプリケーションがkey.xml
のコンテンツにアクセスおよび読むことを許可します
root@hermes:/data/data/sg.vp.owasp_mobile.myfirstapp/shared_prefs # ls -la
-rw-rw-r-- u0_a118 u0_a118 170 2016-04-23 16:51 key.xml
MODE_WORLD_READABLE
およびMODE_WORLD_WRITEABLE
は API 17 で廃止されたことに注意してください。これは新しいデバイスには影響しませんが、Android 4.2 (JELLY_BEAN_MR1
) より前の OS で動作する場合、android:targetSdkVersion が 17 より前の設定でコンパイルされたアプリケーションは依然として影響を受ける可能性があります。
SQLite は .db
ファイルにデータを格納する SQL データベースです。Android SDK には SQLite データベースのサポートが組み込まれています。データベースを管理する主なパッケージは android.database.sqlite
です。
アクティビティ内では、以下のコードを使用してユーザー名やパスワードなどの機密情報を格納できます。
SQLiteDatabase notSoSecure = openOrCreateDatabase("privateNotSoSecure",MODE_PRIVATE,null);
notSoSecure.execSQL("CREATE TABLE IF NOT EXISTS Accounts(Username VARCHAR,Password VARCHAR);");
notSoSecure.execSQL("INSERT INTO Accounts VALUES('admin','AdminPass');");
notSoSecure.close();
アクティビティが呼び出されると、データベースファイル privateNotSoSecure
が提供されたデータで作成され、/data/data/<PackageName>/databases/privateNotSoSecure
に平文で格納されます。
databases ディレクトリには SQLite データベースのほかにいくつかのファイルがある可能性があります。
- ジャーナルファイル: これらは SQLite のアトミックコミットとロールバック機能を実装するために使用される一時ファイルです [3] 。
- ロックファイル: ロックファイルは SQLite の同時並行性を向上させ、writer starvation 問題を低減するために設計されたロックとジャーナルのメカニズムの一部です [4] 。
暗号化なしの SQLite データベースを機密情報の格納に使用すべきではありません。
ライブラリ SQLCipher [5] を使用すると、パスワードを提供することで SQLite データベースが暗号化できます。
SQLiteDatabase secureDB = SQLiteDatabase.openOrCreateDatabase(database, "password123", null);
secureDB.execSQL("CREATE TABLE IF NOT EXISTS Accounts(Username VARCHAR,Password VARCHAR);");
secureDB.execSQL("INSERT INTO Accounts VALUES('admin','AdminPassEnc');");
secureDB.close();
暗号化ありの SQLite データベースを使用する場合、パスワードがソースにハードコードされているかどうか、shared preferences に格納されているか、コードやファイルシステムのどこかに隠されているかどうかを確認します。 キーを取得するセキュアなアプローチは、ローカルに格納するのではなく、次のいずれかになります。
- アプリを開く際、毎回ユーザーに PIN やパスワードを問い合わせ、データベースを復号します (弱いパスワードや PIN はブルートフォース攻撃を受けやすくなります)
- サーバーにキーを格納し、Web サービス経由でアクセス可能にします (アプリはデバイスがオンラインの場合のみ使用できます)
ファイルはデバイスの内部ストレージ [6] に直接保存できます。デフォルトでは、内部ストレージに保存されたファイルはアプリケーション専用であり、他のアプリケーションはアクセスできません。ユーザーがアプリケーションをアンインストールすると、これらのファイルは削除されます。 アクティビティ内で、以下のコードを使用して、変数 test の機密情報を内部ストレージに永続的に格納できます。
FileOutputStream fos = null;
try {
fos = openFileOutput(FILENAME, Context.MODE_PRIVATE);
fos.write(test.getBytes());
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
ファイルモードをチェックする必要があります。MODE_PRIVATE
を使用して、そのアプリ自身のみがファイルにアクセスできることを確認します。MODE_WORLD_READABLE
(非推奨) や MODE_WORLD_WRITEABLE
(非推奨) などの他のモードはとても緩く、セキュリティリスクを引き起こす可能性があります。
クラス FileInputStream
を検索して、どのファイルがアプリ内で読み込まれているかもチェックすべきです。内部ストレージメカニズムの一部にはキャッシュストレージもあります。一時的にデータをキャッシュするために、getCacheDir()
などの関数を使用する可能性があります。
すべての Android 互換デバイスは共有外部ストレージ [7] をサポートしており、ファイルを保存するために使用できます。これはリムーバブルストレージメディア (SD カードなど) や内部 (非リムーバブル) ストレージがあります。
外部ストレージに保存されたファイルは world-readable であり、ユーザーが変更できます。USB マスストレージを有効にすると、コンピュータ上にファイルを転送できます。
アクティビティ内では、以下のコードを使用して、ファイル password.txt
の機密情報を外部ストレージに永続的に格納できます。
File file = new File (Environment.getExternalFilesDir(), "password.txt");
String password = "SecretPassword";
FileOutputStream fos;
fos = new FileOutputStream(file);
fos.write(password.getBytes());
fos.close();
アクティビティが呼び出されると、提供されたデータでファイルが作成され、データは平文で外部ストレージに格納されます。
アプリケーションフォルダ (data/data/<packagename>/
) の外に格納されたファイルは、ユーザーがアプリケーションをアンインストールしたときに削除されないことも知っておく価値があります。
KeyChain クラス [10] は システムワイドの 秘密鍵とそれに関連する証明書 (チェーン) を格納および取得するために使用されます。ユーザーは、初めて KeyChain に何かがインポートされた際に、ロック画面の PIN やパスワードが設定されていない場合、資格情報ストレージを保護するためにそれらを設定するように求められます。キーチェーンはシステムワイドであることに注意します。つまり、すべてのアプリケーションが KeyChain に格納されているマテリアルにアクセスできます。
Android KeyStore [8] は (多かれ少なかれ) セキュアな資格情報ストレージの手段を提供します。Android 4.3 以降では、アプリの秘密鍵を格納及び使用するためにパブリック API を提供しています。アプリは秘密鍵・公開鍵のペアを作成して、公開鍵を使用してアプリケーションの秘密を暗号化し、秘密鍵を使用してそれを復号できます。
Android KeyStore に格納された鍵は保護され、ユーザーがアクセスするためには認証が必要とすることが可能です。ユーザーのロック画面の資格情報 (パターン、PIN、パスワード、指紋) が認証に使用されます。
格納された鍵は次の二つのモードのいずれかで動作するように設定できます。
-
ユーザー認証は一定期間の鍵の使用を許可します。このモードのすべての鍵は、ユーザーがデバイスのロックを解除するとすぐに、使用を許可されます。許可が有効である期間は各鍵ごとにカスタマイズできます。このオプションはセキュアロック画面が有効である場合にのみ使用できます。ユーザーがセキュアロック画面を無効にすると、格納されている鍵は完全に無効になります。
-
ユーザー認証はひとつの鍵に関連付けられた特定の暗号化操作を許可します。このモードでは、そのような鍵を含む操作はユーザーにより個別に許可される必要があります。現在、そのような許可の唯一の手段は指紋認証です。
Android KeyStore により提供されるセキュリティのレベルはその実装に依存し、デバイス間で異なります。最新のデバイスのほとんどはハードウェア支援のキーストア実装を提供します。その場合、鍵は Trusted Execution Environment または Secure Element で生成および使用され、オペレーティングシステムは直接アクセスできません。これは暗号鍵自体がルート化デバイスからでも容易には取得できないことを意味します。鍵の KeyInfo
の一部である isInsideSecureHardware()
に基づいて、鍵がセキュアなハードウェア内にあるかどうかを確認できます。秘密鍵は通常セキュアなハードウェア内に正しく格納されていますが、共通鍵、hmac 鍵は KeyInfo に従ってセキュアに格納されないデバイスがかなりあります。
ソフトウェアのみの実装では、鍵はユーザーごとの暗号マスターキー [16] で暗号化されます。その場合、攻撃者はルート化デバイスのフォルダ /data/misc/keystore/
のすべての鍵にアクセスできます。マスターキーはユーザー自身のロック画面 PIN やパスワードを使用して生成されるため、Android KeyStore はデバイスがロックされているときには利用できません [9] 。
古い Android バージョンには KeyStore はありませんが、JCA (Java Cryptography Architecture) の KeyStore インタフェースを備えています。このインタフェースを実装するさまざまな KeyStore を使用して、キーストア実装に格納される鍵の機密性と完全性の保護を提供できます。その実装はすべてファイルシステムに格納されたファイルであるという事実に依存するため、パスワードによりその内容を保護しています。このため、BounceyCastle KeyStore (BKS) の使用をお勧めします。
KeyStore.getInstance("BKS", "BC");
を使用してそれを作成できます。"BKS" はキーストア名 (BounceycastleKeyStore) であり、"BC" はプロバイダ (BounceyCastle) です。代わりに SpongeyCastle をラッパーとして使用し、キーストアを初期化することもできます: KeyStore.getInstance("BKS", "SC");
。
すべての KeyStore がキーストアファイルに格納された鍵の適切な保護を提供するわけではないことに気をつけます。
既に示したように、Android 内に情報を格納するにはいくつかの方法があります。従って、いくつかのチェックをソースコードに適用して、Android アプリ内で使用されるストレージメカニズムを特定し、機密データが非セキュアにしょりされていないかどうかを確認します。
- 外部ストレージの読み書きのためのパーミッションについて
AndroidManifest.xml
をチェックする。uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
など。 - データの格納に使用される関数および API 呼び出しについてソースコードをチェックする。
- 任意の IDE やテキストエディタで Java ファイルを開くか、コマンドラインで grep を使用して検索する。
- ファイルパーミッション
MODE_WORLD_READABLE
やMODE_WORLD_WRITABLE
。IPC ファイルはアプリのプライベートデータディレクトリに格納されていても、任意のアプリがそのファイルを読み書きする必要がなければ、MODE_WORLD_READABLE
やMODE_WORLD_WRITABLE
のパーミッションで作成するべきではありません。
- クラスおよび関数
SharedPreferences
クラス (キーバリューペアのストレージ)FileOutPutStream
クラス (内部または外部ストレージの使用)getExternal*
関数 (外部ストレージの使用)getWritableDatabase
関数 (書き込み用の SQLiteDatabase を戻す)getReadableDatabase
関数 (読み取り用の SQLiteDatabase を戻す)getCacheDir
およびgetExternalCacheDirs
関数 (キャッシュファイルの使用)
- ファイルパーミッション
- 任意の IDE やテキストエディタで Java ファイルを開くか、コマンドラインで grep を使用して検索する。
暗号化操作は SDK により提供される実証済み機能に依存すべきです。以下では、ソースコードでチェックする必要があるさまざまな「バッドプラクティス」について説明します。
- 単純なビット操作が使用されているかどうかを確認します。XOR やビットフリップなどでローカルに格納される資格情報や秘密鍵などの機密情報を「暗号化」します。これはデータを容易に復元できるため避けるべきです。
- Android KeyStore [8] などの Android オンボード機能を利用せずに鍵を生成または使用されているかどうかを確認します。
- 鍵が開示されているかどうかを確認します。
ハードコードされた暗号鍵や誰にでも読み取り可能な暗号鍵を使用すると、暗号化されたデータを復元される可能性が大幅に高まります。攻撃者がそれを取得すると、機密データを復号する作業は簡単になり、機密性を保護するという当初の考えは失敗します。
対称暗号を使用する場合、鍵はデバイス内に格納する必要があり、攻撃者がそれを特定するのは時間と労力だけの問題です。
次のシナリオを考えます。あるアプリケーションは暗号化されたデータベースを読み書きしますが、復号化はハードコードされた鍵に基づいて行われます。
this.db = localUserSecretStore.getWritableDatabase("SuperPassword123");
鍵はすべてのアプリインストールで同じであるため、取得することは簡単です。機密データを暗号化する利点は無くなり、このような方法で暗号化を使用することで得られるものは実際にはありません。同様に、ハードコードされた API 鍵や秘密鍵およびその他の価値のある部品を探します。エンコードや暗号化された鍵はさらなる試みであり、王冠の宝石を手に入れることは困難ですが不可能ではありません。
このコードを考えて見ましょう。
//A more complicated effort to store the XOR'ed halves of a key (instead of the key itself)
private static final String[] myCompositeKey = new String[]{
"oNQavjbaNNSgEqoCkT9Em4imeQQ=","3o8eFOX4ri/F8fgHgiy/BS47"
};
このケースでの元の鍵を解読するためのアルゴリズムは以下のようになります [1] 。
public void useXorStringHiding(String myHiddenMessage) {
byte[] xorParts0 = Base64.decode(myCompositeKey[0],0);
byte[] xorParts1 = Base64.decode(myCompositeKey[1],0);
byte[] xorKey = new byte[xorParts0.length];
for(int i = 0; i < xorParts1.length; i++){
xorKey[i] = (byte) (xorParts0[i] ^ xorParts1[i]);
}
HidingUtil.doHiding(myHiddenMessage.getBytes(), xorKey, false);
}
秘密が通常隠されている一般的な場所を確認します。
- resources (通常は res/values/strings.xml にあります)
例:
<resources>
<string name="app_name">SuperApp</string>
<string name="hello_world">Hello world!</string>
<string name="action_settings">Settings</string>
<string name="secret_key">My_S3cr3t_K3Y</string>
</resources>
- build configs, local.properties や gradle.properties などにあります
例:
buildTypes {
debug {
minifyEnabled true
buildConfigField "String", "hiddenPassword", "\"${hiddenPassword}\""
}
}
- shared preferences, 通常は /data/data/package_name/shared_prefs にあります
ソースコードを調べる際は、Android により提供されるネイティブメカニズムが識別された機密情報に適用されているかどうかを解析する必要があります。機密情報は平文で格納してはいけません。暗号化する必要があります。機密情報をデバイス自体に格納する必要がある場合には、いくつかの API 呼び出しを利用できます。KeyChain [10] や Android Keystore [8] を使用して Android デバイス上のデータを保護します。従って、以下のコントロールを使用する必要があります。
- クラス
KeyPairGenerator
を探して、アプリ内で鍵ペアが作成されているかどうかを確認します。 - アプリケーションが Android KeyStore や Cipher メカニズムを使用して、暗号化された情報をデバイス上にセキュアに格納していることを確認します。パターン
import java.security.KeyStore
,import javax.crypto.Cipher
,import java.security.SecureRandom
および対応する使用法を探します。 store(OutputStream stream, char[] password)
関数を使用して、指定されたパスワードでディスクに KeyStore を格納できます。提供されるパスワードはハードコードされておらず、ユーザー入力により定義され、ユーザーだけが知っているものであることを確認します。パターン.store(
を探します。
アプリをインストールして、それが意図したとおりに使用します。すべての機能を少なくとも一回は実行します。データが生成されるのは、ユーザーが入力したとき、エンドポイントにより送信されたとき、またはインストール時にアプリ内にすでに同梱されています。それから以下の項目を確認します。
/data/data/<package_name>/
にインストールされたモバイルアプリケーションに同梱されているファイルを確認し、製品リリースにはないはずの開発、バックアップ、または単に古いファイルを特定します。- SQLite データベースが利用可能であるかどうか、およびそれらに機密情報 (ユーザー名、パスワード、鍵など) が含まれているかどうかを確認します。SQLite データベースは
/data/data/<package_name>/databases
に格納されます。 - 機密情報について、アプリの shared_prefs ディレクトリに XML ファイルとして格納されている Shared Preferences を確認します。
/data/data/<package_nam>/shared_prefs
にあります。 /data/data/<package_name>
にあるファイルのファイルシステム権限を確認します。アプリをインストールした際に作成されるユーザーおよびグループ (u0_a82 など) のみがユーザー権限の読み取り、書き込み、実行 (rwx) を持つ必要があります。他の人はファイルへの権限を持たないはずですが、ディレクトリに対して実行可能フラグを持つ可能性があります。
データを保存する際の約束事は次のように非常に簡単に要約できます。パブリックデータは誰でも利用可能とすべきですが、機密およびプライベートのデータは保護する必要がありますし、そもそもデバイスに格納しないほうがさらに良いです。
機密情報 (資格情報、鍵、PII など) はデバイス上でローカルに必要である場合、セキュアにデータを格納するために使用されるベストプラクティスは Android により提供されます。そうでなければ、車輪を再発明するか、デバイスに暗号化されていないデータをが残ります。
以下は、証明書、鍵、機密データのセキュアストレージに一般的に使用されるベストプラクティスのリストです。
- 自己実装された暗号化または復号化機能は避ける必要があります。代わりに Cipher [11], SecureRandom [12], KeyGenerator [13] などの Android 実装を使用します。
- ユーザー名とパスワードはデバイスに格納すべきではありません。代わりに、ユーザーが入力したユーザー名とパスワードを使用して初期認証を行い、短期間でサービス固有の認証トークン (セッショントークン) を使用します。可能であれば、AccountManager [14] クラスを使用してクラウドベースサービスを呼び出し、デバイスにパスワードを格納しません。
- ファイルに対して
MODE_WORLD_WRITEABLE
またはMODE_WORLD_READABLE
の使用は通常避けるべきです。他のアプリケーションとデータを共有する必要がある場合には、コンテンツプロバイダを検討すべきです。コンテンツプロバイダは他のアプリに読み取りおよび書き込みパーミッションを提供し、ケースバイケースベースで動的にパーミッションを割り当てることができます。 - データを保護できない Shared Preferences またはその他のメカニズムの使用は機密情報を格納するには避けるべきです。SharedPreferences はセキュアではなく、デフォルトで暗号化されません。Secure-preferences [15] は Shared Preferences 内に格納される値を暗号化するために使用できますが、セキュアにデータを格納するための最初の選択肢は Android Keystore です。
- 機密データ用に外部ストレージを使用してはいけません。デフォルトでは、内部ストレージに保存されるファイルはあなたのアプリケーションのプライベートであり、他のアプリケーションはそれらにアクセスできません (そのユーザーもアクセスできません) 。ユーザーがアプリケーションをアンインストールすると、これらのファイルも削除されます。
- 機密データを更に保護するために、アプリケーションが直接アクセスできない鍵を使用してローカルファイルを暗号化することを選択できます。例えば、鍵を KeyStore に配置し、デバイスに格納されていないユーザーパスワードで保護できます。これはユーザーがパスワードを入力することを監視するルートの侵害からデータを保護するものではありませんが、ファイルシステムの暗号化なしに紛失したデバイスの保護を提供できます。
- 機密情報を使用し終えた変数に null を設定します。
- 機密データに immutable オブジェクトを使用することで、変更できなくなります。
- セキュリティの多層対策として、コードの難読化もアプリに適用し、攻撃者のリバースエンジニアリングを困難にします。
- M1 - 不適切なプラットフォームの利用 - https://www.owasp.org/index.php/Mobile_Top_10_2016-M1-Improper_Platform_Usage
- M2 - 安全でないデータストレージ - https://www.owasp.org/index.php/Mobile_Top_10_2016-M2-Insecure_Data_Storage
- V2.1: "ユーザー資格情報や暗号化鍵などの機密データを格納するために、システムの資格情報保存機能が適切に使用されている。"
- CWE-311 - Missing Encryption of Sensitive Data
- CWE-312 - Cleartext Storage of Sensitive Information
- CWE-522 - Insufficiently Protected Credentials
- CWE-922 - Insecure Storage of Sensitive Information
[1] Security Tips for Storing Data - http://developer.android.com/training/articles/security-tips.html#StoringData [2] SharedPreferences - http://developer.android.com/reference/android/content/SharedPreferences.html [3] SQLite Journal files - https://www.sqlite.org/tempfiles.html [4] SQLite Lock Files - https://www.sqlite.org/lockingv3.html [5] SQLCipher - https://www.zetetic.net/sqlcipher/sqlcipher-for-android/ [6] Using Internal Storage - http://developer.android.com/guide/topics/data/data-storage.html#filesInternal [7] Using External Storage - https://developer.android.com/guide/topics/data/data-storage.html#filesExternal [8] Android KeyStore System - http://developer.android.com/training/articles/keystore.html [9] Use Android Keystore - http://www.androidauthority.com/use-android-keystore-store-passwords-sensitive-information-623779/ [10] Android KeyChain - http://developer.android.com/reference/android/security/KeyChain.html [11] Cipher - https://developer.android.com/reference/javax/crypto/Cipher.html [12] SecureRandom - https://developer.android.com/reference/java/security/SecureRandom.html [13]KeyGenerator - https://developer.android.com/reference/javax/crypto/KeyGenerator.html [14] AccountManager - https://developer.android.com/reference/android/accounts/AccountManager.html [15] Secure Preferences - https://github.com/scottyab/secure-preferences [16] Nikolay Elenvok - Credential storage enhancements in Android 4.3 - https://nelenkov.blogspot.sg/2013/08/credential-storage-enhancements-android-43.html
- Enjarify - https://github.com/google/enjarify
- JADX - https://github.com/skylot/jadx
- Dex2jar - https://github.com/pxb1988/dex2jar
- Lint - http://developer.android.com/tools/help/lint.html
- Sqlite3 - http://www.sqlite.org/cli.html
モバイルデバイス上にログファイルを作成する正当な理由は数多くあります。例えば、クラッシュやエラーを追跡したり、単に使用統計を記録したりするなどです。ログファイルはオフライン時にはローカルに格納され、再びオンラインになるとエンドポイントに送信されます。しかし、ユーザー名やセッション ID などの機密情報をログ出力すると、攻撃者や悪意のあるアプリケーションにデータを公開され、データの機密性を失う可能性があります。 ログファイルはさまざまな方法で作成できます。以下のリストは Android で利用可能なメカニズムを示しています。
- Log クラス [1]
- Logger クラス
- System.out/System.err.print
ソースコードにいくつかのチェックを適用して、Android アプリ内で使用されているログ出力メカニズムを特定する必要があります。これは機密データが非セキュアに処理されているかどうかを特定します。 ソースコードはAndroid アプリ内で使用されているログ出力メカニズムをチェックする必要があります。以下のものを検索します。
- 関数及びクラス
android.util.Log
Log.d
|Log.e
|Log.i
|Log.v
|Log.w
|Log.wtf
Logger
System.out.print
|System.err.print
- 非標準のログメカニズムを特定するためのキーワードとシステム出力
- logfile
- logging
- logs
モバイルアプリを広範囲に使用し、すべての機能が少なくとも一度は起動されるようにします。その後、アプリケーションのデータディレクトリを特定し、ログファイルを探します (/data/data/package_name
) 。アプリケーションログを確認してログデータが生成されているかどうかをチェックします。一部のモバイルアプリケーションはデータディレクトリに独自のログを作成および格納します。
多くのアプリケーション開発者は適切なログ出力クラスの代わりに System.out.println()
や printStackTrace()
をいまだに使用しています。したがって、テストアプローチではアプリケーションの起動、実行、終了時にアプリケーションにより生成されるすべての出力をカバーする必要もあります。System.out.println()
や printStackTrace()
を使用して直接出力されるデータを確認するために、ツール LogCat
[2] を使用してアプリの出力をチェックできます。LogCat を実行するには二つの異なるアプローチが利用可能です。
- LogCat はすでに Dalvik Debug Monitor Server (DDMS) の一部であり、Android Studio に組み込まれています。アプリがデバッグモードで実行されている場合、ログ出力は Android Monitor の LogCat タブに表示されます。LogCat にパターンを定義して、アプリのログ出力をフィルタできます。
- adb を使用して LogCat を実行して、ログ出力を永続的に格納できます。
$ adb logcat > logcat.log
一元的なログ出力クラスとメカニズムが使用され、プロダクションリリースからログ出力ステートメントが削除されていることを確認します。ログは他のアプリケーションにより傍受や読み取りが可能です。Android Studio に既に含まれている ProGuard
などのツールを使用して、プロダクションリリースを準備する際にコード内のログ出力部分を取り除くことができます。例えば、クラス android.util.Log
で実行されたログ出力呼び出しを削除するには、ProGuard の proguard-project.txt 設定ファイルに以下のオプションを追加するだけです。
-assumenosideeffects class android.util.Log
{
public static boolean isLoggable(java.lang.String, int);
public static int v(...);
public static int i(...);
public static int w(...);
public static int d(...);
public static int e(...);
public static int wtf(...);
}
- M1 - 不適切なプラットフォームの利用 - https://www.owasp.org/index.php/Mobile_Top_10_2016-M1-Improper_Platform_Usage
- M2 - 安全でないデータストレージ - https://www.owasp.org/index.php/Mobile_Top_10_2016-M2-Insecure_Data_Storage
- V2.2: "機密データがアプリケーションログに書き込まれていない。"
- CWE-117: Improper Output Neutralization for Logs
- CWE-532: Information Exposure Through Log Files
- CWE-534: Information Exposure Through Debug Log Files
- [1] Overview of Class Log - http://developer.android.com/reference/android/util/Log.html
- [2] Debugging Logs with LogCat - http://developer.android.com/tools/debugging/debugging-log.html
- ProGuard - http://proguard.sourceforge.net/
- LogCat - http://developer.android.com/tools/help/logcat.html
アプリに組み込むことでさまざまな機能を実装できるさまざまなサードパーティサービスが利用できます。これらの機能はトラッカーサービスやアプリ内のユーザー行動の監視、販売バナー広告、より良いユーザーエクスペリエンスの作成などさまざまです。これらのサービスとのやり取りは、機能の独自実装や車輪の再発明といった複雑さと必要性を抽象化します。
欠点は、サードパーティライブラリを介してどのようなコードが実行されているかを開発者が詳細に把握せず、したがって可視性をあきらめることです。そのため、必要以上の情報がサービスに送信されていないこと、および機密情報が開示されていないことを確認する必要があります。
サードパーティサービスは主に二つの方法で実装されます。
- スタンドアローンのライブラリを使用する。Android プロジェクトの Jar など。APK に組み込まれる。
- フル SDK を使用する。
一部のサードパーティライブラリは IDE 内のウィザードを使用してアプリに自動的に統合できます。IDE ウィザードを使用してライブラリをインストールする場合には、AndroidManifest.xml
に設定されたパーミッションを確認する必要があります。特に、SMS (READ_SMS
), 連絡先 (READ_CONTACTS
), 位置情報 (ACCESS_FINE_LOCATION
) にアクセスするためのパーミッションは、ライブラリが真に最小限で機能するために本当に必要であるかどうか、説明を求めるべきです。「アプリパーミッションのテスト」も参照します。開発者と話す際には、IDE を使用してライブラリをインストールする前と後でプロジェクトソースコードの相違点を確認し、コードベースにどのような変更が加えられたかを確認する必要があります。
ライブラリまたは SDK を手動で追加する場合にも同じことが適用されます。サードパーティライブラリや SDK により提供される API 呼び出しや関数についてソースコードをチェックする必要があります。適用されるコード変更をレビューし、ライブラリや SDK の利用可能なセキュリティベストプラクティスが適用および使用されているかどうかをチェックする必要があります。
プロジェクトにロードされたライブラリをレビューし、開発者と共にそれらが必要であるかと確認します。また、古くなり既知の脆弱性を含むかどうかも確認します。
外部サービスに対するすべてのリクエストについて、機密情報が埋め込まれているかどうかを解析する必要があります。動的解析は中間者 (MITM) 攻撃を開始することにより実行できます。Burp Proxy [1] や OWASP ZAP を使用し、クライアントとサーバーとの間で交換されるトラフィックを傍受します。傍受プロキシにトラフィックをルーティングできるようになると、アプリからサーバーへおよびその反対のトラフィックを盗聴することができます。アプリを使用する場合には、メイン機能がホストされているサーバーに直接接続されていないすべてのリクエストについて、機密情報がサードパーティに送信されているかどうかを確認する必要があります。これには例えば、トラッカーや広告サービスでの PII (個人識別情報) があります。
サードパーティサービスに送信されるすべてのデータは匿名化されるべきです。そのため PII データは利用できません。また、ユーザーアカウントやセッションにマップできるアプリケーションの ID などの他のすべてのデータをサードパーティに送信すべきではありません。
AndroidManifest.xml
には正しく動作するために必要なパーミッションだけを含む必要があります。
- M1 - 不適切なプラットフォームの利用 - https://www.owasp.org/index.php/Mobile_Top_10_2016-M1-Improper_Platform_Usage
- M2 - 安全でないデータストレージ - https://www.owasp.org/index.php/Mobile_Top_10_2016-M2-Insecure_Data_Storage
- V2.3: "機密データはアーキテクチャに必要な部分でない限りサードパーティと共有されていない。"
- CWE-359 - Exposure of Private Information ('Privacy Violation')
[1] Configure Burp with Android - https://support.portswigger.net/customer/portal/articles/1841101-configuring-an-android-device-to-work-with-burp [2] Bulletproof Android, Godfrey Nolan - Chapter 7, Third-Party Library Integration
- Burp Suite Professional - https://portswigger.net/burp/
- OWASP ZAP - https://www.owasp.org/index.php/OWASP_Zed_Attack_Proxy_Project
入力フィールドにデータを入力すると、ソフトウェアキーボードはユーザーがキー入力したいデータを自動的に提示します。この機能はメッセージングアプリで非常に役に立ち、テキストメッセージを非常に効率的に書くことができます。クレジットカードデータなどの機密情報を要求する入力フィールドでは、その入力フィールドが選択された際にキーボードキャッシュが既存の機密情報を開示する可能性があります。したがって、機密情報を要求する入力フィールドではこの機能を無効にする必要があります。
アクティビティのレイアウト定義では、XML 属性を持つ TextView を定義できます。XML 属性 android:inputType
に定数 textNoSuggestions
を設定すると、その入力フィールドを選択した際にキーボードキャッシュは表示されません。キーボードだけが表示され、ユーザーは手動ですべてを入力する必要があり、何も提示されません。
<EditText
android:id="@+id/KeyBoardCache"
android:inputType="textNoSuggestions"/>
アプリを起動し、機密データを要求する入力フィールドをクリックします。文字列が提示される場合、この入力フィールドでキーボードキャッシュは無効ではありません。
機密情報を要求するすべての入力フィールドでは、以下の XML 属性を実装し、キーボードの提示を無効にする必要があります [1] 。
android:inputType="textNoSuggestions"
- M1 - 不適切なプラットフォームの利用 - https://www.owasp.org/index.php/Mobile_Top_10_2016-M1-Improper_Platform_Usage
- M2 - 安全でないデータストレージ - https://www.owasp.org/index.php/Mobile_Top_10_2016-M2-Insecure_Data_Storage
- V2.4: "機密データを処理するテキスト入力では、キーボードキャッシュが無効にされている。"
- CWE-524 - Information Exposure Through Caching
[1] No suggestions for text - https://developer.android.com/reference/android/text/InputType.html#TYPE_TEXT_FLAG_NO_SUGGESTIONS
入力フィールドにデータを入力する際に、clipboard [1] を使用してデータをコピーできます。クリップボードはシステム全体でアクセスできるため、アプリ間で共有されます。この機能を悪用して、悪意のあるアプリが機密データを取得できる可能性があります。
機密情報を要求する入力フィールドを特定し、クリップボードの表示を抑制するための対策が適切にされているかどうかを調査する必要があります。適用可能なコードスニペットについては改善方法のセクションを参照ください。
アプリを起動し、機密データを要求する入力フィールドをクリックします。データをコピー、ペーストするためのメニューを取得可能である場合、この入力フィールドに対して機能は無効ではありません。
クリップボードに格納されたデータを抽出するには、Drozer モジュール post.capture.clipboard
を使用できます。
dz> run post.capture.clipboard
[*] Clipboard value: ClipData.Item { T:Secretmessage }
一般的なベストプラクティスは入力フィールドのさまざまな機能を上書きして、クリップボードを明確に無効にすることです。
EditText etxt = (EditText) findViewById(R.id.editText1);
etxt.setCustomSelectionActionModeCallback(new Callback() {
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
0
public void onDestroyActionMode(ActionMode mode) {
}
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
return false;
}
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
return false;
}
});
また、入力フィールドに対して longclickable
を無効にする必要があります。
android:longClickable="false"
- M1 - 不適切なプラットフォームの利用
- M2 - 安全でないデータストレージ
- V2.5: "機密データを含む可能性があるテキストフィールドでは、クリップボードが無効化されている。"
- CWE-200 - Information Exposure
[1] Copy and Paste in Android - https://developer.android.com/guide/topics/text/copy-paste.html
モバイルアプリケーションの開発では、共有ファイルやネットワークソケットの使用など IPC に関する従来の技術が適用される可能性があります。モバイルアプリケーションプラットフォームは IPC について独自のシステム機能を実装しているため、旧来の技術よりもはるかに成熟しているこれらのメカニズムを適用すべきです。セキュリティを考慮せずに IPC メカニズムを使用すると、アプリケーションは機密データの漏洩や開示を引き起こす可能性があります。
以下は機密データを開示する可能性のある Android IPC メカニズムのリストです。
- Binders [1]
- Services [2]
- Bound Services [9]
- AIDL [10]
- Intents [3]
- Content Providers [4]
最初のステップは AndroidManifest.xml
を調べて、アプリにより開示されている IPC メカニズムを検出及び特定することです。以下のような要素を特定したいと思うでしょう。
<intent-filter>
[5]<service>
[6]<provider>
[7]<receiver>
[8]
<intent-filter>
要素を除いて、前述の要素に以下の属性が含まれているかどうか確認します。
android:exported
android:permission
IPC メカニズムの一覧を特定したら、ソースコードをレビューして、使用時に機密データが漏洩しているかどうかを検出します。例えば、ContentProviders を使用してデータベース情報にアクセスできます。サービスがプローブされてデータを返すかどうかを調べます。また、BroadcastReceiver と Broadcast インテントはプローブや盗聴された場合に機密情報を漏洩する可能性があります。
脆弱な ContentProvider
脆弱な ContentProvider の例: (and SQL injection -- TODO [Refer to any input validation test in the project] --
<provider android:name=".CredentialProvider"
android:authorities="com.owaspomtg.vulnapp.provider.CredentialProvider"
android:exported="true">
</provider>
上述の AndroidManifest.xml
にあるように、アプリケーションはコンテンツプロバイダをエクスポートしています。
CredentialProvider.java
ファイルでは query
関数を検査して、機密情報を漏洩しているかどうかを検出する必要があります。
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
// the TABLE_NAME to query on
queryBuilder.setTables(TABLE_NAME);
switch (uriMatcher.match(uri)) {
// maps all database column names
case CREDENTIALS:
queryBuilder.setProjectionMap(CredMap);
break;
case CREDENTIALS_ID:
queryBuilder.appendWhere( ID + "=" + uri.getLastPathSegment());
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
if (sortOrder == null || sortOrder == ""){
sortOrder = USERNAME;
}
Cursor cursor = queryBuilder.query(database, projection, selection,
selectionArgs, null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
content://com.owaspomtg.vulnapp.provider.CredentialProvider/CREDENTIALS
にアクセスすると、query ステートメントはすべての資格情報を返します。
- 脆弱な Broadcast
sendBroadcast
,sendOrderedBroadcast
,sendStickyBroadcast
などの文字列でソースコードを検索し、アプリケーションが機密データを送信していない確認します。
脆弱なブロードキャストの例は以下のとおりです。
private void vulnerableBroadcastFunction() {
// ...
Intent VulnIntent = new Intent();
VulnIntent.setAction("com.owasp.omtg.receiveInfo");
VulnIntent.putExtra("ApplicationSession", "SESSIONID=A4EBFB8366004B3369044EE985617DF9");
VulnIntent.putExtra("Username", "litnsarf_omtg");
VulnIntent.putExtra("Group", "admin");
}
this.sendBroadcast(VulnIntent);
アプリケーションのコンテンツプロバイダの動的解析を開始するには、まずアタックサーフェイスを列挙する必要があります。これを実現するためには Drozer モジュール app.provider.info
を使用します。
dz> run app.provider.info -a com.mwr.example.sieve
Package: com.mwr.example.sieve
Authority: com.mwr.example.sieve.DBContentProvider
Read Permission: null
Write Permission: null
Content Provider: com.mwr.example.sieve.DBContentProvider
Multiprocess Allowed: True
Grant Uri Permissions: False
Path Permissions:
Path: /Keys
Type: PATTERN_LITERAL
Read Permission: com.mwr.example.sieve.READ_KEYS
Write Permission: com.mwr.example.sieve.WRITE_KEYS
Authority: com.mwr.example.sieve.FileBackupProvider
Read Permission: null
Write Permission: null
Content Provider: com.mwr.example.sieve.FileBackupProvider
Multiprocess Allowed: True
Grant Uri Permissions: False
この例では、二つのコンテンツプロバイダがエクスポートされ、DBContentProvider
の /Keys
を除いて、それらとやり取りするためのパーミッションを必要としません。この情報を使用すると、コンテンツ URI の一部を再構築して DBContentProvider
にアクセスできます。content://
ではじめる必要があることが知られているためです。しかし、完全なコンテンツプロバイダ URI は現在知られていません。
アプリケーション内のコンテンツプロバイダ URI を特定するには、Drozer の scanner.provider.finduris
モジュールを使用する必要があります。これはさまざまな技法を利用してパスを推測し、アクセス可能なコンテンツ URI の一覧を決定します。
dz> run scanner.provider.finduris -a com.mwr.example.sieve
Scanning com.mwr.example.sieve...
Unable to Query content://com.mwr.
example.sieve.DBContentProvider/
...
Unable to Query content://com.mwr.example.sieve.DBContentProvider/Keys
Accessible content URIs:
content://com.mwr.example.sieve.DBContentProvider/Keys/
content://com.mwr.example.sieve.DBContentProvider/Passwords
content://com.mwr.example.sieve.DBContentProvider/Passwords/
アクセス可能なコンテンツプロバイダの一覧を入手しました。次のステップはそれぞれのプロバイダからデータの抽出を試みることです。実現には app.provider.query
モジュールを使用します。
dz> run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Passwords/ --vertical
_id: 1
service: Email
username: incognitoguy50
password: PSFjqXIMVa5NJFudgDuuLVgJYFD+8w== (Base64
-
encoded)
email: [email protected]
データのクエリに加えて、Drozer では脆弱なコンテンツプロバイダからレコードの更新、挿入、削除にも使用できます。
- レコードの挿入
dz> run app.provider.insert content://com.vulnerable.im/messages
--string date 1331763850325
--string type 0
--integer _id 7
- レコードの更新
dz> run app.provider.update content://settings/secure
--selection "name=?"
--selection-args assisted_gps_enabled
--integer value 0
- レコードの削除
dz> run app.provider.delete content://settings/secure
--selection "name=?"
--selection-args my_setting
Android プラットフォームはユーザーデータの格納に SQLite データベースの使用を勧めます。これらのデータベースは SQL を使用するため、SQL インジェクションに脆弱となる可能性があります。Drozer モジュール app.provider.query
を使用して、SQL インジェクションのテストできます。コンテンツプロバイダに渡される projection と selection フィールドを操作します。
dz> run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Passwords/ --projection "'"
unrecognized token: "' FROM Passwords" (code 1): , while compiling: SELECT ' FROM Passwords
dz> run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Passwords/ --selection "'"
unrecognized token: "')" (code 1): , while compiling: SELECT * FROM Passwords WHERE (')
SQL インジェクションの脆弱性がある場合、アプリケーションは詳細なエラーメッセージを返します。Android の SQL インジェクションを悪用して、脆弱なコンテンツプロバイダのデータを変更または照会できます。以下の例では、Drozer モジュール app.provider.query
を使用して、データベースのすべてのテーブルをリストします。
dz> run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Passwords/ --projection "*
FROM SQLITE_MASTER WHERE type='table';--"
| type | name | tbl_name | rootpage | sql |
| table | android_metadata | android_metadata | 3 | CREATE TABLE ... |
| table | Passwords | Passwords | 4 | CREATE TABLE ... |
| table | Key | Key | 5 | CREATE TABLE ... |
SQL インジェクションを悪用して、保護されていないテーブルからデータを取得することもできます。
dz> run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Passwords/ --projection "* FROM Key;--"
| Password | pin |
| thisismypassword | 9876 |
これらのステップを自動化するには scanner.provider.injection
モジュールを使用できます。アプリ内の脆弱なコンテンツプロバイダを自動的に見つけます。
dz> run scanner.provider.injection -a com.mwr.example.sieve
Scanning com.mwr.example.sieve...
Injection in Projection:
content://com.mwr.example.sieve.DBContentProvider/Keys/
content://com.mwr.example.sieve.DBContentProvider/Passwords
content://com.mwr.example.sieve.DBContentProvider/Passwords/
Injection in Selection:
content://com.mwr.example.sieve.DBContentProvider/Keys/
content://com.mwr.example.sieve.DBContentProvider/Passwords
content://com.mwr.example.sieve.DBContentProvider/Passwords/
コンテンツプロバイダは基礎となるファイルシステムへのアクセスを提供できます。これによりアプリはファイルを共有できます。Android サンドボックスはそれを抑制してないでしょう。Drozer モジュール app.provider.read
および app.provider.download
を使用して、エクスポートされたファイルベースのコンテンツプロバイダからファイルを読み取りまたはダウンロードできます。これらのコンテンツプロバイダはディレクトリトラバーサルの脆弱性を受けやすく、ターゲットアプリケーションのサンドボックス内の保護されていないファイルを読み取ることが可能となります。
dz> run app.provider.download content://com.vulnerable.app.FileProvider/../../../../../../../../data/data/com.vulnerable.app/database.db /home/user/database.db
Written 24488 bytes
ディレクトリトラバーサルの影響を受けやすいコンテンツプロバイダを見つけるプロセスを自動化するには、scanner.provider.traversal
モジュールを使用する必要があります。
dz> run scanner.provider.traversal -a com.mwr.example.sieve
Scanning com.mwr.example.sieve...
Vulnerable Providers:
content://com.mwr.example.sieve.FileBackupProvider/
content://com.mwr.example.sieve.FileBackupProvider
注釈:adb
を使用して、デバイスのコンテンツプロバイダを照会することもできます。
$ adb shell content query --uri content://com.owaspomtg.vulnapp.provider.CredentialProvider/credentials
Row: 0 id=1, username=admin, password=StrongPwd
Row: 1 id=2, username=test, password=test
...
インテントを盗聴するには、デバイス (実際のデバイスまたはエミュレートされたデバイス) にアプリケーションをインストールおよび実行し、Drozer や Intent Sniffer などのツールを使用してインテントやブロードキャストメッセージをキャプチャします。
activity, broadcast, service に対して呼出元のパーミッションはコードまたはマニフェストで確認できます。
完全に要求されていない場合には、IPC が AndroidManifest.xml
ファイルに android:exported="true"
の値を持たないことを確認します。そうしないと、Android 上の他のすべてのアプリが通信および呼び出すことができてしまいます。
intent が同じアプリケーションでのみブロードキャストおよび受信される場合には、LocalBroadcastManager
を使用できます。他のアプリがブロードキャストメッセージを受信できないように設計されています。これにより機密情報が漏洩するリスクを低減します。
LocalBroadcastManager.sendBroadcast().BroadcastReceivers
は android:permission
属性を使用する必要があります。そうしないと、他のアプリケーションがそれらを呼び出すことができてしまいます。Context.sendBroadcast(intent, receiverPermission);
を使用して、レシーバーが必要とするバーミッションを指定し、ブロードキャストを読むことができます [11] 。
明示的なアプリケーションパッケージ名を設定して、このインテントが解決するコンポーネントを制限できます。デフォルト値の null のままにすると、すべてのアプリケーションのすべてのコンポーネントを考慮します。null ではない場合、インテントは指定されたアプリケーションパッケージ内のコンポーネントにのみマッチします。
IPC が他のアプリケーションからアクセスできるようにするには、<permission>
要素を使用してセキュリティポリシーを適用し、適切な android:protectionLevel
を設定します。サービスの宣言で android:permission
を使用する場合、他のアプリケーションはマニフェストに対応する <uses-permission>
を宣言する必要があり、それによりサービスの開始、停止、またはバインドできるようになります。
- M1 - 不適切なプラットフォームの利用
- M2 - 安全でないデータストレージ
- V2.6: "機密データがIPCメカニズムを介して公開されていない。"
- CWE-634 - Weaknesses that Affect System Processes
[1] IPCBinder - https://developer.android.com/reference/android/os/Binder.html [2] IPCServices - https://developer.android.com/guide/components/services.html [3] IPCIntent - https://developer.android.com/reference/android/content/Intent.html [4] IPCContentProviders - https://developer.android.com/reference/android/content/ContentProvider.html [5] IntentFilterElement - https://developer.android.com/guide/topics/manifest/intent-filter-element.html [6] ServiceElement - https://developer.android.com/guide/topics/manifest/service-element.html [7] ProviderElement - https://developer.android.com/guide/topics/manifest/provider-element.html [8] ReceiverElement - https://developer.android.com/guide/topics/manifest/receiver-element.html [9] BoundServices - https://developer.android.com/guide/components/bound-services.html [10] AIDL - https://developer.android.com/guide/components/aidl.html [11] SendBroadcast - https://developer.android.com/reference/android/content/Context.html#sendBroadcast(android.content.Intent)
- Drozer - https://labs.mwrinfosecurity.com/tools/drozer/
- IntentSniffer - https://www.nccgroup.trust/us/about-us/resources/intent-sniffer/
多くのアプリでは、例えば、アカウントを登録したり、支払いを実行するために、ユーザーはさまざまな種類のデータをキー入力する必要があります。アプリが適切にマスクしない場合や平文でデータを表示する場合に、機密データが開示される可能性があります。
アプリのアクティビティ内の機密データをマスクすることは、漏洩防止やショルダハックなどの軽減のために実施する必要があります。
アプリケーションがユーザーによりキー入力される機密情報をマスクしているかどうかを検証するには、EditText の定義の以下の属性をチェックします。
android:inputType="textPassword"
アプリケーションが機密情報をユーザーインタフェースに漏洩しているかどうかを解析するには、アプリケーションを実行して、情報を表示しているか情報をキー入力するよう求めている、アプリの部分を特定します。
例えば、テキストフィールドの文字をアスタリスクに置き換えることなどにより、情報がマスクされている場合、アプリはユーザーインタフェースにデータを漏洩していません。
パスワードや PIN の漏洩を防ぐには、機密情報をユーザーインタフェース内でマスクする必要があります。したがって、EditText フィールドでは 属性 android:inputType="textPassword"
を使用する必要があります。
- M4 - Unintended Data Leakage
- V2.7: "パスワードやピンなどの機密データは、ユーザーインタフェースを介して公開されていない。"
- CWE-200 - Information Exposure
他の現代的なモバイルオペレーティングシステムと同様に、Android は自動バックアップ機能を備えています。バックアップは通常、デバイスにインストールされているすべてのアプリのデータと設定のコピーが含まれます。明白な懸念として、アプリにより格納された機密のユーザーデータが意図せずこれらのデータバックアップに漏洩する可能性があるかどうかがあります。
多様なエコシステムを考えると、Android には構成される多くのバックアップオプションがあります。
-
一般的な Android には USB バックアップ機能が組み込まれています。フルデータバックアップ、または特定のアプリのデータディレクトリのバックアップを取得できます。USB デバッグを有効にして、
abd backup
コマンドを使用します。 -
Google は "Back Up My Data" 機能も提供しています。すべてのアプリデータを Google のサーバーにバックアップします。
-
アプリ開発者は複数の Backup API を利用できます。
-
キー・バリューバックアップ (Backup API または Android バックアップサービス) は選択したデータを Android バックアップサービスにアップロードします。
-
アプリの自動バックアップ: Android 6.0 (>= API レベル 23) では、Google は「アプリの自動バックアップ機能」を追加しました。この機能は最大25MBのアプリデータを Google Drive アカウントに自動的に同期します。
-
-
OEM は追加のオプションを追加することがあります。例えば、HTC デバイスには "HTC Backup" オプションがあり、これをアクティブにすると、クラウドへのデイリーバックアップが実行されます。
-- [TODO - recommended approach] --
すべてのアプリケーションデータをバックアップするために、Android は allowBackup
[1] という属性を提供しています。この属性は AndroidManifest.xml
ファイル内で設定されます。この属性の値が true に設定されている場合、デバイスはユーザーがアプリケーションをバックアップできます。Android Debug Bridge (ADB) - $ adb backup
を使用します。
注: デバイスが暗号化されている場合、バックアップファイルも暗号化されます。
以下のフラグについて AndroidManifest.xml
ファイルを確認します。
android:allowBackup="true"
その値が true に設定されている場合、アプリが何かしらの機密データを保存しているかどうかを調査し、テストケース「機密データのテスト (ローカルストレージ)」をチェックします。
キー・バリューまたは自動バックアップのどちらを使用しているかに関わらず、以下を特定する必要があります。
- どのファイルがクラウドに送信されるか (SharedPreferences など)
- ファイルに機密情報が含まれているかどうか
- 機密情報はクラウドに送信される前に暗号化により保護されているかどうか
自動バックアップ
自動バックアップはアプリケーションのマニフェストファイル内でブール属性 android:allowBackup
により設定されます。明示的に設定されていない場合、Android 6.0 (API レベル 23) 以上を対象とするアプリケーションではデフォルトで自動バックアップが有効になります [10] 。属性 android:fullBackupOnly
を使用して、バックアップエージェントを実装する際に自動バックアップを有効にすることもできますが、Android 6.0 以降でのみ利用できます。他の Android バージョンではキー・バリューバックアップが代わりに使用されます。
android:fullBackupOnly
自動バックアップにはアプリのほとんどすべてのファイルが含まれ、ユーザーの Google Drive アカウントに格納されます。アプリごとに 25MB に制限されています。最新のバックアップのみが格納され、以前のバックアップは削除されます。
キー・バリューバックアップ
キー・バリューバックアップを有効にするには、バックアップエージェントをマニフェストファイルで定義する必要があります。AndroidManifest.xml
内で以下の属性を探します。
android:backupAgent
キー・バリューバックアップを実装するには、以下のクラスのいずれかを拡張する必要があります。
- BackupAgent
- BackupAgentHelper
ソースコード内でこれらのクラスを探して、キー・バリューバックアップの実装を確認します。
アプリを使用する際に利用可能なすべての機能を実行した後、adb
を使用してバックアップの作成を試みます。成功した場合には、バックアップアーカイブで機密データを調べます。ターミナルを開き、以下のコマンドを実行します。
$ adb backup -apk -nosystem packageNameOfTheDesiredAPK
Back up my data オプションを選択して、デバイスからバックアップを承認します。バックアッププロセスが終了した後、現在の作業ディレクトリに .ab ファイルが作成されます。 以下のコマンドを実行し、.ab ファイルを .tar ファイルに変換します。
$ dd if=mybackup.ab bs=24 skip=1|openssl zlib -d > mybackup.tar
あるいは、この作業に Android Backup Extractor を使用します。このツールが機能するためには、JRE7 用または JRE8 用の Oracle JCE Unlimited Strength Jurisdiction ポリシーファイル [6] [7] をダウンロードし、JRE の lib/security フォルダに配置する必要があります。以下のコマンドを実行して、tar ファイルに変換します。
java -jar android-backup-extractor-20160710-bin/abe.jar unpack backup.ab
現在の作業ディレクトリに tar ファイルを抽出して、機密データの解析を実行します。
$ tar xvf mybackup.tar
アプリデータのバックアップを防止するには、AndroidManifest.xml
の android:allowBackup
属性に false を設定します。この属性を利用していない場合、allowBackup 設定はデフォルトで有効となります。したがって、無効にするためには明示的に設定する必要があります。
機密情報を平文でクラウドに送信してはいけません。以下のいずれかであるべきです。
- そもそも情報を格納することを避ける
- クラウドに送信する前に、装置上で情報を暗号化する
ファイルを Google Cloud で共有しない場合は、自動バックアップから除外することもできます [2] 。
- M1 - 不適切なプラットフォームの利用
- M2 - 安全でないデータストレージ
- V2.8: "機密データがモバイルオペレーティングシステムにより生成されるバックアップに含まれていない。"
- CWE-530 - Exposure of Backup File to an Unauthorized Control Sphere
[1] Documentation for the application tag - https://developer.android.com/guide/topics/manifest/application-element.html#allowbackup [2] IncludingFiles - https://developer.android.com/guide/topics/data/autobackup.html#IncludingFiles [3] Backing up App Data to the cloud - https://developer.android.com/guide/topics/data/backup.html [4] KeyValueBackup - https://developer.android.com/guide/topics/data/keyvaluebackup.html [5] BackupAgentHelper - https://developer.android.com/reference/android/app/backup/BackupAgentHelper.html [6] BackupAgent - https://developer.android.com/reference/android/app/backup/BackupAgent.html [7] Oracle JCE Unlimited Strength Jurisdiction Policy Files JRE7 - http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html [8] Oracle JCE Unlimited Strength Jurisdiction Policy Files JRE8 - http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html [9] AutoBackup - https://developer.android.com/guide/topics/data/autobackup.html [10] Enabling AutoBackup - https://developer.android.com/guide/topics/data/autobackup.html#EnablingAutoBackup
- Android Backup Extractor - https://sourceforge.net/projects/adbextractor/
製造者はアプリケーションが起動や終了した際デバイスユーザーに美しく快適な効果を提供したいため、アプリケーションがバックグラウンドになるときにスクリーンショットを保存するという概念を導入しました。この機能によりアプリケーションに潜在的にセキュリティリスクが発生する可能性があります。機密データが表示されているときユーザーが意図的にアプリケーションのスクリーンショットを撮る場合、または悪意のあるアプリケーションがデバイス上で動作していて断続的に画面をキャプチャできる場合に、機密データは開示される可能性があります。この情報はローカルストレージに書き込まれます。ルート化されたデバイス上での悪意のあるアプリケーションや、デバイスを盗む何者かによりその情報を復元される可能性があります。
例えば、デバイス上で動作している銀行アプリケーションのスクリーンショットをキャプチャすると、ユーザーアカウント、預金残高、取引明細などに関する情報が写し出される可能性があります。
Android では、アプリがバックグラウンドにいくとき、現在のアクティビティのスクリーンショットが撮影され、次にアプリに遷移したときに快適な効果をもたらすために使用されます。しかし、これはアプリ内に存在する機密情報を漏洩するでしょう。
アプリケーションがタスクスイッチャーを介して機密情報を開示するかどうかを検証するには、FLAG_SECURE
[1] オプションが設定されているかどうかを検出します。以下のコードスニペットのようなものを見つけることができるはずです。
LayoutParams.FLAG_SECURE
見つからない場合、アプリケーションはスクリーンキャプチャに対して脆弱です。
ブラックボックステストの中で、機密情報を含むアプリ内の任意の画面を開き、ホームボタンをクリックして、アプリがバックグラウンドにいきます。次にタスクスイッチャーボタンを押して、スナップショットを表示します。以下に示すように、FLAG_SECURE
が設定されている場合 (右側の画像) 、スナップショットは空ですが、FLAG_SECURE
が設定されていない場合 (左側の画像) 、アクティビティに情報が表示されます。
FLAG_SECURE not set |
FLAG_SECURE set |
---|---|
![]() |
![]() |
ユーザーや悪意のあるアプリケーションがバックグラウンドのアプリケーションからの情報にアクセスすることを防ぐには、以下に示すように FLAG_SECURE
を使用します。
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE);
setContentView(R.layout.activity_main);
さらに、以下の提案を実装して、アプリケーションセキュリティ態勢を強化することもできます。
- バックグラウンドではアプリを完全に終了します。これにより保持されている GUI 画面が破棄されます。
- 画面を離れる前、またはログアウトする前に GUI 画面のデータを無効にします。
- M1 - 不適切なプラットフォームの利用
- M2 - 安全でないデータストレージ
- V2.9: "バックグラウンド時にアプリはビューから機密データを削除している。"
- CWE-200 - Information Exposure
[1] FLAG_SECURE - https://developer.android.com/reference/android/view/Display.html#FLAG_SECURE
メモリを解析することで、アプリケーションがクラッシュした理由など、さまざまな問題の根本原因を特定できますが、機密データの特定にも使用できます。このセクションではプロセスメモリ内の機密データと一般的なデータの開示を確認する方法について説明します。
アプリケーションのメモリを調査できるようにするには、最初にメモリダンプを作成するか、メモリをリアルタイム更新で閲覧する必要があります。特定の機能がアプリケーション内で実行されている場合、アプリケーションはメモリに特定の情報を格納するだけであるため、これも既に問題です。。メモリの調査はもちろんアプリケーションのあらゆる段階でランダムに実行できますが、メモリの解析を行う前に、モバイルアプリケーションは何をしているのか、どのような機能を提供しているのかをまず理解し、(デコンパイルされた) ソースコードを深く研究することもより有益です。 データの復号化など、機密性の高い機能を特定したら、メモリダンプの調査が有益な場合があります。鍵や復号化された情報自体などの機密データを特定します。
まず、どの機密情報がメモリに格納されているかを特定する必要があります。次に、実行する必要があるチェックがいくつかあります。
- 機密情報がイミュータブルな構造体に格納されていないことを確認します。イミュータブルな構造体は無効化や変更後でも実際にはヒープ上で上書きされません。代わりに、イミュータブルな構造体を変更すると、ヒープ上にコピーが作られます。
BigInteger
とString
はメモリに機密を格納する際に最もよく使われる例の二つです。 - ミュータブルな構造体を使用する場合、
byte[]
やchar[]
など構造体のコピーがすべてクリアされることを確認します。
注意: 鍵を破壊すること (SecretKey secretKey = new SecretKeySpec("key".getBytes(), "AES"); secret.destroy();
など) は機能 しません 。SecretKeySpec ベースの鍵として secretKey.getEncoded()
からバックしたバイト配列を無効化すると、バックしたバイト配列のコピーを返します。
したがって開発者は、AndroidKeyStore
を使用しない場合、鍵がラップされ、適切に保護されていることを確認する必要があります (詳細は改善方法を参照) 。
RSA 鍵ペアは BigInteger
に基づいていることを理解します。AndroidKeyStore
の外で最初に使用した後にメモリに残ります。
最後に、一部の暗号はバイト配列を適切にクリーンアップしないものがあります。例えば、BounceyCastle
の AES Cipher
は必ずしも最新の作業鍵をクリーンアップするとは限りません。
Android Studio でアプリのメモリを解析するには、アプリを debuggable にする必要があります。 まだされていない場合、アプリをデバッグ可能にするために Android アプリを再パッケージおよび署名する方法については、XXX (-- TODO [Link to repackage and sign] --) の手順を参照します。また、メモリダンプを取るためには、Android Studio の "Tools/Android/Enable ADB Integration" で adb 統合を有効にする必要があります。
基本的な解析のために Android Studio の組込みツールを使用できます。Android Studio には "Android Monitor" タブにメモリを調べるためのツールが含まれています。"Android Monitor" タブでデバイスと解析したいアプリを選択し "Dump Java Heap" をクリックすると .hprof ファイルが作成されます。
.hprof ファイルを表示する新しいタブで、Package Tree View を選択する必要があります。その後、アプリのパッケージ名を使用して、メモリダンプに保存されたクラスのインスタンスにナビゲートできます。
メモリダンプをより深く解析するには、Eclipse Memory Analyser (MAT) を使用する必要があります。.hprof ファイルは、Android Studio 内に開いているプロジェクトパスを基準にして、ディレクトリ "captures" に格納されます。
.hprof ファイルを MAT で開く前に、変換する必要があります。ツール hprof-conf は Android SDK のディレクトリ platform-tools にあります。
./hprof-conv file.hprof file-converted.hprof
MAT を使用することにより、Object Query Language (OQL) の使用法にあるように、より多くの機能を利用できます。OQL は SQL ライクな言語であり、メモリダンプのクエリを実行するために使用できます。解析はドミネーターツリー上で行う必要があります。これにのみ静的クラスの変数やメモリが含まれています。
.hprof ファイルの潜在的な機密データをすばやく発見するには、それに対して string
コマンドを実行すると便利です。メモリ解析を行うときには、以下のような機密情報を調べます。
- パスワードやユーザー名
- 復号化された情報
- ユーザーまたはセッションに関連する情報
- セッション ID
- OS とのやり取り、例:ファイルの内容を読むなど
Java では、イミュータブルな構造体を使用して、秘密を運ぶべきではありません (String
, BigInteger
など) 。それらを無効化することは効果的ではありません。ガベージコレクタはそれらを収集するかもしれませんが、JVM のヒープに長期間留まる可能性があります。
むしろ、操作が完了した後に消去されるバイト配列 (byte[]
) または文字配列 (char[]
) を使用します。
byte[] secret = null;
try{
//get or generate the secret, do work with it, make sure you make no local copies
} finally {
if (null != secret && secret.length > 0) {
for (int i = 0; i < secret; i++) {
array[i] = (byte) 0;
}
}
}
鍵は AndroidKeyStore
により処理されるべきです。あるいは SecretKey
クラスを調整する必要があります。SecretKey
のより良い実装のために、以下の ErasableSecretKey
クラスを使用できます。このクラスは二つの部分で構成されています。
ErasableSecretKey
と呼ばれるラッパークラス。内部鍵の構築、クリーンメソッドと静的コンビニエンスメソッドの追加を管理します。ErasableSecretKey
のgetKey()
を呼び出すことで、実際の鍵を取得できます。- 内部の
InternalKey
クラス。javax.crypto.SecretKey, Destroyable
を実装する。実際にそれを破壊することができ、JCE から SecretKey として動作します。破壊可能な実装は、最初にヌルバイトを内部鍵に設定し、次に実際の鍵を表す byte[] への参照として null を設定します。InternalKey
は内部の byte[] 表現のコピーを提供しないことがわかります。代わりに実際のバージョンを示します。これによりアプリケーションメモリの多くの部分に鍵のコピーを持つことがなくなることを確認します。
public class ErasableSecretKey implements Serializable {
public static final int KEY_LENGTH = 256;
private java.security.Key secKey;
// Do not try to instantiate it: use the static methods.
// The static construction methods only use mutable structures or create a new key directly.
protected ErasableSecretKey(final java.security.Key key) {
this.secKey = key;
}
//Create a new `ErasableSecretKey` from a byte-array.
//Don't forget to clean the byte-array when you are done with the key.
public static ErasableSecretKey fromByte(byte[] key) {
return new ErasableSecretKey(new SecretKey.InternalKey(key, "AES"));
}
//Create a new key. Do not forget to implement your own 'Helper.getRandomKeyBytes()'.
public static ErasableSecretKey newKey() {
return fromByte(Helper.getRandomKeyBytes());
}
//clean the internal key, but only do so if it is not destroyed yet.
public void clean() {
try {
if (this.getKey() instanceof Destroyable) {
((Destroyable) this.getKey()).destroy();
}
} catch (DestroyFailedException e) {
//choose what you want to do now: so you could not destroy it, would you run on? Or rather inform the caller of the clean method informing him of the failure?
}
}
//convinience method that takes away the null-check so you can always just call ErasableSecretKey.clearKey(thekeytobecleared)
public static void clearKey(ErasableSecretKey key) {
if (key != null) {
key.clean();
}
}
//internal key klass which represents the actual key.
private static class InternalKey implements javax.crypto.SecretKey, Destroyable {
private byte[] key;
private final String algorithm;
public InternalKey(final byte[] key, final String algorithm) {
this.key = key;
this.algorithm = algorithm;
}
public String getAlgorithm() {
return this.algorithm;
}
public String getFormat() {
return "RAW";
}
//Do not return a copy of the byte-array but the byte-array itself. Be careful: clearing this byte-array, will clear the key.
public byte[] getEncoded() {
if(null == this.key){
throw new NullPointerException();
}
return this.key;
}
//destroy the key.
public void destroy() throws DestroyFailedException {
if (this.key != null) {
Arrays.fill(this.key, (byte) 0);
}
this.key = null;
}
public boolean isDestroyed() {
return this.key == null;
}
}
public final java.security.Key getKey() {
return this.secKey;
}
}
- M1 - 不適切なプラットフォームの利用
- M2 - 安全でないデータストレージ
- V2.10: "アプリは必要以上に長くメモリ内に機密データを保持せず、使用後は明示的にメモリがクリアされている。"
- CWE-316 - Cleartext Storage of Sensitive Information in Memory
- Securely stores sensitive data in RAM - https://www.nowsecure.com/resources/secure-mobile-development/coding-practices/securely-store-sensitive-data-in-ram/
- Memory Monitor - http://developer.android.com/tools/debugging/debugging-memory.html#ViewHeap
- Eclipse’s MAT (Memory Analyzer Tool) standalone - https://eclipse.org/mat/downloads.php
- Memory Analyzer which is part of Eclipse - https://www.eclipse.org/downloads/
- Fridump - https://github.com/Nightbringer21/fridump
- LiME - https://github.com/504ensicsLabs/LiME
機密情報を処理または照会するアプリは、信頼できるセキュアな環境で実行されていることを確認する必要があります。これを実現するために、アプリはデバイス上で以下のローカルチェックを実行できます。
- デバイスのアンロックするために設定された PIN やパスワード
- Android OS の最小バージョンの使用
- アクティブな USB デバッグの検出
- 暗号化されたデバイスの検出
- ルート化デバイスの検出 (「ルート検出のテスト」も参照)
アプリにより強制されるデバイスアクセスセキュリティポリシーをテストできるようにするには、ポリシーの書面によるコピーを提供する必要があります。ポリシーではどのようなチェックが利用可能であり、どのように実施されているかを定義する必要があります。例えば、あるチェックでアプリが Android Marshmallow (Android 6.0) 以上でのみ動作し、アプリが Android バージョン < 6.0 で実行されている場合、アプリは自身を終了することを要求します。
ポリシーを実装するコード内の関数をバイパスできるかどうかを特定および確認する必要があります。
動的解析はアプリにより実行されるチェックと期待される動作に依存し、バイパスできるかどうかをチェックする必要があります。
Android デバイス上のさまざまなチェックは Settings.Secure [1] からさまざまなシステム設定を照会することで実装できます。Device Administration API [2] はセキュリティ対応アプリケーションを作成するためのさまざまなメカニズムを提供します。パスワードポリシーやデバイスの暗号化を実施できます。
- M1 - 不適切なプラットフォームの利用
- V2.11: "アプリは最低限のデバイスアクセスセキュリティポリシーを適用しており、ユーザーにデバイスパスコードを設定することなどを必要としている。"
- [1] Settings.Secure - https://developer.android.com/reference/android/provider/Settings.Secure.html
- [2] Device Administration API - https://developer.android.com/guide/topics/admin/device-admin.html
ユーザーを教育することはモバイルアプリの使用における重要な要素です。多くのセキュリティコントロールがすでに導入されていますが、ユーザーは迂回や誤用の可能性があります。
以下のリストは、アプリを最初に開いて使用する際のユーザーに対する潜在的な警告や助言を示しています。
- どのような種類のデータがローカル及びリモートに格納されているかの一覧を表示します。これは情報が非常に広範である可能性があるため外部リソースへのリンクとなることもあります。
- 新しいユーザーアカウントがアプリ内に作成された場合、提供されたパスワードがセキュアでありパスワードポリシーに沿っているかどうかをユーザーに表示する必要があります。
- ユーザーがルート化デバイスにアプリをインストールしている場合、これは危険であり、OS レベルのセキュリティコントロールを無効にし、マルウェアに感染しやすいという警告を表示する必要があります。詳細は「ルート検出のテスト」も参照ください。
- ユーザーがアプリを古い Android バージョンにインストールした場合、警告を表示する必要があります。詳細は「デバイスアクセスセキュリティポリシーのテスト」も参照ください。
実装されている教育コントロールのリストを提供する必要があります。コントロールは適切に実装されベストプラクティスに沿っているかどうかを、コード内で検証する必要があります。
アプリをインストールした後および使用中に、教育目的を持ち定義された教育コントロールに沿った警告がユーザーに示されているかどうかをチェックする必要があります。
概要セクションに記載されているキーポイントに対処する警告を実装する必要があります。
- M1 - 不適切なプラットフォームの利用
- V2.12: "アプリは処理される個人識別情報の種類、およびユーザーがアプリを使用する際に従うべきセキュリティのベストプラクティスについて通知している。"