このリクエスト、私のアプリが送ってますね。Approov SDK と Cloudflare API Shield で簡単に保護を強化。
はじめに
モバイルアプリ向け API エンドポイントのセキュリティを高める場合、不正なリクエストを成功させるためのコスト・ハードルを高く保つことが、第一歩になると思います。
この記事では Approov が提供するモバイルアプリ向けセキュリティ App Attestation と Cloudflare の API Shield を組み合わせることで、悪性 Bot に対してより高い壁として立ちはだかれるか、試します。
要点
- Approov SDK を組み込まれたモバイルアプリは、置かれた環境や状態に関する自分のシグナルを Approov に報告
- Approov はポリシーに基づき検査・通知
- API Shield はその結果を利用し、API 保護ルールの精度、突破の難度を向上
デフォルトのポリシー(いろいろ取れそ)
$ 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
-
サインアップ
- 30日のフリートライアルあり
- Approov CLI のインストール
- Mac なので Brew を利用
- CLI から Init
approov init <account_id> <a time limited onboarding code>
セットアップで使ったその他コマンド
# アカウント確認 approov whoami # ロール切り替え eval `approov role admin` eval `approov role dev` # パスワードの再発行(ロールごと) approov password -sendCode
Mac
- Xcode インストール
- Xcode プロジェクト作成
- URLSession で API エンドポイントに接続させるコード
- API エンドポイントにアクセスして正常に応答(JSON)が来ることを確認
- Approov は仕込み前
- API Shield や WAF は許可ルール
Cloudflare
API Shield (Enterprise Plan with API Gateway)の有効化
テスト
Mac
Xcode
-
Approov の Mobile App Quickstarts から iOS Swift URLSession をプロジェクトのパッケージに追加
-
コードに追加
- サンプルに従う
- セッションを置換する数行の更新のみ
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
- (オプション)iOS シミュレータで試験できるようにする
approov forcepass -addDevice latest
関連コマンド
リストの確認
approov forcepass -listDevices
もとに戻す
approov forcepass -removeDevice <device_ID>
強制拒否にする
approov forcefail -addDevice <device_ID>
- 鍵セットの作成 keyset
approov keyset -kid <鍵の名> -add <方式>
- API エンドポイントドメインの追加と鍵セットの紐付け api
approov api -add <ドメイン名> -keySetKID <鍵の名>
- 鍵セットのエクスポート
- API Shield の設定に必要
approov keyset -getJWKS <ファイル名>
Cloudflare
API Shield で Approov からのシグナルをもとにリクエストを検査します。
具体的には HTTP リクエストヘッダー approov-token
の有無・トークンの有効性を検証します。
-
バリデーションの設定
-
Header
でapproov-token
を指定 -
Token Key
にエクスポートした検証用の鍵セット情報を入力
-
-
ルールの設定
- API エンドポイントとバリデーションを関連付け
- アクションを選択(ブロック)
通信確認
正常なリクエスト
シミュレータからリクエストを発行すると、API Sheild で許可され、オリジン API エンドポイントから応答が来ました。
デフォルトでオリジンにも approov-token
ヘッダーが伝わるので、 Cloudflare Workers をオリジンとし、トークンをデコードした結果を戻してみます。
接続元デバイスのグローバル IP アドレスなど、トークンに含まれる各 Claim が表示されています。
実機で試すと変わってくると想像します。
不正なリクエスト
シミュレータ以外からリクエストをしてみます。
Approov CLI でテスト用のトークン(有効・無効両方)を発行できるのでそれを使います。
トークン作成
$ 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=="
}
- トークンなし
- API Shield がブロック
$ http -h https://code.oymk.work/
HTTP/1.1 403 Forbidden
- 無効なトークンで接続
- API Shield がブロック
$ http -h https://code.oymk.work/ approov-token:$INVALID
HTTP/1.1 403 Forbidden
- 有効なトークンで接続
- 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 の連携がとても役立ちそうことがわかりました。
Discussion