Zenn
🌟

このリクエスト、私のアプリが送ってますね。Approov SDK と Cloudflare API Shield で簡単に保護を強化。

2025/03/23に公開

はじめに

モバイルアプリ向け API エンドポイントのセキュリティを高める場合、不正なリクエストを成功させるためのコスト・ハードルを高く保つことが、第一歩になると思います。

この記事では Approov が提供するモバイルアプリ向けセキュリティ App Attestation と Cloudflare の API Shield を組み合わせることで、悪性 Bot に対してより高い壁として立ちはだかれるか、試します。
https://approov.io/mobile-app-security/rasp/app-attestation/

要点

  • Approov SDK を組み込まれたモバイルアプリは、置かれた環境や状態に関する自分のシグナルを Approov に報告
  • Approov はポリシーに基づき検査・通知
  • API Shield はその結果を利用し、API 保護ルールの精度、突破の難度を向上
デフォルトのポリシー(いろいろ取れそ)

policy

$ approov policy -get
security policy is "default,default,default" meaning:
  rules: default (Default security rules)
  rejection: default (Reject all rooted/jailbroken devices and emulators/simulators/cloned multiapps)
  annotation: default (No device properties provided)
device property settings:
         adb-enabled                         Android device has adb access enabled
  reject app-not-registered                  App is not registered, which may indicate that it is fake or tampered
  reject app-sign-certs-invalid              iOS app has not been signed using a valid certificate chain
         appattest-appid-fail                iOS device has completed an AppAttest attestation but app is not registered
         appattest-apple-err                 Indicates a problem using the Apple fraud lookup endpoint, possibly due to an invalid AuthKey
         appattest-assert-performed          iOS device has just performed an AppAttest assertion
         appattest-attest-performed          iOS device has just performed an AppAttest attestation
         appattest-completed                 iOS device has completed (now or previously) an AppAttest attestation
  reject appattest-failed                    iOS device has failed an AppAttest attestation or assertion, or app is unregistered or fraud risk exceeds threshold
         appattest-high-risk                 iOS device has completed an AppAttest attestation but fraud risk exceeds threshold
         appattest-unavailable               iOS AppAttest was attempted but it was unavailable
         attack-tools                        Indicates that there is evidence of attacking tools installed on the device
         attests-per-minute-limit            Account limit for attestations per minute has been exceeded
         attests-per-month-limit             Account limit for attestations per month has been exceeded
         automated                           Evidence that the operation of an app is being automated in some way
         automated-launch                    Android app is being launched in some automated way rather than from the standard launcher
  reject bad-hmac                            HMAC integrity check failure, indicating tampering or using iOS bitcode without bitcode registration option
  reject cloned                              Android app is running in the environment of a cloning app and sandbox security may be compromised
  reject cycript                             iOS app running on a device with the cyript framework installed
  reject cydia                               iOS app running on a device with the cydia framework installed
  reject debug                               App is currently being debugged
         dev-settings-enabled                Android device has developer settings enabled
  reject device-changed                      Device ID is being tampered with
         device-state-err                    Indicates a problem communicating the device state
         devicecheck-apple-err               Indicates a problem using the Apple endpoint, possibly due to an invalid AuthKey
  reject devicecheck-ban                     iOS device has been permanently banned using DeviceCheck
         devicecheck-completed               iOS device has completed (now or previously) a DeviceCheck
  reject devicecheck-failed                  iOS device has generated an invalid device token during a DeviceCheck
         devicecheck-performed               iOS device has just performed a DeviceCheck
         devicecheck-unavailable             iOS DeviceCheck was attempted but it was unavailable
         devices-per-month-limit             Account limit for devices per month has been exceeded
  reject did-ban                             Device ID has been banned
  reject edxposed                            Android app has the EdXposed framework installed
  reject emulator                            App is running on an Android emulator (such as QEMU)
         filtered                            Device attributes have matched one or more specified filters
  reject filtered-reject                     Device attributes have matched a filter set to cause rejection
  reject force-fail                          App has been forced to fail using the CLI forcefail options
         force-pass                          App has been forced to pass using the CLI forcepass options
  reject frida                               App running on a device that has the Frida framework installed
  reject frida-hook                          App has some functions being actively hooked by Frida to try and evade detection
         game-guardian                       App running on a device that has the GameGuardian hacking tool installed
         installer-not-play-store            Android app was installed by something other than the Play Store
  reject ios-app-on-mac                      App is running on an ARM based Mac
  reject ios-simulator                       App is running on the iOS simulator
  reject ip-tampered                         Attempt to tamper with the IP address being presented to the Approov cloud service
         is-development                      Indicates that the app is a development version
  reject jailbroken                          iOS device that the app is running on has been jailbroken
         kernelsu                            Android device is rooted with KernelSU and SuperUser is active for the app
         libs-inline                         Android app is using libraries as embedded uncompressed in the host APK
         load-throttled                      Attestation has been skipped because of attester load-throttling
  reject magisk                              Android Magisk root manager is installed
  reject memory-tampered                     Memory layout of the device has been modified in a suspicious manner
         new-install                         App is a new installation and is being used for the first time
         no-package-query                    Indicates Android app does not have permission to query all packages, so some detections are not possible
         non-standard-launch                 Android app launched in non-standard way, which may be a custom launcher or automation
  reject pinning-tampered                    Continuous or specific URL probe testing indicates device pinning has been compromised
         playintegrity-completed             Android device has completed (now or previously) Play Integrity attestation
         playintegrity-fail-app-cert-hash    Android device has failed Play Integrity because the app certificate hash is incorrect
         playintegrity-fail-app-integrity    Android device has failed Play Integrity because of the app integrity verdict
         playintegrity-fail-app-licensing    Android device has failed Play Integrity because of the app licensing verdict
         playintegrity-fail-device-integrity Android device has failed Play Integrity because of the device integrity verdict
  reject playintegrity-failed                Android device has failed the Play Integrity check
         playintegrity-google-err            Indicates a transient problem with the Google Play Integrity API, possibly due to bad credentials or exceeded rate limit
         playintegrity-need-cpn              Android Play Integrity was attempted and failed and may need a Google cloud project number set
         playintegrity-performed             Android device has just performed a Play Integrity attestation
         playintegrity-unavailable           Android Play Integrity was attempted but it was unavailable
  reject rate-limit                          Device ID is making excessive requests
         reported                            Device ID has been reported as having a potential token misuse
         reported-pending                    First attestation by a Device ID after being reported as having a potential token misuse
  reject riru                                Android app has the Riru hooking framework injected
         risky-device                        Device is considered to be in a risky category and is being issued with shorter lifetime tokens
  reject root-risk                           Device may be rooted but this cannot be categorically proven
  reject root-without-xposed                 Device is rooted and does not have the Xposed framework installed
  reject rooted                              Android device has been rooted
         safetynet-completed                 SafetyNet check has been completed (now or previously) on the device
  reject safetynet-fail-cert                 SafetyNet check fails to correctly match the app signing certificates provided
  reject safetynet-fail-integrity            SafetyNet check fails the basic device integrity criteria
         safetynet-fail-profile              SafetyNet check fails Google compatibility testing
  reject safetynet-fail-token                SafetyNet check failed because the token provided was illegal
         safetynet-performed                 SafetyNet check was just performed
         safetynet-unavailable               SafetyNet was attempted but it was not available
  reject scan-fail                           One or more scans of the app status have failed
  reject tampered                            One or more runtime integrity checks of the app have failed
  reject xposed                              Android app has the Xposed framework installed
  reject xposed-unsafe                       Android app has the Xposed framework installed with a known hacking module
  reject zygisk                              Android app has the Magisk Zygisk framework injected and is active

フロー

通信フローの例です。

テスト準備

決めておくこと

ドメイン名

  • Cloudflare でリバースプロキシする API エンドポイント

Approov

  1. サインアップ
    • 30日のフリートライアルあり
  2. Approov CLI のインストール
  3. CLI から Init
    approov init <account_id> <a time limited onboarding code>
    
    セットアップで使ったその他コマンド

    CLI Reference

    # アカウント確認
    approov whoami
    # ロール切り替え
    eval `approov role admin`
    eval `approov role dev`
    # パスワードの再発行(ロールごと)   
    approov password -sendCode
    

Mac

  1. Xcode インストール
  2. Xcode プロジェクト作成
    • URLSession で API エンドポイントに接続させるコード
  3. API エンドポイントにアクセスして正常に応答(JSON)が来ることを確認
    • Approov は仕込み前
    • API Shield や WAF は許可ルール

Cloudflare

API Shield (Enterprise Plan with API Gateway)の有効化

テスト

Mac

Xcode

  1. Approov の Mobile App Quickstarts から iOS Swift URLSession をプロジェクトのパッケージに追加


  2. コードに追加

    • サンプルに従う
    • セッションを置換する数行の更新のみ
    sample
    import ApproovURLSession
    :
    func initializeApproov() {
         do {
             try ApproovService.initialize(config: "メールできたコンフィグ")
         } catch {
             print("Failed to initialize Approov: \(error)")
         }
     }
     
     func fetchData() {
         guard let url = URL(string: "https://APIエンドポイント/") else {
             resultText = "Invalid URL"
             return
         }
         
         // Replace URLSession with ApproovURLSession
         let session = ApproovURLSession(configuration: URLSessionConfiguration.default)
     :
    

Approov CLI

  1. (オプション)iOS シミュレータで試験できるようにする
    approov forcepass -addDevice latest
    
    関連コマンド

    リストの確認

    approov forcepass -listDevices
    

    もとに戻す

    approov forcepass -removeDevice <device_ID>
    

    強制拒否にする

    approov forcefail -addDevice <device_ID>
    
  2. 鍵セットの作成 keyset
    approov keyset -kid <鍵の名> -add <方式>
    
  3. API エンドポイントドメインの追加と鍵セットの紐付け api
    approov api -add <ドメイン名> -keySetKID <鍵の名>
    
  4. 鍵セットのエクスポート
    • API Shield の設定に必要
    approov keyset -getJWKS <ファイル名>
    

Cloudflare

API Shield で Approov からのシグナルをもとにリクエストを検査します。
具体的には HTTP リクエストヘッダー approov-token有無・トークンの有効性を検証します。

  1. バリデーションの設定
    • Headerapproov-token を指定
    • Token Key にエクスポートした検証用の鍵セット情報を入力
  2. ルールの設定
    • API エンドポイントとバリデーションを関連付け
    • アクションを選択(ブロック)

通信確認

正常なリクエスト

シミュレータからリクエストを発行すると、API Sheild で許可され、オリジン API エンドポイントから応答が来ました。
デフォルトでオリジンにも approov-token ヘッダーが伝わるので、 Cloudflare Workers をオリジンとし、トークンをデコードした結果を戻してみます。

接続元デバイスのグローバル IP アドレスなど、トークンに含まれる各 Claim が表示されています。
実機で試すと変わってくると想像します。

不正なリクエスト

シミュレータ以外からリクエストをしてみます。
Approov CLI でテスト用のトークン(有効・無効両方)を発行できるのでそれを使います。

トークン作成

token

$ INVALID=`approov token -genExample code.oymk.work -type invalid |awk /^e/|sed -e 's/\n//'`
$ echo $INVALID | ~/bin/jwtdecode 
{
  "alg": "ES384",
  "kid": "aprv1",
  "typ": "JWT"
}
{
  "exp": 1742698652,
  "ip": "1.2.3.4",
  "did": "ExampleApproovTokenDID=="
}

$ VALID=`approov token -genExample code.oymk.work |awk /^e/|sed -e 's/\n//'`
$ echo $VALID | ~/bin/jwtdecode
{
  "alg": "ES384",
  "kid": "aprv1",
  "typ": "JWT"
}
{
  "exp": 1742698811,
  "ip": "1.2.3.4",
  "did": "ExampleApproovTokenDID=="
}
  1. トークンなし
    • API Shield がブロック
$ http -h https://code.oymk.work/
HTTP/1.1 403 Forbidden
  1. 無効なトークンで接続
    • API Shield がブロック
$ http -h https://code.oymk.work/ approov-token:$INVALID
HTTP/1.1 403 Forbidden
  1. 有効なトークンで接続
    • API Shield が許可、オリジンから応答が来る
$ http -h https://code.oymk.work/ approov-token:$VALID
HTTP/1.1 200 OK

$ http -b https://code.oymk.work/ approov-token:$VALID
{
    "message": "Token is valid",
    "tokenData": {
        "did": "ExampleApproovTokenDID==",
        "exp": 1742698811,
        "ip": "1.2.3.4"
    },
    "valid": true
}

メトリクスの観測・オブサバビリティ

結果を観測するツールを見てみます。

Approov

アカウント用の GraphQL UI を開く CLI コマンドが用意されています。

approov metrics

叩くと URL が開きます。

たとえば、Live: Mobile App Attestations を見ると Reject の理由など、統計を確認できます。

Cloudflare

Security Analytics確認することができます。

API エンドポイント Host Path を指定し、どのセキュリティルールが効いているか Mitigation service で分類すると、API Shield - Token Validation でブロックした不正な API リクエストが浮かび上がります。

APIログ探索など、他にも検知する方法・場所はあります。
自動化など、用途に応じて活用可能です。

まとめ

以上、モバイルアプリ向け API エンドポイントの保護に Approov と Cloudflare の連携がとても役立ちそうことがわかりました。

Approov

Docs
Video
Blog

関連記事

Cloudflare API Shield を JWT 門番にする

Discussion

ログインするとコメントできます