Androidアプリのセキュリティチェックリスト
フェーズ0:ガバナンス、環境、およびCI/CD
CIゲートの適用(CI gates)
- 各PRで静的チェックを実行:ktlint / Android Lint、ユニット/UIテスト、依存関係監査(Gradle、SBOM)。
- SAST/SCA/DASTのHigh/Criticalな発見事項についてリリースをブロック。リリースブランチでlintエラーがゼロであること。
シークレットの取り扱い
- ソース、BuildConfig、AndroidManifest.xml、リソース、またはログにシークレットを含めない。
- 環境/シークレットマネージャー(例:Gradleシークレット、クラウドKMS)を使用。アナリティクス内のシークレットを墨塗り(redact)。
- キー/トークンを定期的に、また漏洩の疑いがある場合はローテーションする。
証拠とトレーサビリティ
- スキャンレポート、SBOM、ビルドログ、およびリリースノートを各アーティファクトとともにアーカイブする。
スキャンの自動化
- モバイルSAST/DAST/SCAプラットフォームをCI/CDに統合し、各PRおよびプレリリースで実行し、High/Criticalな問題が発生した場合はゲートを自動的に失敗させる。
フェーズ1:脅威モデリング(Threat modeling)、データ分類、およびインベントリ
- データ(PII、認証情報、トークン、支払い)を分類し、収集、処理、保存(SharedPreferences、ファイル、データベース、Keystore)、および送信をマッピングする。
- 信頼境界(デバイス、アプリのサンドボックス、Keystore、IPC/Binder、WebView、ネイティブライブラリ、バックエンドAPI)を特定する。
- サードパーティSDKのインベントリを作成。メンテナンスが行き届き、署名された、最小特権のSDKを優先し、数を最小限に抑える。
フェーズ2:ビルドと構成の堅牢化(Gradle + Manifest)
Gradle/Release設定(Release settings)
- コードの縮小/難読化機能を持つリリースビルドを使用する(
minifyEnabled true、shrinkResources true)。 - 出荷されるAPK/AABからデバッグシンボルを取り除く。
- マッピングファイルはサーバー側に保持する。
- テスト/デバッグ用ライブラリをリリースから除外する。
- 開発用エンドポイントとフィーチャーフラグ(feature flags)を本番ビルドから削除する。
署名と権限
- 署名キーを保護する(ハードウェアバックのキーストア、アクセス制限)。
- 専用のリリース署名構成を使用する。
-
AndroidManifest.xmlには必要な権限のみを宣言する。 - 過度な危険な権限(dangerous permissions)を避ける。
ネットワークセキュリティ構成
-
networkSecurityConfigを使用してTLSを強制する。 - クリアテキストトラフィックを許可しない(
android:usesCleartextTraffic="false")。 - ドメイン構成の上書きは、必要な場合にのみ期限付きで使用する。
マニフェストの衛生管理
- 厳密に必要でない限り、コンポーネントに
exported="true"を設定しない。エクスポートする場合は権限で制限する。 - デバッグフラグを削除する(リリースでは
android:debuggable="false"、適切な暗号化を伴う場合を除きallowBackup="true"を設定しない)。 - すべての実行時権限(runtime permissions)について、正確な権限の根拠テキスト(アプリ内)を提供する。
フェーズ3:ローカルストレージとKeystoreのセキュリティ
Keystoreの使用
- 長期キーは、実行可能な最も強力な保護(利用可能な場合はハードウェアバック、UXが許せばユーザー認証)を備えたAndroid Keystoreに保存する。
- 非常に機密性の高いキーには
setUserAuthenticationRequired(true)を使用し、必要に応じて生体認証/ロック画面で操作を制限する。 - ログアウト時またはデバイス変更時に、Keystoreでラップされたシークレットを削除する。
- 高リスクなトークンの場合、デバイスバインディング(device-binding)を検討する。
ファイル、SharedPreferences、データベース
- プレーンテキストファイルまたは
SharedPreferencesにシークレットや機密PIIを保存しない。 - 機密データベース/ファイルを暗号化する(例:SQLCipherまたは同等のファイル暗号化)。
- アプリ専用ストレージ(
MODE_PRIVATE)を使用する。 - サポートされている場合はファイル保護フラグを適用する。
- バックアップに暗号化されていないシークレットが含まれていないことを確認する。
キャッシュと派生データ
- キャッシュ/一時ファイルにPIIを永続化しない。
- 必要に応じて、ログアウト時およびバックグラウンド処理の一環として、キャッシュ/一時ファイルをパージする。
- 一時的であっても、シークレットをログや
SharedPreferencesに書き込まない。
フェーズ4:プライバシーの保護(UI、クリップボード、スクリーンショット)
- アプリスイッチャーのスクリーンショットで機密UIを墨塗りする(例:機密のActivities/Viewsで
FLAG_SECUREを使用する)。 - 非常に機密性の高いフローについては、追加のマスキングまたは別の「セキュアモード」のActivitiesを検討する。
- アプリの起動時にクリップボードを読み取らない。パスワード/長期トークンをクリップボードに配置しない。
フェーズ5:ネットワークセキュリティとTLS/ピニング(pinning)
HTTPS/TLSの適用
- 信頼できるホストにHTTPSを強制する。無効な証明書/ホスト名を拒否する。HTTPへのフォールバックを無効にする。
- 厳格なタイムアウト、ジッター(jitter)を伴うエクスポネンシャルバックオフを設定し、TLSエラー時にはフェイルクローズ(fail closed)する。
証明書のピニング(Certificate pinning)
- SPKI SHA-256ピニングを優先。ホストごとに少なくとも1つのバックアップピンを含める。
- OkHttp / カスタム
TrustManagerまたはnetworkSecurityConfigを介してピニングを実装する。 - MITMシナリオおよび期限切れ/ローテーションされたリーフ証明書に対して検証する。
- 安全なピンのローテーションを計画する。ピンの失敗に関するテレメトリを含める。
Cookieとトークン
- Webフローには、セキュア、HttpOnly、SameSiteのCookieを使用する。
- デバイス上の長期ベアラートークン(bearer tokens)を避ける。
- 機密セッション後にCookieとWebViewデータをクリアする。
フェーズ6:認証、認可、およびセッション
認証フロー
- パブリッククライアントにはPKCEを使用したOAuth2/OIDCを使用する。アプリにクライアントシークレットを絶対に埋め込まない。
- ログインには、アプリ内のWebViewよりもシステム/ブラウザベースの認証(Chrome Custom Tabs / デフォルトブラウザ)を優先する。
トークン
- 短期のアクセストークン(access tokens)を使用する。
- リフレッシュトークン(refresh tokens)は、Keystoreキーにバインドされた暗号化ストレージにのみ保存する。
- ログアウト時、デバイス変更時、および不審なアクティビティ発生時に、トークンをローテーションして無効化する。
- 必要に応じて、セッションをデバイスのコンテキストリスク(device risk)にバインドする。
認可
- サーバー側で認可を強制する。クライアント側のチェックはアドバイザリ(参考)としてのみ扱う。
- アプリが使用するすべてのバックエンドAPIにわたって、IDOR/BOLAの脆弱性と権限昇格をテストし、防止する。
フェーズ7:WebViewの堅牢化
- 必要な場合にのみWebViewを使用する。可能な場合は認証と支払いにブラウザを優先する。
デフォルト
- 厳密に必要でない限りJavaScriptを無効にする。
- JavaScriptを有効にする場合は、機能を制限し、
setAllowFileAccessFromFileURLsおよびsetAllowUniversalAccessFromFileURLsを無効にする。 - 特にAPI < 17では、機密メソッドを公開するオブジェクトで
addJavascriptInterfaceを有効にしない。 - ナビゲーションの許可リスト(ホスト/パス)を強制する。
file://、javascript:、および信頼できないスキームをブロックする。 - 可能な場合は、機密セッションに非永続的なWebViewストレージを使用する。
- ログアウト時にCookie、キャッシュ、および履歴をクリアする。
コンテンツセキュリティ
- ファーストパーティのWebコンテンツはHTTPSのみで提供する。
- 混在コンテンツを許可しない(
MIXED_CONTENT_NEVER_ALLOW)。 - リモートコンテンツの整合性がチェックされ、制御されていることを確認する(厳密に必要でない限り、信頼できないサードパーティのスクリプトを使用しない)。
フェーズ8:アプリ間通信とディープリンク(deep links)
- ディープリンクにはApp Links(検証済みリンク)を優先する。受信したリンクのオリジンとパラメータを検証する。
カスタムスキームとインテント(intents)
- カスタムスキームまたは暗黙的インテントが使用される場合は、すべての受信データの一意性と堅牢な検証を確保する。
- エクスポートされたActivities/Services/BroadcastReceiversを権限または明示的インテントで保護する。過度に広範なインテントフィルターを避ける。
- 呼び出し元のIDとパラメータをチェックすることにより、インテントスプーフィング(intent spoofing)、URLハイジャック、およびオープンリダイレクトから防御する。
フェーズ9:リバースエンジニアリングと改ざんに対する耐性(tampering)
静的漏洩の低減
- リリースビルドから、デバッグビルド、詳細ログ、デバッグメニュー、およびテスト用エンドポイントを削除する。
- コード(ProGuard/R8)とリソースを難読化し、縮小する。
- 意味のある文字列(APIキー、シークレット)をリソースやネイティブライブラリに残さない。
環境チェック
- root/フッキング/デバッガーのインジケーター(一般的なrootパス、書き込み可能なシステムディレクトリ、インスツルメンテーションフレームワーク)を検出し、シグナルを記録する。
- UXを維持しながら、リスクに基づいて比例して対応する(機能の低下、追加の検証、またはブロック)。
改ざんテスト(Tamper testing)
- 再パッケージ化または再署名されたアプリが整合性チェックまたはサーバーアテステーション(例:セキュリティ/アテステーションAPI)に失敗することを確認する。
- クライアント側のシグナルをバックエンドへのヒントとして扱う。単独のセキュリティコントロールとして決して扱わない。
フェーズ10:暗号化とランダム性
- プラットフォームが提供する暗号プリミティブ(Java Cryptography Architecture、Android Keystore)を使用する。カスタムアルゴリズムを構築しない。
- 認証付き暗号化には、AES-GCM/ChaCha20-Poly1305などの最新のAEADモードを使用する。
- 適切なパラメータとソルトとともに確立されたKDF(PBKDF2/HKDF)を使用する。
- セキュアなRNG(
SecureRandomまたはプラットフォームAPI)を使用してキー/IVを生成する。 - IV/ノンスを決して再利用しない。
- キーはKeystoreに保持するか、保存時に暗号化する。
フェーズ11:権限、通知、および識別子
- 使用時点で必要最小限の権限を要求し、ユーザーにとっての価値をコンテキストに合わせて明確に説明する。
プライバシー関連の権限
- 位置情報、連絡先、カメラ、マイク、ストレージ、電話の状態へのアクセスを必要最小限に制限する。
- 拒否および取り消しをグレースフルに(gracefully)処理する。
- ユーザーにプレッシャーをかけるダークパターン(dark patterns)を避ける。
識別子とトラッキング
- アプリスコープの、またはリセット可能な識別子を優先する。アプリ間で永続的なフィンガープリンティング(fingerprinting)を構築しない。
- プラットフォームのポリシーに従ってのみ、広告IDまたはアナリティクスIDを使用する。
- トラッキングに禁止されているハードウェア識別子を決して使用しない。
通知
- 通知コンテンツ内の機密データを最小限に抑える。
- 可能な場合は、一般的な表現を使用し、ロック画面では詳細を非表示にする。
フェーズ12:ロギング、アナリティクス、およびクラッシュレポート
- 重大度レベルを持つ構造化ロギングを使用する。リリースビルドでは詳細/デバッグロギングを無効にする。
- ログ、アナリティクスイベント、およびクラッシュレポートからPIIとシークレットを墨塗りする。
- 可能な場合はアナリティクスを集約してサンプリングする。
- クラッシュの難読化解除のために、難読化されたビルドのシンボル/マッピングファイルがサーバー側に安全に保存されていることを確認する。
- アプリ内にシンボル/マッピングファイルを出荷しない。
フェーズ13:テスト、DAST、および実行時検証
トラフィック傍受テスト
- MITMに対する耐性(無効な証明書、ホスト名の不一致、信頼できないCA)を検証する。
- 構成されている場合、ピニングが傍受をブロックすることを検証する。
実行時検査
- ログにシークレット/PIIが表示されないことを確認する。
- 機密データが保存時に暗号化されていることを検証する。
- 実行時のWebViewの動作(ナビゲーション、JS、ストレージ)を確認する。
- 実行時のローカルストレージの動作(ファイル、SharedPreferences、データベース)を確認する。
- Keystoreバックのキーが期待される場所で使用されていることを検証する。
- Keystoreキーのアクセス制御が設計通りに機能していることを検証する。
API悪用テスト
- レート制限(rate limiting)をテストする。
- リプレイ保護(replay protection)をテストする。
- ページネーションの境界をテストする。
- 大量割り当て(mass assignment)をテストする。
- バックエンドAPIのエラーメッセージの衛生管理をテストする。
動的解析
- コアフローを実行しながらモバイルDASTを実行し、トランスポートの構成ミス、WebViewの問題、ストレージの漏洩、およびロギングの問題を明らかにする。
- トリアージと再テストのためにDASTから証拠をエクスポートする。
フェーズ14:SBOMおよび証拠
SBOMと依存関係
- すべてのGradleモジュールおよび組み込みSDKのSBOMを生成する。
- CVEをトラッキングし、依存関係を定期的に更新する。
- リリース準備の一環として依存関係の健全性を監視する。
- リリース準備の一環としてライセンスのコンプライアンスを監視する。
証拠のバンドル
- スキャンアーティファクトをリリースレコードに添付する。
- SBOMをリリースレコードに添付する。
- マニフェストの差分をリリースレコードに添付する。
- 権限とコンポーネントのエクスポートリストをリリースレコードに添付する。
- 自動/手動テストレポートをリリースレコードに添付する。
フェーズ15:レポートと基準の整合性
レポート
- エグゼクティブサマリー、範囲/方法論、PoC/証拠を含む詳細な発見事項、リスク評価、修復ガイダンス、および再テストの結果を提供する。
基準マッピング(例)
- ARCH:フェーズ1–2
- PLATFORM:フェーズ2、7–8、11
- STORAGE:フェーズ3–4
- CRYPTO:フェーズ10
- AUTH:フェーズ6
- NETWORK:フェーズ5
- CODE:フェーズ2、9、12
- RESILIENCE:フェーズ9
実務担当者向けクイックリファレンス
- Manifest/Network:リリース時にクリアテキストトラフィックがないこと。厳格な
networkSecurityConfigドメイン。強力な正当性なしにコンポーネントをエクスポートしないこと。 - シークレットとストレージ:長期キーにはAndroid Keystoreを使用。DB/ファイルを暗号化。シークレットを
SharedPreferencesやログに絶対に保存しないこと。 - Network:HTTPSのみ。クリティカルなエンドポイントのバックアップを備えた証明書/SPKIピニング。MITMの試みが失敗することを検証すること。
- Auth:OIDC + PKCE。短期アクセストークン。暗号化ストレージ内のリフレッシュトークン。サーバー側での認可。IDOR/BOLAのテスト。
- WebView:認証にはブラウザを優先。WebViewの場合、デフォルトでJSをオフ、インターフェースの制限、ナビゲーションの許可リスト、機密フロー用の非永続的ストア。
- プライバシー:機密画面には
FLAG_SECUREを使用。クリップボードのシークレットを避ける。権限を最小限に抑え、明確な説明を提供すること。 - リリースの衛生管理:難読化と縮小。デバッグログ/メニュー/テストエンドポイントの削除。各リリースで権限とエクスポートされたコンポーネントを検証すること。