iOSアプリのセキュリティチェックリスト
フェーズ0:ガバナンス、環境、およびCI/CD
CIゲートの適用(CI gates)
- 各PRで静的チェックを実行:swiftlint(または同等のもの)、ユニット/UIテスト、依存関係監査。
- SAST/SCA/DASTのHigh/Criticalな発見事項についてリリースをブロック。リリースブランチでlintエラーがゼロであること。
シークレットの取り扱い
- ソース、Info.plist、ストーリーボード、またはログにシークレットを含めない。
- シークレットには環境/シークレットマネージャーを使用。
- アナリティクス内のシークレットを墨塗り(redact)。
- キー/トークンを定期的に、また漏洩の疑いがある場合はローテーションする。
証拠とトレーサビリティ
- 各アーティファクトとともにスキャンレポートをアーカイブする。
- 各アーティファクトとともにSBOMをアーカイブする。
- 各アーティファクトとともにビルドログをアーカイブする。
- 各アーティファクトとともにリリースノートをアーカイブする。
スキャンの自動化
- モバイルSAST/DAST/SCAプラットフォームをCI/CDに統合し、各PRおよびプレリリースで実行する(例:Ostorlab)。
- SAST/DAST/SCAからのHigh/Criticalな問題でCIゲートを自動的に失敗させる。
フェーズ1:脅威モデリング(Threat modeling)、データ分類、およびインベントリ
- データ(PII、認証情報、トークン、支払い)を分類し、収集、処理、保存、および送信をマッピングする。
- 信頼境界(デバイス、Keychain/ファイル、URLSession、WebView、ネイティブラリへのプラットフォームチャネル)を特定する。
- サードパーティSDKのインベントリを作成。メンテナンスが行き届き、署名された、最小特権のSDKを優先し、数を最小限に抑える。
フェーズ2:ビルドと構成の堅牢化(Xcode + Info.plist)
XcodeのRelease設定(Release settings)
- シンボルとデッドコード(dead code)を取り除く。
- モジュール全体の最適化(whole-module optimization)を有効にする。
- リリースビルドでDEBUGフラグを無効にする。
- テスト/デバッグ用フレームワークをリリースから除外する。
- 開発用エンドポイントとフィーチャーフラグ(feature flags)をリリースから削除する。
コード署名とエンタイトルメント(entitlements)
- 必要なエンタイトルメントのみを付与する(使用されていないApp Groups、Keychain Sharing、NFC、Bluetoothなどは不可)。
- 専用のリリースプロファイルを使用した自動署名を利用する。
- 署名用クレデンシャルを保護する(例:CIのシークレットストア、アクセス制御)。
App Transport Security(ATS)
-
NSAllowsArbitraryLoads = falseに設定する。 - TLS 1.2+のみを優先する。
- 厳格な
NSExceptionDomainsは、必要な場合にのみ期限付きで使用する。
Info.plistの衛生管理
- 必要な場合を除き、ドキュメントの共有を無効にする:
-
UIFileSharingEnabled = false -
LSSupportsOpeningDocumentsInPlace = false -
LSApplicationQueriesSchemesを最小限に抑える。カスタムスキームよりもUniversal Linksを優先する。 - すべての権限について、正確な
NS[AppData/AppleEvents/SystemAdministration]UsageDescription文字列を提供する。
フェーズ3:ローカルストレージとKeychainのセキュリティ
Keychainの使用
- トークン/シークレットは、可能な限り最も強力なクラスでKeychainに保存する:
-
kSecAttrAccessibleWhenUnlockedThisDeviceOnly - または、適用可能な場合は
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly。 - UXが許せば、
kSecAccessControlフラグ(例:biometryCurrentSet、userPresence)を使用して機密アイテムをゲート制御する。 - ログアウト時にKeychainアイテムを削除する。
- 高リスクなトークンの場合、デバイスバインディング(device-binding)を検討する。
ファイル保護とデータベース
- ファイルにデータ保護を適用する:
NSFileProtectionComplete(デフォルト属性またはファイル単位)。 - 機密データベース/ファイルを暗号化する(例:SQLCipherまたは同等のもの)。
- シークレットをプレーンテキストで決して保存しない。
キャッシュと派生データ
- キャッシュ/一時ファイルにPIIを永続化しない。
- 必要に応じて、ログアウト時に機密なキャッシュ/一時データをパージする。
- 必要に応じて、バックグラウンド処理で機密なキャッシュ/一時データをパージする。
-
UserDefaultsにシークレットを書き込まない。
フェーズ4:プライバシーの保護(UI、クリップボード、スクリーンショット)
- バックグラウンドに移行する際に機密UIを墨塗りする(
sceneWillResignActive/applicationWillResignActiveでのぼかしまたはプレースホルダー)。 - 画面キャプチャ(
UIScreen.main.isCaptured)を検出し、非常に機密性の高いビューについては動作を調整する。 - アプリの起動時にクリップボードを読み取らない。
-
UIPasteboard.generalにシークレットを決して配置しない。
フェーズ5:ネットワークセキュリティとTLS/ピニング(pinning)
URLSession/ATS
- 信頼できるホストにHTTPSを強制する。
- 無効な証明書とホスト名を拒否する。
- HTTPへのフォールバックを無効にする。
- 厳格なタイムアウトを設定する。
- 該当する場合は、ジッター(jitter)を伴うリトライを実装する。
- TLSエラー時にはフェイルクローズ(fail closed)する。
- サーバーのTLS設定でTLS 1.2+の使用を強制する。
証明書のピニング(Certificate pinning)
- SPKI SHA-256ピニングを優先。
- ホストごとに少なくとも1つのバックアップピンを含める。
-
URLSessionDelegateの信頼評価を介してピニングを実装する。 - MITMの試みに対する動作を検証する。
- 期限切れ/ローテーションされたリーフ証明書の動作を検証する。
- 安全なピンのローテーションを計画する。
- ピンの失敗に関するテレメトリを含める。
Cookieとトークン
- Webフローには、セキュア、HttpOnly、SameSiteのCookieを使用する。
- デバイス上の長期ベアラートークン(bearer tokens)を避ける。
- 機密セッション後にCookieとWebデータをクリアする。
フェーズ6:認証、認可、およびセッション
認証フロー
- パブリッククライアントにはPKCEを使用したOAuth2/OIDCを使用する。
- アプリにクライアントシークレットを絶対に埋め込まない。
- 認証には、WebViewよりもシステムブラウザ(
ASWebAuthenticationSession/SafariViewController)を優先する。
トークン
- 短期のアクセストークン(access tokens)を使用する。
- リフレッシュトークン(refresh tokens)はKeychainに保存する。
- ログアウト時にトークンをローテーションして無効化する。
- デバイス変更時にトークンをローテーションして無効化する。
- 必要に応じて、セッションをデバイスのコンテキストリスク(device risk)にバインドする。
- 異常(geo/IP/ASNの変更)を検出し、対応する。
認可
- サーバー側で認可を強制する。クライアント側のチェックはアドバイザリ(参考)として扱う。
- IDOR/BOLAの脆弱性を検証する。
- 権限昇格の問題を検証する。
フェーズ7:WebViewの堅牢化(WKWebView)
- 必要な場合にのみ
WKWebViewを使用する。 - 分離のためにアプリバウンドドメイン(
WKAppBoundDomains)を優先する。
デフォルト
- 必要な場合を除きJavaScriptを無効にする。
-
javaScriptCanOpenWindowsAutomaticallyを無効にする。 -
WKNavigationDelegateを介してナビゲーションを許可リスト化する。 -
file://URLをブロックする。 -
javascript:URLをブロックする。 - 機密セッションには非永続的なデータストアを使用する。
- ログアウト時に
HTTPCookieStoreとWebサイトデータをクリアする。 - リリース時にWeb Inspectorが無効になっていることを確認する(iOS 16+
isInspectable = false)。
コンテンツセキュリティ
- ファーストパーティのWebコンテンツには、CSPを強制する。
- HTTPSを強制する。
- 混在コンテンツを許可しない。
フェーズ8:アプリ間通信とディープリンク(deep links)
- Universal Linksを優先する。
- 受信したリンクのオリジンとパラメータを検証する。
カスタムURLスキームが使用される場合
- 一意のスキームを選択する。
- すべてのパラメータを検証する。
- URLハイジャックから防御する。
- オープンリダイレクトから防御する。
-
LSApplicationQueriesSchemesを既知の必要なスキームに限定する。
フェーズ9:リバースエンジニアリングと改ざんに対する耐性(tampering)
静的漏洩の低減
- ストアに出荷されるバイナリからシンボルを取り除く。
- クラッシュシンボリケーションのためにdSYMをサーバー側に保存する。
- リリースビルドから詳細ログを削除する。
- リリースビルドからデバッグメニューを削除する。
- リリースビルドからテスト用エンドポイントを削除する。
- 本番向けではないフィーチャートグル(feature toggles)を削除する。
環境チェック
- ジェイルブレイクの存在(ファイル、システムコール、不審なライブラリ)に関するシグナルを記録する。
- フッキングフレームワークに関するシグナルを記録する。
- デバッガーの存在に関するシグナルを記録する。
- クライアントのチェックのみに依存せず、比例的に対応する(機能低下/ブロック/テレメトリ)。
改ざんテスト(Tamper testing)
- 再パッケージ化されたビルドが整合性チェックまたはサーバーアテステーションに失敗することを検証する。
- 再署名されたビルドが整合性チェックまたはサーバーアテステーションに失敗することを検証する。
- クライアント側のチェックはシグナルとして扱い、単独のセキュリティコントロールとしては決して扱わない。
フェーズ10:暗号化とランダム性
- プラットフォームの暗号化(
CryptoKit/CommonCrypto)を使用する。カスタムの暗号化を実装しない。 - 認証付き暗号化にはAES-GCMまたはChaCha20-Poly1305を使用する。
- KDFとしてHKDF/PBKDF2を使用し、適切なソルトと十分なパラメータを使用する。
-
SecRandomCopyBytesを使用してキー/IVを生成する。 - IV/ノンスを決して再利用しない。
- 暗号化キーはKeychainに保存する。
フェーズ11:権限、通知、および識別子
- 必要な最小限の権限を要求する。
- 使用時点で権限を要求する。
- 権限を要求する際は、ユーザーへの価値提案を明確に説明する。
ATT(App Tracking Transparency)
- 必要な場合にのみATTを要求する。
- ATTが拒否された場合はグレースフルに(gracefully)機能を低下させる。
- フィンガープリンティング(fingerprinting)および類似の禁止されているトラッキング手法を避ける。
識別子
-
identifierForVendorを優先する。 - ATTが付与されていない限り、IDFAを避ける。
- 禁止されている永続的な識別子を決して使用しない。
フェーズ12:ロギング、アナリティクス、およびクラッシュレポート
- プライバシーアノテーション付きの
os_logを使用する。 - リリースビルドでは詳細ログを無効にする。
- ログからPIIを墨塗りする。
- ログからシークレットを墨塗りする。
- アナリティクスイベントからPIIとシークレットを墨塗りする。
- クラッシュレポートからPIIとシークレットを墨塗りする。
- 可能な場合はアナリティクスをサンプリングして集約する。
- クラッシュのシンボリケーションがサーバー側で行われることを確認する。
- アプリと一緒にシンボルを絶対に出荷しない。
フェーズ13:テスト、DAST、および実行時検証
トラフィック傍受テスト
- 無効な証明書によるMITMへの耐性を検証する。
- ホスト名の不一致によるMITMへの耐性を検証する。
- 信頼できないCAによるMITMへの耐性を検証する。
- 実装されている場合、ピニングが傍受をブロックすることを確認する。
実行時検査
- ログにシークレット/PIIが含まれていないことを確認する。
- Keychainアイテムが正しいACL(アクセス制御リスト)で存在することを確認する。
- ファイルに
NSFileProtectionComplete(または必要に応じてより厳格なもの)があることを確認する。
API悪用テスト
- レート制限(rate limiting)をテストする。
- リプレイ保護(replay protections)をテストする。
- ページネーションの境界をテストする。
- 大量割り当て(mass assignment)の脆弱性をテストする。
- 詳細なエラーの漏洩をテストする。
動的解析
- コアフローを実行しながらモバイルDASTを実行し、トランスポートの構成ミスを明らかにする。
- DASTを使用して実行時の問題を明らかにする(例:Ostorlabを使用)。
- トリアージと再テストのための証拠をエクスポートする。
フェーズ14:SBOMおよび証拠
SBOMと依存関係
- Swift/Objective-CコードのSBOMを生成する。
- 組み込みSDKのSBOMを生成する。
- 依存関係に影響を与えるCVEをトラッキングする。
- リスクに基づいて依存関係を定期的に更新する。
証拠のバンドル
- スキャンアーティファクトをリリースに添付する。
- SBOMをリリースに添付する。
- Info.plistの差分をリリースに添付する。
- エンタイトルメントのリストをリリースに添付する。
- テストレポートをリリースに添付する。
フェーズ15:レポートと基準の整合性
レポート
- エグゼクティブサマリーを作成する。
- 範囲と方法論を文書化する。
- PoCと証拠を含む詳細な発見事項を文書化する。
- 発見事項にリスク評価を割り当てる。
- 修復ガイダンスを提供する。
- 再テストの結果を含める。
OWASP MASVSとのマッピング
- PLATFORM:フェーズ2、7–8、11。
- STORAGE:フェーズ3–4。
- CRYPTO:フェーズ10。
- AUTH:フェーズ6。
- NETWORK:フェーズ5。
- CODE:フェーズ2、9、12。
- RESILIENCE:フェーズ9。
実務担当者向けクイックリファレンス
- Info.plist/ATS:任意のロードなし。厳格な例外ドメイン。必要な場合を除き、ドキュメント/ファイルの共有を無効にする。
- Keychain:
ThisDeviceOnlyクラス。オプションでbiometryCurrentSet/userPresence。ログアウト時にシークレットを削除。 - ファイル:
NSFileProtectionComplete。データベース/ファイルを暗号化。シークレットにUserDefaultsを使用しないこと。 - Network:HTTPSのみ。
URLSessionDelegateによるピニング(バックアップ付きのSPKI SHA-256)。MITMの試みが失敗することを検証すること。 - Auth:OIDC + PKCE。短期トークン。Keychain内のリフレッシュトークン。サーバー側での認可。IDOR/BOLAのテスト。
- WebView:認証にはシステムブラウザを優先。
WKWebViewの場合、デフォルトでJSオフ、ナビゲーションの許可リスト化、アプリバウンドドメイン、機密フロー用の非永続的ストア。 - プライバシー:バックグラウンドのスナップショットをマスキング。クリップボードのシークレットを避ける。権限を最小限に抑え、正確な使用法の文字列を維持すること。
- リリースの衛生管理:アプリ内のシンボルを削除し、dSYMを非公開に保つ。デバッグログ/メニューの削除。エンタイトルメントを検証すること。
構成の例とスニペット
ATS(Info.plist)
-
NSAppTransportSecurityを構成する: -
NSAllowsArbitraryLoads = false -
NSExceptionDomains = { yourdomain.com: { NSTemporaryExceptionAllowsInsecureHTTPLoads = false, NSIncludesSubdomains = true, NSTemporaryExceptionMinimumTLSVersion = TLSv1.2 } }
ファイル保護(Swift)
- 完全なファイル保護を使用する:
-
try data.write(to: url, options: .completeFileProtection)
Keychainアイテム(Swiftの要点)
- Keychainアイテムを構成する:
-
kSecClass: kSecClassGenericPassword -
kSecAttrAccessible: kSecAttrAccessibleWhenUnlockedThisDeviceOnly -
kSecAttrAccessControl: SecAccessControlCreateWithFlags(..., [.biometryCurrentSet, .userPresence])
URLSessionピニング(概要)
-
urlSession(_:didReceive:completionHandler:)内: -
SecTrustを評価する。 - 証明書からSPKIを抽出する。
- SPKIのSHA-256をピンの許可リストと比較する。
- 適切に
.useCredentialまたは.cancelを選択する。
軽量なワークフロー
プレマージゲート(Pre-merge gate)
- lintとテストを実行する。
- 依存関係監査を実行する。
- SAST/SCAスキャンをトリガーする。
- High/Criticalな問題でマージをブロックする。
プレリリースステージング(Pre-release staging)
- Info.plistとエンタイトルメントを検証する。
- リリースバイナリをビルドする。
- アプリ内にシンボルが出荷されていないことを確認する。
- 主要なユーザーフローを実行しながらDASTを実行し(例:Ostorlabを使用)、TLS/ピニング、ストレージ保護、およびロギングの衛生状態を検証する。
- 問題を修正し、再テストする。
リリース後の監視(Post-release monitoring)
- クラッシュとパフォーマンスをトラッキングする。
- 定められたスケジュールでキー/トークンをローテーションする。
- SDKのアドバイザリとCVEを監視する。
- サードパーティSDK/ライブラリ(例:Ostorlab)を監視し続け、新しい脆弱性を警告する。
- 新しい脆弱性が発見された場合に的を絞った再テストをトリガーする。