CyberFix Note
セキュアコーディング

OWASP MASVSで組み立てるモバイルアプリのセキュリティ設計。端末内の保存から通信とプラットフォーム連携まで

対象の目安: iOSとAndroidのアプリ開発者および設計レビュー担当 / 実務

リク編集長 / セキュリティ全般・戦略
・ 約41分で読めます
OWASP MASVSで組み立てるモバイルアプリのセキュリティ設計。端末内の保存から通信とプラットフォーム連携まで

サーバー中心のWebアプリでは、コードもデータもサーバー側にあり、守るべき境界はHTTPリクエストの入口でした。モバイルアプリはそうなりません。バイナリは利用者の端末に配布され、実行環境そのものが開発者の管理下から離れます。端末が正常に動いていても、同じ端末で動く別のアプリはアプリの外部からのデータ供給元になりますし、端末が改変されていれば、アプリのメモリも保存領域も外から観測されうる状態になります。

この前提に立って設計を組み立てるための共通言語が、OWASPのMobile Application Security(MAS)プロジェクトが公開しているMASVS(Mobile Application Security Verification Standard)とMASTG(Mobile Application Security Testing Guide)です。この記事では、MASVSの制御カテゴリに沿って、端末内のデータ保存、認証、通信、プラットフォーム連携、コード品質、耐タンパー性という順で設計項目を整理し、レビュー時に見る具体的な設定名まで落とします。

対象読者はiOSとAndroidのアプリ開発者、および設計レビューを担当する立場の方です。解析や検証は自組織が管理する端末とアプリに限って行う前提とし、攻撃の再現手順や保護機構の迂回手法は扱いません。

モバイルアプリのセキュリティ設計が前提とする脅威

MASプロジェクトは、検証の深さを決める枠組みとしてMAS Testing Profilesを定義しており、そこでプロファイルごとの脅威前提が明文化されています。最も基礎的なMAS-L1でも、OSのセキュリティ機構は信頼できるものの、同じ端末で動く他のアプリは敵とみなす、という置き方をします。MAS-L2ではOSのセキュリティ機構自体を信頼できない前提(端末がroot化や脱獄されうる前提)に移り、MAS-Rではアプリの主たる利用者自身を敵とみなします。

Webアプリの感覚との違いはここに出ます。サーバー側の処理は攻撃者から見えず改変もできませんが、モバイルアプリのクライアント側処理は、条件次第で読み取られ、書き換えられ、実行時に介入されます。そのため「クライアントで検証しているから安全」という設計は成り立ちません。クライアント側の検査は攻撃の手間を上げる層であり、判断そのものはサーバー側で行います。

OWASPはこの分担を標準の側でも明示しています。MASVSはモバイルアプリ(クライアント側)のセキュリティだけを対象とし、アプリが接続するリモートエンドポイントの制御は含まないため、そちらはOWASP ASVSなど適切な標準で検証すべきだとされています。MASTGについても、リモートエンドポイントのテストは対象外とされ、WSTGの併用が示されています。

OWASPは、MASVSがモバイルアプリのクライアント側のセキュリティのみを扱い、関連するリモートエンドポイント(Webサービスなど)向けの制御を含まないため、それらはOWASP ASVSのような適切な標準に照らして検証すべきだとしています。

したがって、モバイルアプリのセキュリティ設計は「サーバー側APIの設計」と「クライアント側の設計」の二本立てになります。サーバー側の認可やレート制限や入力検証をどう組むかは別の主題として扱う必要があり、この記事はクライアント側に集中します。

OWASP MASVSとMASTGの現行構成

MASVSの最新リリースは、執筆時点で確認できる範囲ではv2.1.0(2024年1月18日公開)です。制御は8つのカテゴリに分かれ、合計24件で構成されています。カテゴリ名がそのままレビューの切り口になるため、まず全体を把握します。

カテゴリ制御数扱う範囲
MASVS-STORAGE2端末内の意図した保存と、意図しない保存や漏えい
MASVS-CRYPTO2暗号アルゴリズムの選択と鍵の管理
MASVS-AUTH3認証と認可、ローカル認証、ステップアップ認証
MASVS-NETWORK2通信路の安全な設定と、identity pinning
MASVS-PLATFORM3IPCとWebViewとUIを介したプラットフォーム連携
MASVS-CODE4プラットフォーム版数、更新の強制、依存部品、未信頼データ
MASVS-RESILIENCE4実行環境の完全性と改変耐性
MASVS-PRIVACY4利用者データの透明性と最小化

MASVSはv2.0.0で構造が変わっています。かつて標準本体に含まれていたL1とL2とRという検証レベルは、2023年の再構成でMAS Testing Profilesとして作り直され、標準本体からは外れました。v2.1.0ではMASVS-PRIVACYが加わっています。

テスト手順の側はMASTGが担います。MASTGの最新リリースはv2.0.0(2026年6月30日公開)で、MASVSの制御とテストの間にMASWE(Mobile Application Security Weakness Enumeration)という弱点カタログが挟まる構造になりました。MASWEはv1.0.0が2026年8月17日に公開され、78件の弱点で構成されています。全体としては、MASVSの制御からMASWEの弱点へ、そこからMASTGのテストとデモへとたどれる構造です。MASTG v2.0.0のリリース時点で、サイトの構成要素の一覧はテスト285件、デモ152件、テクニック167件、ツール135件、ナレッジ140件、ベストプラクティス72件を示しています。ただしテストの285件は非推奨になったv1のテストを含む総数で、GitHubのリリースノートが集計している現行のv2のテストは193件です。v1のテストはサイト上に残るものの保守されず、v2のテストが正になります。

OWASPは、MASTG v2.0.0でMASVSの制御からMASWEの弱点、MASTGのテスト、MASTGのデモへとたどれるトレーサビリティの連鎖を導入したと説明しています。

注意

運用面で影響が大きい変更として、これまで配布されていたMAS Checklistのスプレッドシートが、MASTG v2.0.0では公式リリースの成果物に含まれなくなりました。OWASPはMASウェブサイトとプロジェクトのリポジトリを正とすると告知しています。過去リリースのスプレッドシートは履歴として残るものの、版を配って手元で埋める運用は最新のテスト構成と食い違うため、参照先をサイトとリポジトリに切り替える必要があります。

なお、MASTGのテストIDはv1からv2への移行で採番が変わっており、MASWEのIDもベータ版からv1.0.0で整理されています。社内標準や過去資料に個別のテスト番号を書き込んでいる場合は、番号ではなくMASVSの制御IDを基準に紐づけ直すほうが安全です。

MAS Testing Profilesで検証の深さを決める

設計に着手する最初のステップは、どこまでの脅威を想定するかを決めることです。MAS Testing Profilesは、セキュリティ側の3つ(MAS-L1、MAS-L2、MAS-R)とプライバシー側の1つ(MAS-P)で構成されます。

プロファイル位置づけ主な脅威前提
MAS-L1基礎的なセキュリティ。全アプリのベースラインOSの機構は信頼できる。主たる利用者は敵ではない。同一端末の他アプリは敵
MAS-L2高度なセキュリティ。機微なデータや機能を扱うアプリ向けOSの機構を信頼できない。他アプリと第三者は敵。物理アクセスの有無を問わない
MAS-R改変への耐性。追加層OSを信頼できず、主たる利用者自身も敵(解析者や不正利用者)
MAS-P基礎的なプライバシー個人情報や健康や医療や金融のデータを扱うアプリ全般に推奨

OWASPが示す組み合わせの例では、ニュースアプリやカレンダーはMAS-L1とMAS-P、広告収益のある天気アプリはそこにMAS-Rを加え、メッセンジャや健康やスポーツのアプリはMAS-L2とMAS-P、銀行や保険やゲームや娯楽のアプリはMAS-L2とMAS-PとMAS-Rという置き方になっています。あわせて、すべてのプロファイルやプロファイル内の全テストに準拠することが目的ではない、とも明記されています。守る範囲を決めずに全項目を追いかけると、労力の配分を誤ります。

OWASPは、MAS-L1では同一端末上の他アプリを敵とみなし、MAS-L2ではOSのセキュリティ機構を信頼できない前提を置き、MAS-Rでは主たる利用者自身を敵とみなすと整理しています。

組織としてアプリの検証プロセスを整える場合は、NISTのSP 800-163 Rev.1「Vetting the Security of Mobile Applications」(2019年4月公開、2015年のSP 800-163を廃止)が参考になります。MAS Testing Profilesのページでも、アプリ検証プロセスの参照先として挙げられています。

もう一点、認証の扱いも押さえておきます。OWASPはベンダー中立の非営利団体としてベンダーや検証者やソフトウェアの認証を行っていないため、MASVSに関する公式の認定やトラストマークは存在しません。第三者の検証を受ける場合も、どの制御をどの深さで確認したのかという中身で判断します。

端末内のデータ保存をプラットフォームの機構に任せる

MASVS-STORAGEは、意図して保存するデータと、意図せず残ってしまうデータを分けて考えます。前者は保存先と保護属性の選択の問題、後者はログやキャッシュやバックアップやスクリーンショットへの混入の問題です。

iOSでは、ファイルの保護レベルをData Protectionのクラスが決めます。Class A(NSFileProtectionComplete)は端末ロック後に復号済みのクラス鍵が破棄され、Class B(NSFileProtectionCompleteUnlessOpen)は開いた状態のファイルの扱いを別に定め、Class C(NSFileProtectionCompleteUntilFirstUserAuthentication)は再起動後の初回ロック解除以降アクセスでき、Class D(NSFileProtectionNone)は保護を伴いません。Appleのプラットフォームセキュリティの記述では、Class Cが、明示的にクラスを指定しないサードパーティアプリのデータの既定になります。バックグラウンド処理の都合で既定のまま使うと、端末がロックされていても読み出せる状態でデータが置かれることになります。

短い秘密値はKeychainに置きます。Keychainの項目は2つのAES-256-GCM鍵(メタデータ用と秘密値用)で暗号化されてSQLiteのデータベースに格納され、秘密値の鍵の利用はSecure Enclaveを経由します。アクセス制御はsecuritydが担い、keychain-access-groupsapplication-identifierなどのエンタイトルメントを見て判断するため、項目の共有は同一開発者のアプリ間に限られます。設計で選ぶのは、項目ごとのkSecAttrAccessibleの値です。

いつアクセスできるか他端末への移行
kSecAttrAccessibleWhenUnlocked端末のロック解除中。明示しない場合の既定値暗号化バックアップ経由で移行する
kSecAttrAccessibleWhenUnlockedThisDeviceOnly端末のロック解除中他端末には移行しない
kSecAttrAccessibleAfterFirstUnlock再起動後の初回ロック解除以降暗号化バックアップ経由で移行する
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly再起動後の初回ロック解除以降他端末には移行しない
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnlyパスコード設定端末のロック解除中iCloudキーチェーンに同期されずバックアップもされない

Appleはアプリにとって意味のある範囲で最も制限の強い選択肢を使うことを勧めており、iCloudに保存されては困る機微なデータにはkSecAttrAccessibleWhenPasscodeSetThisDeviceOnlyを挙げています。この値の項目はシステムキーバッグにのみ存在し、パスコードが削除またはリセットされるとクラス鍵が破棄されて使用できなくなります。

Appleは、属性名がThisDeviceOnlyで終わる項目はバックアップを作成した同一端末には復元できるが他端末には移行しないこと、明示しない場合の既定値がkSecAttrAccessibleWhenUnlockedであることを示しています。

Androidでは、鍵の保護をAndroid Keystoreに任せます。Keystoreの鍵材料はアプリのプロセスに入らず、暗号処理はシステム側で行われるため、アプリプロセスが侵害されても鍵そのものの取り出しは防げます。鍵をTEEやSecure Elementに置けているかは、API 31で追加されたKeyInfo.getSecurityLevel()の戻り値がTRUSTED_ENVIRONMENTSTRONGBOXかで確認します。API 30以下ではAPI 23で追加されたKeyInfo.isInsideSecureHardware()を使います。このメソッドはAPI 31で非推奨になり、getSecurityLevel()に置き換えられました。StrongBoxは専用のCPUとセキュアストレージを備えた実装で、扱えるアルゴリズムがRSA 2048とAESとECDSAやECDHのP-256とHMAC-SHA256などに限られます。Googleは大半のアプリにStrongBoxは必要ないとしています。

Jetpack側の事情も変わりました。androidx.security:security-cryptoEncryptedSharedPreferencesEncryptedFileは1.1.0-alpha07(2025年4月9日)ですべてのAPIが非推奨になり、リリースノートでは既存のプラットフォームAPIとAndroid Keystoreの直接利用が代替として示されています(安定版1.1.0は2025年7月30日)。既存アプリでこのライブラリに依存している場合は、Keystoreの鍵で自前に暗号化する構成への移行を計画に入れます。なお非推奨の理由づけについて公式のリリースノートが述べているのはこの一文までで、それ以上の説明は公式には確認できません。

意図しない保存では、バックアップの扱いが落とし穴になります。Androidはandroid:dataExtractionRulesが指すルール(Android 12以上)とandroid:fullBackupContentが指すルール(Android 11以下)で除外を指定し、cloud-backupとdevice-transferの両方を書きます。iOSにはバックアップから確実に除外する仕組みがなく、NSURLIsExcludedFromBackupKeyはシステムに含めないよう指示するだけで除外を保証しません。そのため機微な値はThisDeviceOnlyのKeychain項目に置いた鍵で暗号化し、暗号文をアプリコンテナに保存する形にします。ログへの機微データ出力も同じ分類の問題で、本番ビルドで詳細ログを残さない設定を合わせて確認します。

認証の判断をサーバー側に置く設計

MASVS-AUTH-1は、認証と認可の強制はリモートエンドポイント側で行わなければならないとしたうえで、アプリ側も関連するベストプラクティスに従うことを求めています。つまりアプリは判断者ではなく、サーバーの判断を安全に運ぶ側です。トークンの保存先、セッション終了時の後始末、再認証の要求といった点がアプリ側の担当になります。

そのうえで、モバイル固有の論点がローカル認証です。生体認証や画面ロックの認証を、成功したというコールバックへの反応として実装すると、アプリの実行を制御できる状況では保護されているはずの処理を直接呼び出せてしまいます。OWASPはこれをMASWE-0020として整理し、ローカル認証はイベントへの束縛ではなく、保護対象への暗号的な束縛にする必要があるとしています。TEEやSecure Enclaveの中の鍵で復号や署名を行わせれば、コールバックだけを偽装しても暗号的な結果は作れません。

OWASPは、ローカル認証が保護対象に暗号的に結びつかずイベントに反応する検査として実装されている場合に迂回されうるとし、対策としてセキュアハードウェア内の鍵を使う復号や署名といった暗号処理を要求することを挙げています。

AndroidではBiometricPromptCryptoObjectを渡し、鍵の生成時にKeyGenParameterSpec.BuildersetUserAuthenticationRequiredを有効にします。Keystoreの鍵での暗号処理に結びつける目的で使うのはSignatureCipherMacで、CryptoObjectはこの3つをAPI 28から扱えます。framework版のandroid.hardware.biometricsとAndroidX版のandroidx.biometricは、これに加えてIdentityCredentialPresentationSessionKeyAgreementも受け取れますが、扱える型と必要なAPIレベルは両者で一致しないため、使う型はリファレンスで確認します。認証器の種別は、Class 3のBIOMETRIC_STRONG、Class 2のBIOMETRIC_WEAK、画面ロックのDEVICE_CREDENTIALがあります。OWASPのベストプラクティスは、機微な操作ではBIOMETRIC_STRONGを要求し、生体のみで確認する意図がある場合はDEVICE_CREDENTIALを許可リストに含めないことを勧めています。金融や行政や医療のような高保証のアプリでは、画面ロックへのフォールバックを常時許すと、のぞき見や説得による解除に対する耐性が下がるためです。

iOSでは、SecAccessControlCreateWithFlagsSecAccessControlを作り、Keychain項目のkSecAttrAccessControlに設定します。フラグはkSecAccessControlUserPresenceを使うと、生体とパスコードのどちらで確認するかの選択をシステムに任せられます。項目固有のパスワードを追加したい場合はkSecAccessControlApplicationPasswordを組み合わせます。

見落としが多いのが、生体情報の登録が変わったときの鍵の無効化(MASWE-0022)です。Androidで既定の無効化が効くのは、ユーザー認証を必須にしたうえで有効期間を設けず、鍵を使うたびに認証を求める鍵です。この条件の鍵は、新しい生体情報が登録されるか登録済みの生体情報がなくなると復元できない状態になります。setInvalidatedByBiometricEnrollment(false)を書いた場合と、setUserAuthenticationParametersAUTH_DEVICE_CREDENTIALを許可した場合は、この無効化は起きません。iOSは逆で、アクセス制御の作成時にbiometryCurrentSetを明示しない限り無効化されません。両プラットフォームで挙動が反対方向なので、片方の感覚でもう片方を実装すると保護が抜けます。

セッションの後始末も設計項目です。ログアウトや再認証の後に、前のセッションで取得したデータが端末内に残らないようにします。トークンの保存とセッション寿命の考え方は、Web側のセッション管理と共通する部分が多く、次の記事で機構から整理しています。

あわせて読みたい

セッション管理とセッションハイジャック対策。Cookie属性とセッションID再生成で不正利用を防ぐ設計

通信はプラットフォームの既定から組み立てる

MASVS-NETWORK-1は通信路の安全な設定を求めます。実務では、独自の実装を足すより先に、プラットフォームの既定を壊していないかを確認するほうが効果があります。

iOSのApp Transport Security(ATS)は、iOS 9.0またはmacOS 10.11以降のSDKにリンクしたアプリで既定で有効になります。ATSは既定のサーバートラスト評価に加えて、サーバー証明書がRSA 2048ビット以上またはECC 256ビット以上の鍵で署名されていること、証明書のダイジェストがSHA-2でダイジェスト長256ビット以上であること、接続がTLS 1.2以降であること、データがAES-128またはAES-256で交換されること、ECDHEによる前方秘匿性をサポートすることを求めます。

落とし穴は適用範囲です。ATSはURL Loading Systemに適用され、NetworkフレームワークやCFNetworkのような低水準のインタフェースには適用されません。低水準APIを直接使う実装では、接続の安全性の担保は実装側の責任になります。

例外はInfo.plistNSAppTransportSecurity辞書で指定します。NSAllowsArbitraryLoadsのように全体を緩めるキーではなく、NSExceptionDomains配下で対象ドメインを絞り、NSExceptionAllowsInsecureHTTPLoadsNSExceptionMinimumTLSVersionのように必要な項目だけを緩めます。Appleも、ATSの失敗に直面したらサーバー側を直すほうが常に良く、例外はアプリのセキュリティを下げるとしています。

Appleは、ATSがTLS 1.2以降とRSA 2048ビット以上またはECC 256ビット以上の鍵、SHA-2による256ビット以上のダイジェスト、AES-128またはAES-256、ECDHEによる前方秘匿性を要求すること、およびNetworkフレームワークやCFNetworkへの呼び出しには適用されないことを示しています。

Androidは、AndroidManifest.xmlandroid:networkSecurityConfigが指すNetwork Security Configurationで設定します。クリアテキスト通信はAndroid 8.1(API 27)までは既定で有効、Android 9(API 28)以降は既定で無効です。トラストアンカーの既定はプリインストールのシステムCAで、Android 6.0(API 23)以下を対象とするアプリだけが利用者追加のCAストアも既定で信頼します。開発時の例外はdebug-overridesに書きます。この要素はandroid:debuggableがtrueのときだけ信頼されるため、コード側の条件分岐で本番に紛れ込ませるより安全です。

証明書透明性(CT)と暗号化ClientHello(ECH)の扱いもドキュメントに記載があります。CTはAndroid 15(API 35)以下では利用できず、Android 16(API 36)では既定無効でオプトインでき、Android 17(API 37)以降は既定有効でオプトアウトできるとされています。ECHはAndroid 17(API 37)以降で利用でき、既定では有効です。ただしAndroidの解説は、アプリが使うネットワークライブラリ側がECHに対応していなければ、この設定は効果を持たないとしています。カスタムのトラストアンカーを使う接続ではCTの検証が行われない点も、設定を足すときに確認します。

自前でX509TrustManagerHostnameVerifierを実装して検証を省略するコードは、Androidのリスク一覧でも個別の項目として挙げられています。デバッグ目的で入れた実装がそのまま出荷される事故が起きやすいため、レビューでは実装の有無自体を確認します。

証明書ピンニングの効果と限界

MASVS-NETWORK-2は、開発者が管理するすべてのリモートエンドポイントに対してidentity pinningを行うことを求めます。一方でOWASPのPinning Cheat Sheetは、ピンすべきかという最初の問いへの答えはおそらく「決してすべきでない」であり、停止リスクがセキュリティ上の利得を上回ることがほとんどだとしています。矛盾しているようですが、対象が違います。Cheat Sheetは接続の片側しか管理しない汎用のクライアントを含めて論じたうえで、ピンしない条件として「クライアントとサーバーの両側を管理していないなら、ピンしない」「ピンセットを安全に更新できないなら、ピンしない」「ネイティブのモバイルアプリでないなら、ピンしない」を挙げています。両側を管理するネイティブアプリは、この条件を満たす側に立ちます。

効果と限界は分けて理解します。効果は、公開CAの不正発行や端末のトラストストアへのCA追加に対して、信頼する対象をアプリ運営者が管理する識別子に狭められることです。限界は、アプリを解析できる立場ならピンやピンニング処理を取り除けること、実行時のフックや動的計装を使える端末では検査を迂回できることです。OWASPもピンニングは万能ではないと明記しており、単独の防御として設計に組み込む使い方は避けます。

OWASPは、ピンニングが信頼をアプリ運営者の管理する識別子に狭め傍受や再生の手間を上げる一方で、アプリを解析できる攻撃者はピンやピンニング処理を除去でき、実行時フックや動的計装を使える攻撃者は検査を迂回できると述べています。

運用面では、更新できなくなる事故を防ぐ作りにします。Androidのpin-setには証明書のSubjectPublicKeyInfoのSHA-256ハッシュを入れ、バックアップのピンを必ず含めます。expirationを設定すると、更新されないアプリが接続不能になる事態は避けやすくなりますが、期限を過ぎるとピンが効かなくなるという引き換えがあります。iOSはNSPinnedDomainsにドメインごとの設定を書き、NSPinnedCAIdentitiesNSPinnedLeafIdentitiesのいずれか、または両方を指定します。両方を指定した場合、ATSはそれぞれのカテゴリで一致することを求めます。

Cheat Sheetは、ルートCAのピンを推奨せず、中間CAのピンはそのCAが発行した他の証明書も信頼することになると整理したうえで、リーフ証明書のピンを推奨し、必ずバックアップを用意することを求めています。証明書更新の手順とアプリ更新の配布計画をセットで決めてから導入する対策です。

プラットフォーム連携が開く攻撃面

MASVS-PLATFORMは、IPC、WebView、UIという3方向を扱います。Androidの公式ドキュメント「Mitigate security risks in your app」は、個別のリスクページをMASVSのカテゴリで分類しているため、実装の確認項目を引くときに使いやすい構成になっています。

コンポーネントの公開範囲では、android:exportedを必ず明示します。この属性の既定値はコンポーネントの種類とAPIレベルによって異なってきた経緯があり、たとえばAPIレベル16以下ではprovider要素の既定がtrueでした。Android 12(API 31)以降を対象とし、intent filterを持つactivityやserviceやbroadcast receiverでは、明示しないとビルド時のマニフェストマージで失敗し、端末にインストールできません。あわせてAndroid 12以降を対象とするアプリでは、作成するすべてのPendingIntentについて可変性を明示する必要があります。

受け取ったIntentを別のコンポーネントへ渡す実装では、intent redirectionへの対策を入れます。Androidのリスクページは、ネストされたIntentの内容を検査し、FLAG_GRANT_READ_URI_PERMISSIONFLAG_GRANT_WRITE_URI_PERMISSIONなどのURI権限フラグを確認またはクリアすること、IntentSanitizerを使うこと、resolveActivityで宛先のパッケージ名とクラス名を確かめることを挙げています。

ディープリンクは、設計上、同じURIに複数のアプリがintent filterを登録できます。Androidではandroid:autoVerify="true"を設定してApp Linksとして検証させ、https://<ホスト名>/.well-known/assetlinks.jsonを配信します。検証状態はadb shell pm get-app-links <パッケージ名>で確認でき、再検証はadb shell pm verify-app-links --re-verify <パッケージ名>で行えます。iOSのUniversal Linksでは、Associated Domainsのエンタイトルメントを設定し、拡張子なしのapple-app-site-associationhttps://<FQDN>/.well-known/配下に置きます。Appleは有効な証明書のHTTPSでリダイレクトなしに配信することを条件としており、複数のサブドメインを使う場合はサブドメインごとにエントリとファイルが必要です。カスタムURLスキームは他のアプリも同じスキームを名乗れるため、受け取った値は未信頼データとして検証します。

WebViewのネイティブブリッジは露出範囲に注意します。addJavascriptInterfaceで公開したJavaオブジェクトは、そのWebViewのすべてのフレーム(iframeを含む)から名前を参照して呼び出せますが、アプリ側には呼び出し元フレームの出自を確認する仕組みがありません。WebViewCompat.postWebMessageWebMessagePort.postMessageを使うメッセージ方式のブリッジも、origin検査を入れなければ任意の送信元からのメッセージを受け入れます。

画面と入力の保護はUI側の項目です。AndroidではFLAG_SECUREをウィンドウに設定すると、スクリーンショットと画面録画を止め、非セキュアなディスプレイやシステムのタスクスイッチャのサムネイルからも内容を隠せます。ただし効果は無条件ではありません。Androidの不正防止のドキュメントは、この方法がオーバーレイ攻撃の防止には信頼できないこと、画面録画が動作中かどうかを正しく判定できない場合があること、Android 11(API 30)以下では端末によってキーボードの入力が記録されうるため確実に効くのは7割程度の端末であることを挙げています。クリップボードは、Android 10(API 29)より前のバージョンではバックグラウンドのアプリが前面アプリのクリップボードを読めます。Android 12(API 31)以降はクリップボードのデータを読んで貼り付けるたびに利用者へトーストが表示されます。機微な値を入れる場合は、ClipboardManager.setPrimaryClip()の前にClipDescription.EXTRA_IS_SENSITIVE(API 32以下のSDKでコンパイルする場合は文字列android.content.extra.IS_SENSITIVE)を設定します。

OWASPは、ウィンドウにFLAG_SECUREを設定するとスクリーンショットの取得と画面録画を防ぎ、非セキュアなディスプレイとシステムのタスクスイッチャでも内容が隠されると説明しています。

コード品質と依存関係で確認する項目

MASVS-CODEの4つの制御は、最新のプラットフォーム版数を要求すること、アプリ更新を強制する仕組みを持つこと、既知の脆弱性を持つ部品を使わないこと、未信頼の入力を検証して無害化することです。

プラットフォーム版数は配布要件とも連動します。Google Playは、2026年8月31日以降、新規アプリとアプリの更新にAndroid 16(API 36)以上を対象とすることを求めています(Wear OSとAndroid Automotive OSはAPI 35以上、Android TVとAndroid XRはAPI 34以上)。既存アプリについても、API 35以上を対象にしていないと、アプリのtarget API levelより新しいAndroidを動かす端末の新規利用者には提供されません。延長の申請は2026年11月1日まで可能とされています。targetSdkVersionの引き上げは動作変更への追従を伴うため、セキュリティ更新の計画に組み込んで扱います。

更新の強制は、本番で重大な脆弱性が見つかったときに、古いバージョンを使い続けている利用者へ更新を届けられるかという設計です。サーバー側で最低バージョンを判定し、満たさないクライアントには機能を制限するといった作りをあらかじめ用意しておきます。

依存部品の管理は、モバイルでもサーバーサイドと同じ方法論で扱えます。SDKやライブラリの構成を把握し、既知脆弱性のスキャンを継続的に回す運用が土台です。依存関係の棚卸しとSCAの進め方は次の記事で扱っています。

あわせて読みたい

依存ライブラリ管理とSCA。SBOMで攻撃面を把握する

未信頼データの範囲は、モバイルでは想像より広くなります。MASWE-0050は、アプリが自分で作っていないデータはすべて未信頼だとしたうえで、TLS越しであってもネットワークの応答、バックアップから復元されたデータ、BluetoothやNFCやUSBのような外部インタフェース、ドキュメントピッカーやアーカイブを含むローカルのファイル、テキストフィールドやQRコードやURLやクリップボードといったUI入力、そしてIntentやbroadcastやcontent URIやディープリンクといったプラットフォームのIPCを列挙しています。

OWASPは、未信頼データを「到着経路にかかわらずアプリ自身が作成していないすべてのデータ」と定義し、TLS上のネットワーク応答やバックアップからの復元データ、外部インタフェース、ローカルファイル、UI入力、プラットフォームのIPCを例として挙げています。

実装での確認点は、SQLをパラメータ化すること、ファイルパスを正規化して意図した領域の外に出ないようにすること、アーカイブの展開でエントリ名に含まれる相対パスを検証すること、デシリアライズの対象を限定することです。あわせて、android:debuggableが本番ビルドでfalseであること、WebViewのデバッグが有効化されたまま出荷されていないこと、詳細ログが残っていないことを確認します。

耐タンパー性を追加層として扱う

MASVS-RESILIENCEは、アプリが利用者の管理する端末で動くという前提に対して、改変や解析の手間を上げる層を扱います。位置づけの理解が肝心です。OWASPはMAS-Rについて、対策がないこと自体が脆弱性を生むわけではない、MAS-L1とMAS-L2の置き換えではなく補強である、解析者は端末への完全なアクセスを持つため十分な時間と資源があれば最終的に成功するので100パーセントの有効性は保証できない、と明記しています。

root化や脱獄の検知(MASWE-0051)についても、単一の検査を1か所に置くと、そこを取り除くだけで防御全体が無効になります。OWASPは検知を層状に配置すること、検知後の応答を段階化すること(警告から機能の制限、必要に応じて終了まで)、そしてローカルの検査をサーバーで検証するアテステーションと組み合わせることを挙げています。

判定をサーバー側に置く根拠は、プラットフォーム提供元のドキュメントにも書かれています。AppleはApp Attestの解説で、侵害されたアプリは結果を偽装できるため、アプリのロジックに自分自身のセキュリティ検査をさせることはできないとしています。App Attestは、Secure Enclaveに保存された秘密鍵でアテステーションを行い、鍵が正当なアプリインスタンスに属することをAppleのサーバーが証明する仕組みで、結果の検証は開発者のサーバー側で行います。対応していない端末があるため、isSupportedで事前に確認して分岐します。

Appleは、侵害されたアプリは検査結果を偽装できるためアプリ自身のロジックにセキュリティ検査を任せられないとし、アテステーションの結果はサーバーへ送って検証すると説明しています。

AndroidではPlay Integrity APIが同じ役割を担います。アプリの操作やサーバーへのリクエストが、Google Playからインストールされた正規のアプリと、正規の認証済みAndroid端末から来ているかを確認するもので、appIntegrity(バイナリが改変されていないか)、deviceIntegrity(正規の認証済み端末か)、accountDetails(Google Playでの入手や購入)といった判定を返します。オプトインで、MEETS_STRONG_INTEGRITYappAccessRiskVerdictのような追加の判定も利用できます。いずれの仕組みでも、返ってきた判定をどう扱うかの決定はバックエンドで行います。

ここで記事全体の原則に戻ります。クライアント側の検証はサーバー側の検証を置き換えません。耐タンパー性の対策は、サーバー側の認可や入力検証やレート制限が整っていることを前提に、その上へ重ねる層として設計します。サーバー側で何を検証すべきかは次の記事で整理しています。

あわせて読みたい

APIセキュリティの基本。認証認可・レート制限・入力検証とOWASP API Top 10で守る

開発者が着手順に使う手順とレビュー項目

ここまでの内容を、着手順の作業として並べます。

  1. 1

    扱うデータと機能からMASプロファイルを決める

    アプリが扱うデータの種類と機能のリスクを整理し、MAS-L1かMAS-L2か、MAS-Pを併せるか、MAS-Rを加えるかを決めます。守る範囲を先に固定してから制御を選びます。

  2. 2

    保存先を棚卸ししてプラットフォームの機構へ移す

    端末内に書いているデータを列挙し、秘密値はKeychainとKeystoreへ、ファイルは適切な保護クラスへ移します。ログとキャッシュとバックアップへの混入もこの段階で確認します。

  3. 3

    通信の既定を確認して例外を棚卸しする

    ATSの例外キーとNetwork Security Configurationの設定を読み、緩めている箇所とその理由を洗い出します。低水準のネットワークAPIを直接使っている箇所も対象です。

  4. 4

    マニフェストとInfo.plistの露出面をレビューする

    公開しているコンポーネント、ディープリンクの登録、WebViewのブリッジ、エンタイトルメントを確認し、意図した範囲を超えて公開されていないかを見ます。

  5. 5

    認証が何に束縛されているかを確認する

    ローカル認証が暗号処理に結びついているか、生体登録の変更で鍵が無効化されるか、認可の判断がサーバー側にあるかを確認します。

  6. 6

    依存部品とビルド設定を締める

    SDKとライブラリの既知脆弱性をスキャンし、target API levelの要件を満たし、デバッグ用の設定が本番ビルドに残っていないことを確認します。

  7. 7

    耐タンパー性は最後に追加層として足す

    ここまでの層が整ってから、改変検知とアテステーションを重ねます。判定はサーバー側で行い、検知の失敗が単独で致命傷にならない設計にします。

レビュー時に実際に開いて見る項目を、設定名で並べます。

  • AndroidManifest.xmlでandroid:exportedをすべてのコンポーネントに明示しているか
  • AndroidManifest.xmlのandroid:debuggableが本番ビルドでfalseか
  • 独自CAやピンニングやドメイン単位の例外が必要な場合にandroid:networkSecurityConfigを指定し、cleartextTrafficPermittedを必要な範囲に限っているか
  • network_security_config.xmlのtrust-anchorsに不要な追加がなく、debug-overridesが本番に効かない構成か
  • pin-setを使う場合にバックアップのピンを含め、expirationの扱いを決めているか
  • android:dataExtractionRulesとandroid:fullBackupContentでcloud-backupとdevice-transferの除外を書いているか
  • ディープリンクのintent-filterにandroid:autoVerifyを設定し、pm get-app-linksで検証状態を確認したか
  • Info.plistのNSAppTransportSecurityに不要な例外がなく、例外がNSExceptionDomainsでドメイン単位に絞られているか
  • NSPinnedDomainsを使う場合にNSPinnedCAIdentitiesとNSPinnedLeafIdentitiesの指定内容を把握しているか
  • Associated Domainsのエンタイトルメントとapple-app-site-associationの配信先が対応しているか
  • Keychain項目のkSecAttrAccessibleが用途に対して最小限で、ThisDeviceOnlyの要否を判断しているか
  • 生体で保護する項目にkSecAttrAccessControlを設定し、biometryCurrentSetの要否を判断しているか
  • Keystoreの鍵でsetUserAuthenticationRequiredを有効にし、CryptoObject経由で暗号処理に束縛しているか
  • setInvalidatedByBiometricEnrollment(false)を意図せず指定していないか
  • 機微な画面にFLAG_SECUREを設定し、クリップボードにEXTRA_IS_SENSITIVEを付けているか
  • パスワード入力欄でisSecureTextEntryなど入力の保護設定を確認したか
  • WebViewのaddJavascriptInterfaceの有無と露出範囲を確認し、postMessage系でoriginを検証しているか
  • assetlinks.jsonとapple-app-site-associationが本番のHTTPSで200を返すか

到達確認は実機と実サーバーの両方で行います。App LinksとUniversal Linksは配信側の設定に依存するため、アプリ側の設定だけを見ても検証が通るかは分かりません。

OWASPのMASVSは、MASVS-STORAGE、MASVS-CRYPTO、MASVS-AUTH、MASVS-NETWORK、MASVS-PLATFORM、MASVS-CODE、MASVS-RESILIENCE、MASVS-PRIVACYの8カテゴリで構成され、v2.0.0以降は検証レベルを標準本体から分離してMAS Testing Profilesへ移しています。

日本語の参考資料としては、JSSEC(一般社団法人日本スマートフォンセキュリティ協会)の「Androidアプリのセキュア設計・セキュアコーディングガイド」があります。公開ページで確認できる最新版は2025年8月27日版で、Android 16での変更点を中心に改訂され、Safer IntentsやIntentリダイレクト攻撃への対策が加わっています。サンプルコードと記事の内容は特別な記述がない限りAndroid 7.0(API Level 24)以降が対象とされているため、設計の考え方の参照として使い、個別のAPIの挙動は公式ドキュメントで確認します。

まとめ

モバイルアプリのセキュリティ設計は、実行環境が利用者の端末にあるという前提から出発します。MASVSの8カテゴリでレビューの切り口を作り、MAS Testing Profilesで検証の深さを決め、保存と通信と認証はプラットフォームが用意した機構に寄せます。ここまでが土台です。

具体的には、秘密値はKeychainとKeystoreに置いて保護属性を最小限に絞り、通信はATSとNetwork Security Configurationの既定を壊さず例外を棚卸しし、ローカル認証はコールバックではなくセキュアハードウェアの鍵による暗号処理に束縛します。プラットフォーム連携では、公開しているコンポーネント、ディープリンク、WebViewのブリッジ、画面とクリップボードという露出面を設定単位で確認します。

そのうえで耐タンパー性は、既存の層を置き換えるものではなく追加の層として扱い、アテステーションの判定はサーバー側で行います。最後にもう一度、クライアント側の検証はサーバー側の検証を置き換えません。この一点を設計の基準線として持っておくと、モバイル固有の対策をどこまで積むかの判断がぶれにくくなります。

出典・参考

この記事をシェア

関連する記事

ペンテスト・CTF

リバースエンジニアリング入門。静的解析と動的解析の基礎を検証環境で安全に学ぶ

コンパイル済みバイナリの中身を読み解くリバースエンジニアリングの考え方を、静的解析と動的解析の違い、必要な検証環境、そして著作権法やライセンス・不正アクセス禁止法といった合法性の前提まで、実務で迷わない判断基準とあわせて入門者向けに解説します。