Flatt Security Blog

株式会社Flatt Securityの公式ブログです。プロダクト開発やプロダクトセキュリティに関する技術的な知見・トレンドを伝える記事を発信しています。

株式会社Flatt Securityの公式ブログです。
プロダクト開発やプロダクトセキュリティに関する技術的な知見・トレンドを伝える記事を発信しています。

新機構Play Integrityを使用したFirebase App Checkの検証を通して見る、スマホアプリの不正検知とBypassの「イタチごっこ」

はじめに

こんにちは。株式会社Flatt Securityエンジニアの大谷(@otani_daiki)です。

Androidのゲーム、銀行系アプリケーションを開発する上で、チート等の不正を可能にするRoot化や改ざんといった行為からアプリケーションを保護するのは必須と言えます。

そこで本稿では、どのようにアプリケーションを保護すれば良いのかを攻撃者側からの観点も踏まえて解説していきます。

また、株式会社Flatt Securityではお客様のプロダクトに脆弱性がないか専門のセキュリティエンジニアが調査するセキュリティ診断サービスを提供しています。料金に関する資料を配布中ですので、ご興味のある方は是非ご覧ください。

注) 本稿では度々サンプルコードを提示することがありますが、明示されない限り実機(Pixel 3a: Android 10)での検証を行なっております。

免責事項

本稿の内容はセキュリティに関する知見を広く共有する目的で執筆されており、脆弱性の悪用などの攻撃行為を推奨するものではありません。許可なくプロダクトに攻撃を加えると犯罪になる可能性があります。当社が記載する情報を参照・模倣して行われた行為に関して当社は一切責任を負いません。

本稿でサンプルとして提示するアプリケーションも本稿用に独自に作成したものです。

Root化とは

Root化とは従来は端末やアプリケーションの保護等の理由から解放されていない権限を、ROMの改ざん等による特殊な方法で解放することを指し、Root化するためのツールはOSSとして出回っています(後述のMagiskもその一つ)。

そして、Root化することによって攻撃者が得られるメリットは主に2つあります。

  1. Android内部のシステムファイル、アプリケーションデータへのアクセスと書き換えが可能になる
  2. Google Play Storeで提供されていないアプリケーションのインストールが可能になる

※厳密にはRoot化無しでもGoogle Play Storeで提供されていないアプリケーションのインストールは可能ですが、攻撃者の期待通りに起動・動作するかはまちまちだと思われます。

もしアプリケーションの開発者が上記の2点について対策を全くしていない場合、攻撃者によって容易にアプリケーションを丸裸にされてチート等の不正行為を許すことになります。

では、Root化や改ざんを防ぐにはどのような手段があるのでしょうか。

SafetyNet

その一つがGoogleによって提供されている「SafetyNet」、正確には「SafetyNet Attestation API」です。

SafetyNetの仕組みを簡単に説明すると、SafetyNetはアプリケーションから呼び出しを受けると実行環境を分析して、Googleのサーバーに分析結果の署名済み構成証明をリクエストします。

その後、信頼できるバックエンドサーバーで構成証明を検証することで、不正防止を検知するという仕組みです。

画像はAndroid Developersから引用

また、構成証明は以下のような形式になっており、アプリケーションのパッケージ名や署名のハッシュ値までバックエンドサーバーにて検証可能です。

{
      "timestampMs": 9860437986543,
      "nonce": "R2Rra24fVm5xa2Mg",
      "apkPackageName": "com.package.name.of.requesting.app",
      "apkCertificateDigestSha256": ["base64 encoded, SHA-256 hash of the
                                      certificate used to sign requesting app"],
      "ctsProfileMatch": true,
      "basicIntegrity": true,
}

ちなみに、ctsProfileMatchの値がtrueの場合、アプリを実行しているデバイスのプロファイルは、Android互換性テストに合格したデバイスのプロファイルと一致していることを示します。 また、basicIntegrityの値のみがtrueの場合、アプリを実行しているデバイスは、おそらく改ざんされていませんが、デバイスがAndroidの互換性テストに合格していない可能性はあることを示しています。

Firebase App Check

SafetyNetを利用してFirebase内のサービスである「Cloud Storage」「RealTime Database」「Cloud Firestore」をRoot化端末や改ざんされたアプリケーションから保護する機能が「Firebase App Check」です。

前述したSafetyNetはその特性上、信頼できるバックエンドサーバーを自分で用意して、jwt検証を行うための複雑なプログラムを用意する必要があるため、実装上のミスにより想定外の脆弱性を生んでしまう可能性もあります。

しかし、Firebase App Checkは検証を全てFirebase側で行なってくれるため簡単な実装で、アプリケーションの起動時にFirestore等と通信することでRoot化やアプリケーションの改ざん検知を行うことが可能です。

SafetyNetを利用したFirebase App Checkの検証

実際にFirebase App Checkを実装したアプリケーションを作成したので、本当にRoot化や改ざんを検知できるのかを検証します。

検証アプリケーションのデフォルトの画面は以下の画像のようになります。

KotlinでFirebase App Check(SafetyNet)を使用するためのコードは以下のように実装するだけなので、非常に簡単です。

FirebaseApp.initializeApp(/*context=*/this)
val firebaseAppCheck = FirebaseAppCheck.getInstance()
firebaseAppCheck.installAppCheckProviderFactory(
    SafetyNetAppCheckProviderFactory.getInstance()
)

実際にユーザーが利用する端末やアプリケーションの状態は主に下記の3つに分類されます。それぞれ、Firebase App Checkがどのような挙動を示すか確認していきましょう。

  • 正規の端末やアプリケーションの場合
  • Root化された端末の場合
  • 改ざんされたアプリケーションの場合

正規の端末やアプリケーションの場合は、正常にレスポンス結果が表示されているのがわかります。

そして、Root化された端末や改ざんされたアプリケーションの場合は、エラーが発生したことを通知するためのトーストが表示されました。

上記の検証結果より、App Checkが端末の状態やアプリケーションの改ざんを正常に検知しているということがわかりました。

SafetyNetの廃止とPlay Integrity

これまではSafetyNetについて解説してきましたので、SafetyNetを実装すれば良いと思う方もいると思いますが、最近になって1つの問題点が発生しました。

それが、SafetyNetの廃止です。

Googleによると、2022年6月にSafetyNetを「Play Integrity」と呼ばれるサービスに統合したようです。 そのため、2023年6月からSafetyNetは段階的に機能しなくなり、2024年6月には完全に機能しなくなるようです。

詳細はAndroid DevelopersのSafetyNetAttestationAPIの廃止をご覧ください。

Play Integrityは仕組み自体はSafetyNetとほとんど変わらないのですが、異なる点としてアプリケーションがGoogle Play Storeからインストールされたかどうかを検知します。つまり、アプリのサイドローディングが出来なくなります。

画像はAndroidDeveloperから引用

またSafetyNetからPlay Integrityへの統合に伴い、FirebaseでもPlay Integrityを使用したFirebase App Checkがリリースされました。

Play Integrityを利用したFirebase App Checkの検証

実際にPlay IntegrityのFirebase App Checkを実装したアプリケーションを作成したので、本当にRoot化や改ざんを検知できるのかを検証します。

KotlinでFirebase App Check(Play Integrity)を実装するためのコードもFirebase App Check(SafetyNet)とほとんど変わらず、呼び出すインスタンスが変わるだけです。

FirebaseApp.initializeApp(/*context=*/this)
val firebaseAppCheck = FirebaseAppCheck.getInstance()
firebaseAppCheck.installAppCheckProviderFactory(
    Play IntegrityAppCheckProviderFactory.getInstance() // only here
)

Play Integrityの場合もSafetyNetと同じく、以下の3パターンに分類して検証します。

  • 正規の端末やアプリケーションの場合
  • Root化された端末の場合
  • 改ざんされたアプリケーションの場合

正規の端末やアプリケーションの場合は、正常にレスポンス結果が表示されているのがわかります。

そして、Root化された端末の場合や改ざんされたアプリケーションの場合はSafetyNetと同じく、エラーが発生したことを通知するためのトーストが表示されました。

よって、Play Integrityの場合もFirebase App Checkが正常に機能していると言えそうです。

ここまで、SafetyNetやPlay Integrityを利用したアプリケーションの不正検知について解説してきました。

それでは、前述した不正検知の仕組みをアプリケーションに組み込めば、絶対に安全なのでしょうか?

残念ながら答えはNoで、そうした検知をBypassする方法が存在します。

Magisk

画像はMagiskのGithubから引用

Magisk」はtopjohnwu氏によって開発されたAndroid端末をRoot化するためのツール(オープンソースソフトウェア)で、システムへのプロキシ証明書のインストール等といった追加機能もモジュールとして何人ものセキュリティ開発者が作成しています。

そしてその開発されたモジュールとMagiskの機能を併用することでSafetyNetとPlay Integrityのデバイスの完全性判定をBypassすることが可能です。

Bypass検証

本稿の前半で使用したSafetyNetとPlay IntegrityのFirebase App Checkを実装した検証アプリケーションで本当にBypass出来るのかを検証します。

※ここからの検証では既にAndroid端末がMagiskを使用してRoot化されていることを前提とします。

Bypassするために、Magiskで予め設定を変更したり、モジュールをインストールしておく必要があるので説明します。

(1) Magiskの設定で「Magiskアプリを隠す」を選択して任意の名前でMagiskを置き換える

(2) ZygiskとDenyListをONにする

(3) Universal SafetyNet Fix(Zygisk version)のモジュールをMagiskにインストールする

これで準備は完了です。

SafetyNetを使用したFirebase App Checkの場合

実際に以下の2パターンの状況で処理の結果を確認してみます。

  • 前述のBypassの準備を行なった状態の場合
  • 前述のBypassの準備を行い、更にアプリケーションの改ざんもした場合

まずは前者のパターンです。デバイスはRoot化されているはずですが、Firebase App Check(SafetyNet)をBypassしてFirestoreからデータを取得することができました。Root化の検知をBypassできてしまっていますね。

しかし、アプリケーションを改ざんするとエラーが発生したことを通知するトーストが画面に表示されます。アプリケーションの改ざん検知はBypassできなかったようです。

Play Integrityを使用したFirebase App Checkの場合

Play Integrityでも以下の2パターンの状況で処理の結果を確認してみます。

  • 前述のBypassの準備を行なった状態の場合
  • 前述のBypassの準備を行い、更にアプリケーションの改ざんもした場合

前者の確認です。SafetyNetのBypass検証の時と同じように、Root化されているはずですが、Firebase App Check(Play Integrity)をBypassしてFirestoreからデータを取得することができました。やはりRoot化検知をBypassできてしまっていますね。

しかしこちらも、アプリケーションを改ざんするとSafetyNet同様にエラーが発生します。改ざん検知まではBypassできないようです。

Magiskによる検知Bypassの結論

以上の検証結果をまとめると以下のような表になります。

Root化検知Bypass 改ざん検知Bypass
SafetyNetを使用した
Firebase App Check
○ 成功 × 失敗
Play Integrityを使用した
Firebase App Check
○ 成功 × 失敗

Play Integrityは新しい機構である分、Root化検知もそう簡単にはBypassできないのではないかと少し期待していたのですが、Magiskを用いれば可能という結果になりました。

残念ながら、クライアントアプリケーションの不正検知とそのBypassは基本的にイタチごっこな側面があり、こう言ったことは珍しくありません。

Root化や改ざん検知を導入する意味

SafetyNetはともかく、新しい機能であるPlay IntegrityでさえもRoot化のBypassが出来てしまったので、導入するメリットを感じないという方も居るかもしれません。

しかし上述の結論のように、今まで紹介した方法では改ざん検知はBypass出来ませんでした。

つまり、導入によってアプリケーションの改ざんへの対策は出来ているということです。すなわちその観点においてSafetyNetやPlay Integrityに導入の価値はあると言えます。

ちなみに、ここではこれ以上解説しませんが、改ざん検知のBypassもやろうと思えば出来る可能性が高いです。厳密には前述のMagiskを用いた方法は、あくまでデバイスの完全性のチェックをBypassする方法であって、アプリケーションの改ざん検知をBypassするための方法ではないからです。

しかしそれにはかなりのスキルとReversingが必要になり、難易度が高いものとなります。なので、少なくともツールだけを用いてアプリケーションに対する不正行為を行おうとする攻撃者からは、SafetyNetやPlay Integrityを用いてアプリケーションを保護することが可能です。

まとめ

前述のように、クライアントアプリケーションの不正検知と、そのBypassは基本的にイタチごっこです。「これを導入すれば安心!」といった銀の弾丸は無いのが実情ですが、最新の保護手法にはキャッチアップしておくことをオススメします(SafetyNetに関しては廃止も決まっていますので)。

アプリケーションを保護するには追加の実装コストがかかるというデメリットもありますが、不正検知の対策が多く実装されているアプリケーションほど、攻撃者がBypassにかける時間が増加して、攻撃されにくいという大きなメリットもあります。

アプリケーションを保護するためのコストを削減せずに、不正検知をなるべく多く実装して、自分が開発したアプリケーションを守りましょう!

最後に、弊社Flatt Securityのセキュリティ診断サービスの紹介です。

Flatt Securityではセキュリティエンジニアが手動で検査を行うセキュリティ診断サービスを提供しています。スマートフォンアプリに関してはクライアント側の実装もAPI側の診断も可能ですし、AWS・GCP・Azure等パブリッククラウドをセキュアに扱えているか合わせて診断することも可能です。

診断プランは柔軟に様々な形が組めますが、上記のデータが示すように、診断は幅広いご予算帯に応じて実施が可能です。ご興味のある方向けに下記バナーより料金に関する資料もダウンロード可能です。

また、Flatt Securityはセキュリティに関する様々な発信を行っています。 最新情報を見逃さないよう、公式Twitterのフォローをぜひお願いします!

twitter.com

ここまでお読みいただきありがとうございました。

変更履歴

2022年8月1日

「Root化とは」セクションにおいて「※厳密にはRoot化無しでもGoogle Play Storeで提供されていないアプリケーションのインストールは可能ですが、攻撃者の期待通りに起動・動作するかはまちまちだと思われます。」の文章を追記しました。