Slide 1

Slide 1 text

Flutterで実装する実践的な攻撃対策と セキュリティ向上 FlutterKaigi 2025 株式会社サイバーエージェント Fuji Kinaga © 2025 WinTicket Inc.

Slide 2

Slide 2 text

株式会社WinTicket Mobile Application Engineer Fuji Kinaga 職歴 2020年 株式会社サイバーエージェント 中途入社 株式会社AbemaTVでAndroidエンジニア 株式会社WinTicketでFlutterエンジニア 最近 アプリチームのマネージャー サーバーチーム バックエンド勉強中です 趣味 睡眠, 盆栽, レコード, 働いた後の家系ラーメン

Slide 3

Slide 3 text

© 2025 WinTicket Inc.

Slide 4

Slide 4 text

© 2025 WinTicket Inc.

Slide 5

Slide 5 text

日本一技術で事業価値を作る アプリチーム

Slide 6

Slide 6 text

日本一技術で事業価値を作るには? 専門性 技術や事業の課題 を解決できる腕力 FlutterKaigi登壇 週3リリース 収益性 可用性の高い サービス品質 自動テスト 誰でも見れるSLI/SLO 操作性 ユーザーインサイト を捉えたUI/UX コキャマン100本ノック

Slide 7

Slide 7 text

ベッティングというとtoCのエンタメに興味があったり、ビジョンに共感できる方ぜひ

Slide 8

Slide 8 text

アジェンダ まえがき 本セッションで取り扱う機能の紹介 不正の脅威モデルと対策方法の解説 サービス特性上の不正の実例 デバイスから取得するデータの偽造 サーバーリクエストの改ざん まとめ

Slide 9

Slide 9 text

免責事項 セキュリティの発表となるため、不正の手法 について触れる場面があります 攻撃を推奨するわけではなく、対策について の知見を共有することが目的です 絶対に悪用はしないようにしてください

Slide 10

Slide 10 text

注意喚起 本セッションではより具体的な不正対策につ いて触れるため、サービス内に存在する機能 も実例として取り扱います 今回紹介する不正対策は実際にサービス内で 取り扱っているものを含みますが、対策の中 身全てを解説したものではありません 絶対に攻撃の目的でサービス利用はしないで ください

Slide 11

Slide 11 text

まえがき 本セッションではサンプルコードによる解説 はあまり登場しません 実際の活用時は公式の導入ガイドを参照する ようにしてください 全体像や抽象的な解説が多くなってしまうこ とをご容赦ください

Slide 12

Slide 12 text

チェックイン機能 競輪場に来場してくれたユーザーさんにポイント で還元 競輪は映像よりも生で見た方が楽しい! 作るだけでなく、不正対策が重要になる

Slide 13

Slide 13 text

“As it turns out, mobile security is all about data protection” 結局のところ、モバイルセキュリティとはデータ保護がすべてである OWASP MASVS 序文

Slide 14

Slide 14 text

“It depends on the particular client-side threats one aims to defend against.” レジリエンスは、防御対象となるクライアント側の具体的な脅威によって異なる OWASP MASVS 序文

Slide 15

Slide 15 text

今回の不正の脅威モデリング 1日で2日分のポイント獲得 23:55にチェックインして24:05に 再度チェックインする 1回来場するだけで2回分のポイン トを獲得する 位置情報の偽造 チェックインイベント開催中にデ バイスの位置情報を偽造 競輪場にいたことにしてポイント を獲得する サーバーリクエストの改ざん チェックイン処理のコードや通信 を解析して差し替えてリクエスト 競輪場付近の位置情報を投げてポ イントを獲得する

Slide 16

Slide 16 text

1日で2日分のポイント獲得 16:00 来場

Slide 17

Slide 17 text

1日で2日分のポイント獲得 16:30 チェックイン

Slide 18

Slide 18 text

1日で2日分のポイント獲得 19:30 観戦

Slide 19

Slide 19 text

1日で2日分のポイント獲得 23:30 カラオケ

Slide 20

Slide 20 text

1日で2日分のポイント獲得 24:30 再来場

Slide 21

Slide 21 text

1日で2日分のポイント獲得 24:30 チェックイン

Slide 22

Slide 22 text

1日で2日分のポイント獲得の対策 チェックイン可能時間を定める 競輪場の開場時刻 ~ 最終レースが終了する時刻

Slide 23

Slide 23 text

位置情報の偽造 デバイスから取得できる位置情報を 偽造してアプリケーションに返却する チェックイン開催地にいるように振る舞う

Slide 24

Slide 24 text

geolocatorを使いつつ 位置情報を偽造してみる pub.dev/packages/geolocator

Slide 25

Slide 25 text

Androidの位置情報偽造の手段 位置情報偽造ツールの使用 位置情報モックのアプリを選択。そこから注入された位置情報に差し 替わり、本当の位置情報のようにデバイス側で扱われる 開発者オプション → 仮の現在地情報アプリを選択 から選ぶ PC接続型のツールやアプリインストール型のツールがある デバイスからの応答を差し替え アプリケーションにルートアクセスを提供(Root化)したり、動的解析 ツールを使用して、フレームワークやロケーションプロバイダに介入 偽の位置情報を返すようにフックする 理屈上は可能 引用: https://jp.imyfone.com/ 引用: https://github.com/topjohnwu/Magisk, https://frida.re/

Slide 26

Slide 26 text

iOSの位置情報偽造の手段 位置情報偽造ツールの使用 位置情報をデバイス上のソフトウェアシミュレーションを使用して生成 する手法 PC接続型のツールやVPN接続を駆使したアプリ型のツールがある デバイスからの応答を差し替え 脱獄(Jailbreak)したり、動的解析ツールを使用して、Core Locationに 介入 OSセキュリティレベルの向上により年々脱獄は難しくなっている 偽の位置情報を返すようにフックする 引用: https://jp.imyfone.com/ 引用: https://github.com/JJTech0130/TrollRestore, https://frida.re/

Slide 27

Slide 27 text

Androidではどういう位置情報のレスポンスになるか? latitude: 35.xxx longitude: 139.xxx timestamp: YYYY-MM-DD HH:mm:ss.SSSZ accuracy: 15.555000305175781
 altitude: 100.80000305175781
 altitudeAccuracy: 0.6149576306343079
 heading: 217.95425415039062
 headingAccuracy: 45.0
 speed: 0.32402801513671875
 speedAccuracy: 1.5
 isMocked: false 偽造時 isMockedがtrueになる 一部の値が常に固定で返却される 偽造には開発者オプションの設定が必須 latitude: 36.xxx longitude: 138.xxx
 timestamp: YYYY-MM-DD HH:mm:ss.SSSZ
 accuracy: 2.0
 altitude: 18.337348515379652
 altitudeAccuracy: 0.0
 heading: 1.0
 headingAccuracy: 0.0
 speed: 0.0
 speedAccuracy: 0.0
 isMocked: true 正常時 isMockedでは以下を参照 API31以降 → Location.isMock API31未満 → isFromMockProvider

Slide 28

Slide 28 text

Androidではどういう位置情報のレスポンスになるか? latitude: 35.xxx longitude: 139.xxx timestamp: YYYY-MM-DD HH:mm:ss.SSSZ accuracy: 15.555000305175781
 altitude: 100.80000305175781
 altitudeAccuracy: 0.6149576306343079
 heading: 217.95425415039062
 headingAccuracy: 45.0
 speed: 0.32402801513671875
 speedAccuracy: 1.5
 isMocked: false 偽造時 isMockedがtrueになる 一部の値が常に固定で返却される 偽造には開発者オプションの設定が必須 latitude: 36.xxx longitude: 138.xxx
 timestamp: YYYY-MM-DD HH:mm:ss.SSSZ
 altitude: 18.337348515379652
 accuracy: 2.0
 altitudeAccuracy: 0.0
 heading: 1.0
 headingAccuracy: 0.0
 speed: 0.0
 speedAccuracy: 0.0
 isMocked: true 正常時 isMockedでは以下を参照 API31以降 → Location.isMock API31未満 → isFromMockProvider

Slide 29

Slide 29 text

iOSではどういう位置情報のレスポンスになるか? latitude: 35.xxx longitude: 139.xxx timestamp: YYYY-MM-DD HH:mm:ss.SSSZ accuracy: 8.991563588862155 altitude: 23.232012915556 altitudeAccuracy: 30.0 heading: -1.0 headingAccuracy: -1.0 speed: 0.0 speedAccuracy: 0.0 isMocked: false 偽造時 isMockedがtrueになる 一部の値が常に固定で返却される 正常時 isMocked以下を参照 iOS15以降 → Core Location#isSimulatedBySoftware iOS15未満 → 常にfalse latitude: 36.xxx longitude: 138.xxx
 timestamp: YYYY-MM-DD HH:mm:ss.SSSZ
 accuracy: 5.0
 altitude: 0.0
 altitudeAccuracy: 0.0
 heading: -1.0
 headingAccuracy: -1.0
 speed: 0.0
 speedAccuracy: 0.0
 isMocked: true 偽造解除時(端末再起動前) isMockedがfalseになる accuracyが大きい値で返却される latitude: 35.xxx longitude: 139.xxx timestamp: YYYY-MM-DD HH:mm:ss.SSSZ
 accuracy: 1414.0
 altitude: 0.0
 altitudeAccuracy: 0.0
 heading: -1.0
 headingAccuracy: -1.0
 speed: 0.0
 speedAccuracy: 0.0
 isMocked: false

Slide 30

Slide 30 text

iOSではどういう位置情報のレスポンスになるか? latitude: 35.xxx longitude: 139.xxx timestamp: YYYY-MM-DD HH:mm:ss.SSSZ accuracy: 8.991563588862155 altitude: 23.232012915556 altitudeAccuracy: 30.0 heading: -1.0 headingAccuracy: -1.0 speed: 0.0 speedAccuracy: 0.0 isMocked: false 偽造時 isMockedがtrueになる 一部の値が常に固定で返却される 正常時 isMocked以下を参照 iOS15以降 → Core Location#isSimulatedBySoftware iOS15未満 → 常にfalse latitude: 36.xxx longitude: 138.xxx
 timestamp: YYYY-MM-DD HH:mm:ss.SSSZ
 heading: -1.0
 headingAccuracy: -1.0
 speed: 0.0
 speedAccuracy: 0.0
 accuracy: 5.0
 altitude: 0.0
 altitudeAccuracy: 0.0
 isMocked: true 偽造解除時(端末再起動前) isMockedがfalseになる accuracyが大きい値で返却される latitude: 35.xxx longitude: 139.xxx timestamp: YYYY-MM-DD HH:mm:ss.SSSZ
 accuracy: 1414.0
 altitude: 0.0
 altitudeAccuracy: 0.0
 heading: -1.0
 headingAccuracy: -1.0
 speed: 0.0
 speedAccuracy: 0.0
 isMocked: false

Slide 31

Slide 31 text

iOSではどういう位置情報のレスポンスになるか? latitude: 35.xxx longitude: 139.xxx timestamp: YYYY-MM-DD HH:mm:ss.SSSZ accuracy: 8.991563588862155 altitude: 23.232012915556 altitudeAccuracy: 30.0 heading: -1.0 headingAccuracy: -1.0 speed: 0.0 speedAccuracy: 0.0 isMocked: false 偽造時 isMockedがtrueになる 一部の値が常に固定で返却される 正常時 isMocked以下を参照 iOS15以降 → Core Location#isSimulatedBySoftware iOS15未満 → 常にfalse latitude: 36.xxx longitude: 138.xxx
 timestamp: YYYY-MM-DD HH:mm:ss.SSSZ
 heading: -1.0
 headingAccuracy: -1.0
 speed: 0.0
 speedAccuracy: 0.0
 accuracy: 5.0
 altitude: 0.0
 altitudeAccuracy: 0.0
 isMocked: true 偽造解除時(端末再起動前) isMockedがfalseになる accuracyが大きい値で返却される latitude: 35.xxx longitude: 139.xxx timestamp: YYYY-MM-DD HH:mm:ss.SSSZ
 heading: -1.0
 headingAccuracy: -1.0
 speed: 0.0
 speedAccuracy: 0.0
 accuracy: 1414.0
 altitude: 0.0
 altitudeAccuracy: 0.0
 isMocked: false

Slide 32

Slide 32 text

どう防ぐか Androidの対策 偽造ツール使用の検知は、isMockedがtrueかを見ればOK isMockedの値をフックして返されると偽造のチェックが 通ってしまうリスクがある iOSの対策 偽造ツール使用の検知は、isMockedがtrueかを見るだけで は不十分 ただ、常に固定で送られるフィールドの前後比較ができれば 検知は可能かもしれない isMockedの値をフックして返されると偽造のチェックが 通ってしまうリスクがある アプリやデバイスの改ざんの対策が必要 偽造ツールの検知だけでなく、アプリやデバイスの不正利 用でも突破されづらくする多層防御を検討する必要がある

Slide 33

Slide 33 text

Root化や脱獄、動的解析 の検知方法はあるのか? pub.dev/packages/root_jailbreak_sniffer

Slide 34

Slide 34 text

root_jailbreak_sniffer Rjsniffer.amICompromised() Android Root化や危険アプリのインストール、Magisk使用の有 無などで判定 iOS IOSSecuritySuiteのライブラリとブリッジ Rjsniffer.amIEmulator() Android エミュレーターによる起動の判定 iOS IOSSecuritySuiteのライブラリとブリッジ Rjsniffer.amIDebugged() Android 開発者オプションの有効可否やfrifaの接続可否で判定 iOS IOSSecuritySuiteのライブラリとブリッジ

Slide 35

Slide 35 text

位置情報の偽造まとめ isMockedと特定の値の変化を確認しよう 偽造ツールを使用した位置情報の偽造はレスポンスの特定 の値を見ることでiOS/Androidともに大部分は検知可能 Root化や脱獄、動的解析も検知しよう デバイスの状態を元に不正の疑いを検知して利用を制限する ことでセキュリティリスクを下げられる flutterで利用可能なroot_jailbreak_snifferなどがある それでも対策は完全ではない それぞれの考慮をすれば不正が0になるということはない あくまで多層的にリスクを減らしているに過ぎない ポイントの不正取得の観点においては、リクエストの中間者 攻撃や改ざんしたアプリで利用されるとこれらの対策では 不十分

Slide 36

Slide 36 text

サーバーリクエストの改ざん 実際の重要な処理を解析され、 本物のリクエストのように改ざんする チェックイン開催地にいるように振る舞う

Slide 37

Slide 37 text

リクエスト改ざんの例 中間者攻撃(MITM攻撃) 攻撃者がユーザーと利用サービス の間に割り込んで、受け渡すデー タを盗聴したり、改ざんしたりす る リクエストを覗かれて処理の詳細 を把握されてしまう リプレイ攻撃 ユーザーとサーバー間でやり取り された認証情報や通信データを傍 受し、そのデータを再送信する 正しく考慮できていないと高負荷 につながったり、複数回有効な処 理を通してしまう 逆コンパイル ソースコードを解析し、特定の処 理についての情報を得る 正しいリクエストを生成されて有 効な処理として判定されてしまう

Slide 38

Slide 38 text

対策の基礎 通信の暗号化 HTTPS/TLS AES/RSA 秘匿情報を持たない/出力しない ソースコードに置かない デバイスストレージに保存しない ログに出力しない コードの難読化 --obfuscate --split-debug-info iOS/Androidネイティブ側も忘れずに 最新OSやSDKに追従する 同時に低いバージョンを使い続けないこ とも大事 ストアコンソール機能でも対策できます ex. Play App Signing, Cloud- managed certificates, Xcode Cloud

Slide 39

Slide 39 text

重要なリクエストを守る さらに アプリの完全性が担保されている デバイスの完全性が担保されている アプリからリクエスト送信されている ことを目指したい

Slide 40

Slide 40 text

iOSとAndroidの完全性の判定方法 Play Integrity API Playサーバーと連携し、アプリ・デバイス・ユーザーの完全性を担保する Google Play によってインストールされたか 正規の認定 Android デバイスで実行されているか 正規のアプリから送信されているか App Attest / Device Check Appleサーバーと連携し、完全性を担保する Device Check 主にデバイス、部分的にアプリの完全性を検証する App Attest 正規のiOSデバイスで実行されているか 正規のアプリから送信されているか ペイロードに改変が加えられていないか 引用: https://jp.imyfone.com/ 引用: https://www.apple.com/privacy/

Slide 41

Slide 41 text

Play Integrity APIの特徴 ② チャレンジ用文字列を生成 自社サーバー ① チャレンジリクエストを送信

Slide 42

Slide 42 text

Play Integrity APIの特徴 ③ チャレンジ用文字列を返却 自社サーバー

Slide 43

Slide 43 text

Play Integrity APIの特徴 ④ Tokenを要求 with ハッシュ化したチャレンジ用文字列

Slide 44

Slide 44 text

Play Integrity APIの特徴 ⑤ Tokenリクエストを生成 ⑥ Tokenリクエストを送信 Google Play Service

Slide 45

Slide 45 text

Play Integrity APIの特徴 ⑧ Tokenを返却 ⑦ Tokenを生成 Google Play Service

Slide 46

Slide 46 text

Play Integrity APIの特徴 ⑨ チェックインリクエスト with Tokenを送信 10. Tokenを検証 自社サーバー Googleサーバー Google Play Service

Slide 47

Slide 47 text

Play Integrity APIの特徴 自社サーバー Googleサーバー Google Play Service 11. チェックイン処理 12. チェックイン処理結果を返却

Slide 48

Slide 48 text

Play Integrity API リクエスト種別 標準リクエスト standardTokenProviderを事前に取得し、メモリに保持しておく 部分的な構成証明情報をキャッシュしておくことで、整合性判定のリク エストのレイテンシを削減することができる。 トークンに端末情報やリクエスト情報を含めてPlay(Google) Server側 で判定しており、 Play(Google) Server側でリプレイ攻撃も防止できる クラシックリクエスト 標準リクエストに比べて作成コストが高い サーバーローカルで復号化して検証する選択肢がある 標準リクエストは Googleサーバーでの復号しか対応していない こっちを選択すると実装を正しく行う必要があり、開発負荷増 引用: https://developer.android.com/google/play/integrity/classic Figure.1 引用: https://developer.android.com/google/play/integrity/standard Figure.1

Slide 49

Slide 49 text

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 { { , , }, { , , [ ], }, { [ ] }, { } } "requestDetails" "requestPackageName" "timestampMillis" "nonce" "requestHash" "appIntegrity" "appRecognitionVerdict" "packageName" "certificateSha256Digest" "versionCode" "deviceIntegrity" "deviceRecognitionVerdict" "accountDetails" "appLicensingVerdict" : : : : : : : : : : : : : : "com.package.name" "1617893780" "xxxxx" "xxxxx" "PLAY_RECOGNIZED" "com.package.name" "6a6a1474b5cbbb2b1aa57e0bc3" "42" "MEETS_DEVICE_INTEGRITY" "LICENSED" Token payloadの中身

Slide 50

Slide 50 text

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 { { , , }, { , , [ ], }, { [ ] }, { } } "requestDetails" "requestPackageName" "timestampMillis" "nonce" "requestHash" "appIntegrity" "appRecognitionVerdict" "packageName" "certificateSha256Digest" "versionCode" "deviceIntegrity" "deviceRecognitionVerdict" "accountDetails" "appLicensingVerdict" : : : : : : : : : : : : : : "com.package.name" "1617893780" "xxxxx" "xxxxx" "PLAY_RECOGNIZED" "com.package.name" "6a6a1474b5cbbb2b1aa57e0bc3" "42" "MEETS_DEVICE_INTEGRITY" "LICENSED" requestDetails アプリからリクエストしたデータとPlayサーバー から返却されたデータが一致しているかを確認す るためのフィールド リクエスト形式で異なる 標準リクエスト: requestHash クラシックリクエスト: nonce

Slide 51

Slide 51 text

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 { { , , }, { , , [ ], }, { [ ] }, { } } "requestDetails" "requestPackageName" "timestampMillis" "nonce" "requestHash" "appIntegrity" "appRecognitionVerdict" "packageName" "certificateSha256Digest" "versionCode" "deviceIntegrity" "deviceRecognitionVerdict" "accountDetails" "appLicensingVerdict" : : : : : : : : : : : : : : "com.package.name" "1617893780" "xxxxx" "xxxxx" "PLAY_RECOGNIZED" "com.package.name" "6a6a1474b5cbbb2b1aa57e0bc3" "42" "MEETS_DEVICE_INTEGRITY" "LICENSED" appIntegrity アプリの完全性を検証するためのフィールド PLAY_RECOGNIZED: アプリと証明書が GooglePlayから配布されているバージョンと一 致 UNRECOGNIZED_VERSION: GooglePlayの記録 と一致しない UNEVALUATED: 評価の要件を満たせなかった (デバイスの完全性が不十分など)

Slide 52

Slide 52 text

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 { { , , }, { , , [ ], }, { [ ] }, { } } "requestDetails" "requestPackageName" "timestampMillis" "nonce" "requestHash" "appIntegrity" "appRecognitionVerdict" "packageName" "certificateSha256Digest" "versionCode" "deviceIntegrity" "deviceRecognitionVerdict" "accountDetails" "appLicensingVerdict" : : : : : : : : : : : : : : "com.package.name" "1617893780" "xxxxx" "xxxxx" "PLAY_RECOGNIZED" "com.package.name" "6a6a1474b5cbbb2b1aa57e0bc3" "42" "MEETS_DEVICE_INTEGRITY" "LICENSED" deviceIntegrity デバイスの完全性を検証するためのフィールド MEETS_DEVICE_INTEGRITY: 正規の認証済み Android端末で動作している 空: フック実行やRoot化、エミュレータの疑い がある PlayConsole上でオプショナルなチェックをオプト インできる

Slide 53

Slide 53 text

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 { { , , }, { , , [ ], }, { [ , , ] }, { } } "requestDetails" "requestPackageName" "timestampMillis" "nonce" "requestHash" "appIntegrity" "appRecognitionVerdict" "packageName" "certificateSha256Digest" "versionCode" "deviceIntegrity" "deviceRecognitionVerdict" "accountDetails" "appLicensingVerdict" : : : : : : : : : : : : : : "com.package.name" "1617893780" "xxxxx" "xxxxx" "PLAY_RECOGNIZED" "com.package.name" "6a6a1474b5cbbb2b1aa57e0bc3" "42" "MEETS_DEVICE_INTEGRITY" "MEETS_BASIC_INTEGRITY" "MEETS_STRONG_INTEGRITY" "LICENSED" (optional) deviceIntegrity デバイスの完全性を検証するためのフィールド MEETS_BASIC_INTEGRITY: ブートローダーの ロック可否やブート状態の検証状態に関わら ず、基本的なシステム整合性チェックを通過し たかどうか MEETS_STRONG_INTEGRITY: 最新のセキュリ ティアップデートが適用された正規の認証済み Androidデバイスかどうか

Slide 54

Slide 54 text

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 { { , , }, { , , [ ], }, { [ ] }, { } } "requestDetails" "requestPackageName" "timestampMillis" "nonce" "requestHash" "appIntegrity" "appRecognitionVerdict" "packageName" "certificateSha256Digest" "versionCode" "deviceIntegrity" "deviceRecognitionVerdict" "accountDetails" "appLicensingVerdict" : : : : : : : : : : : : : : "com.package.name" "1617893780" "xxxxx" "xxxxx" "PLAY_RECOGNIZED" "com.package.name" "6a6a1474b5cbbb2b1aa57e0bc3" "42" "MEETS_DEVICE_INTEGRITY" "LICENSED" accountDetails デバイスにログインしているユーザーアカウントに おけるアプリのGoogle Playライセンスステータス を表すフィールド LICENSED: デバイス上のGoogle Playからアプ リをインストールまたはアップデートした UNLICENSED: アプリをサイドロードしたり、 Google Play外からのインストールの疑いがあ る UNEVALUATED: 評価の要件を満たせなかった

Slide 55

Slide 55 text

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 { { { }, { } }, { , { [ , , ] } } } "deviceIntegrity" "recentDeviceActivity" "deviceActivityLevel" "deviceAttributes" "sdkVersion" "environmentDetails" "playProtectVerdict" "appAccessRiskVerdict" "appsDetected" : : : : : : : : : "LEVEL_3" "NO_ISSUES" "KNOWN_INSTALLED" "UNKNOWN_INSTALLED" "UNKNOWN_CAPTURING" 32 そのほかのoptional recentDeviceActivity: 過去1時間に特定のデバ イスでアプリが整合性トークンを要求した回数 deviceAttributes.sdkVersion: デバイスのバー ジョン番号。要件を満たしてない場合、返却さ れない playProtectVerdict: Google Play Protectの有 効可否 appAccessRiskVerdict: 画面のキャプチャや オーバーレイの表示、デバイスの制御に使用で きる他アプリが実行中かどうかなど ※deviceRecallはまだβ版で情報が少なく割愛

Slide 56

Slide 56 text

Remediation dialogs ※AndroidプラットフォームAPI呼び出しが必要 ①PlayStoreへの誘導 いずれかの状態時に表示 appLicensingVerdict: ”UNLICENSED” appRecognitionVerdict: ”UNRECOGNIZED_VERSION” ②不正アプリを閉じるよう促す appsDetectedが以下のいずれか ”KNOWN_CAPTURING” “KNOWN_CONTROLLING” ”UNKNOWN_CAPTURING” ”UNKNOWN_CONTROLLING” ③デバイスの整合性の判定 ①に加えてdeviceRecognitionVerdictに以下が 含まれない ”MEETS_DEVICE_INTEGRITY” ”MEETS_STRONG_INTEGRITY” または修復可能なクライアント例外が発生した 場合 引用: https://developer.android.com/google/play/integrity/remediation

Slide 57

Slide 57 text

Play Integrityは上限あり 1日あたり10000件のリクエスト制限 Tokenリクエストと復号それぞれ 標準リクエストとクラシックリクエストで共有 上限引き上げのリクエストは可能 割り当て設定アラートを入れておくとQuota消 費を検知できる https://support.google.com/googleplay/android-developer/contact/piaqr

Slide 58

Slide 58 text

開発時の考慮 Play Consoleの特定のメールリストを指定して Integrity API結果の差し替えが可能 テスト中や開発環境ではレスポンスを成功に差 し替えておく PlayConsoleでのアプリ登録が必要 PlayConsoleで管理される署名証明書情報を 比較するため packageNameの異なるDevアプリで検証し たい場合など

Slide 59

Slide 59 text

アプリとデバイスの完全性 Play Integrity API Playサーバーと連携し、アプリ・デバイス・ユーザーの完全性を担保する Google Play によってインストールされたか 正規の認定 Android デバイスで実行されているか 正規のアプリから送信されているか App Attest / Device Check Appleサーバーと連携し、完全性を担保する Device Check 主にデバイス、部分的にアプリの完全性を検証する App Attest 正規のiOSデバイスで実行されているか 正規のアプリから送信されているか ペイロードに改変が加えられていないか 引用: https://jp.imyfone.com/ 引用: https://www.apple.com/privacy/

Slide 60

Slide 60 text

iOSで使える機能の紹介 Device Check デバイスが持つ情報と開発者アカウントの情報に紐づく識別子を生成 Appleアカウントやアプリケーションの情報を見ない 2ビット(true, false)からなる二つの識別子をデバイス×開発者アカウン トの分だけ Appleサーバーで管理する ex. {0, 1} + timestamp 今後使われるケースは減っていくと予想されるので詳細の解説は割愛 App Attest App AttestAPI呼び出し時に取得したKeyとデバイスが持つ SecureEnclaveに保存された秘密鍵のキーペア、Appleサーバー間でデ バイス・アプリの完全性を保証する SecureEnclaveはICレベルのプロセッサやストレージがメインから隔離 されており、侵害されてもユーザーのデータを安全に守る サーバーに送る際は構成証明を取得しておいたキーペアで署名する 構成証明の中にアプリに関する身元のハッシュデータを入れている 引用: https://developer.apple.com/jp/videos/play/wwdc2021/10244/

Slide 61

Slide 61 text

App Attestの特徴 自社サーバー Appleサーバー Secure Enclave Appleサーバー

Slide 62

Slide 62 text

1 2 3 4 5 6 7 8 9 10 11 12 // attestation { fmt 'apple-appattest', attStmt { x5c [ , ], receipt }, authData } : : : : : 30 82 02 30 82 02 36 30 80 06 09 21 00 1 2 3 4 5 // assertion { signature , authenticatorData } : : 30 45 02 20 21 00 Token payloadの中身 (実際はJSONではないので注意)

Slide 63

Slide 63 text

最初は段階公開を推奨 Integrity APIとは異なり、リクエスト数に関 する上限の明示はなし App Attestは内部的にDevice Checkフレー ムワークがAppleサーバーにアクセスする 急にリクエストが増えると過負荷状態を回避 するように振る舞う場合がある

Slide 64

Slide 64 text

【おさらい】 アプリとデバイスの完全性 Play Integrity API Playサーバーと連携し、アプリ・デバイス・ユーザーの完全性を担保する Google Play によってインストールされたか 正規の認定 Android デバイスで実行されているか 正規のアプリから送信されているか App Attest / Device Check Appleサーバーと連携し、完全性を担保する Device Check 主にデバイス、部分的にアプリの完全性を検証する App Attest 正規のiOSデバイスで実行されているか 正規のアプリから送信されているか ペイロードに改変が加えられていないか 引用: https://jp.imyfone.com/ 引用: https://www.apple.com/privacy/

Slide 65

Slide 65 text

Firebase App Check 引用: https://firebase.google.com/products/app-check

Slide 66

Slide 66 text

Firebase App Check 各PFの認証プロバイダーをサポート Apple: DeviceCheck または App Attest Android: Play Integrity API Web: reCAPTCHA Enterprise サードパーティの認証プロバイダや独自サービスも接続可 Firebaseサービスと連携 バックエンドリソースへのアクセスを伴うサービスで利用可 能 ex. Firebase Authentication, Cloud Firestore, Cloud Storage etc PFごとに対応しているSDKが豊富 iOS, Android, Web, Flutter, Unity, C++

Slide 67

Slide 67 text

App Checkを使うことのメリット 導入と運用の簡素化 Integrity APIや App Attestを自前 で使う場合に比べて、プラット フォームAPIの呼び出しのコードを 書かなくて済む Token発行や検証部分の具体実装 がプロバイダ単位で差し替え可能 になる Firebaseサービスとの親和性 コンソールで設定するだけで正規 アプリからのリクエスト以外を防 止できる Token管理の簡素化 TokenのTTL設定・ローテーション リプレイ攻撃への対策 デバッグキー管理 nonce生成, 署名検証 クライアントはgetTokenを呼ぶだ けでいい サーバーはverifyTokenを呼ぶだけ でいい

Slide 68

Slide 68 text

App Checkの特徴

Slide 69

Slide 69 text

App Checkの特徴 ① App Check Tokenを要求

Slide 70

Slide 70 text

App Checkの特徴 ② Integrity API Token生成チャレンジリクエストを生成 ③ チャレンジリクエストを送信 AppCheckサーバー

Slide 71

Slide 71 text

App Checkの特徴 ④ チャレンジ処理を実行 ⑤ 結果とTTLを返却 AppCheckサーバー

Slide 72

Slide 72 text

App Checkの特徴 ⑥ Integrity API Tokenリクエストを生成&送信 Google Play Service ⑦ Integrity API Tokenを返却

Slide 73

Slide 73 text

App Checkの特徴 ⑧ App Check Token変換リクエストを生成 ⑨ 交換リクエストを送信 AppCheckサーバー

Slide 74

Slide 74 text

App Checkの特徴 10 交換処理を実行 11 App Check TokenとTTLを返却 AppCheckサーバー

Slide 75

Slide 75 text

App Checkの特徴 12 App Check Tokenを返却 AppCheckサーバー

Slide 76

Slide 76 text

App Checkの特徴 13 チェックインリクエスト with App Check Tokenを送信 14 App CheckTokenを検証 自社サーバー AppCheckサーバー AppCheckサーバー

Slide 77

Slide 77 text

App Checkの特徴 自社サーバー AppCheckサーバー AppCheckサーバー 16 チェックイン処理結果を返却 15 チェックイン処理

Slide 78

Slide 78 text

App Checkを使う際の留意点 完全性検証に特化 Integrity APIをフル活用したい 場合は直接の利用が必要 単一障害点になる SDKがデバイス側のOSアップ デートに依存しやすい 動作検証が大変 不具合にも遭遇しやすい 実運用にのせる場合、バイパ ス機構の用意も検討が必要 サービス障害時 SDKの不具合 クラシックリクエスト Integrity APIのプロバイダでは クラシックリクエストが利用 されている Token発行のレイテンシが 大きい 本来Integrity API Tokenの キャッシュは避けるべき App Checkトークンの形で 端末に永続化して再利用し てしまっている Quota消費が早くなる Integrity APIは 1日10000件 App Checkは内部にトークン のTTLを持っており、定期的に ローテーションされる ex. TTL1hなら30mで 想定より早く枯渇する可能性 がある

Slide 79

Slide 79 text

サーバーリクエスト改ざんまとめ データ保護のための基礎対策を怠らない 暗号化や難読化、秘匿情報の管理は早い段階で見直しをし てみよう OSやライブラリのバージョン更新、日々頑張っていく デバイスやアプリの完全性を担保しよう Play Integrity APIやApp Attestを使って検証ができる Flutterでの利用の場合は特に実装が大変 Firebase App Checkを使えばかなり手軽に導入できる それでも不正とのイタチごっこ ポイントの不正取得の観点においては、かなり不正されづら くなったはず ただ、これまでに紹介した機能やサービスも完全に不正を 検知できるとは限らない 仰々しいセキュリティ対策だけでなく、サービス特性や機能 の特徴に応じた不正対策も重要

Slide 80

Slide 80 text

【おさらい】 今回の不正の脅威モデリング 1日で2日分のポイント獲得 23:55にチェックインして24:05に 再度チェックインする 1回来場するだけで2回分のポイン トを獲得する 位置情報の偽造 チェックインイベント開催中にデ バイスの位置情報を偽造 競輪場にいたことにしてポイント を獲得する サーバーリクエストの改ざん チェックイン処理のコードや通信 を解析して差し替えてリクエスト 競輪場付近の位置情報を投げてポ イントを獲得する

Slide 81

Slide 81 text

今回の不正の脅威モデリングの対策まとめ 1日で2日分のポイント獲得 想定される正常なユーザーさんの ユースケースを元にして機能仕様 のアップデートを検討した 位置情報の偽造 位置情報のモックに関してはロ ケーションライブラリから返却さ れるisMockedの値を確認するよう にした 位置情報が外部から差し替えられ る不正のケースを判定し、エラー に倒すようにした サーバーリクエストの改ざん セキュリティにおける基礎対策を 施し、処理の詳細が漏れづらくし た アプリやデバイスの完全性を保証 する仕組みを導入し、正常性判定 に活用するようにした

Slide 82

Slide 82 text

まとめ セキュリティの重要性 セキュリティは一発アウトのリス クや事業的な収益にも影響するリ スクを孕んでいる 多層防御で考える 複数の対策を組み合わせることで 攻撃者側のコストメリットを下 げ、不正リスクを下げられる 予防しすぎないこと 色々と大変になる 動作テストが難しくなる 処理が複雑になる 障害点が増え、バグリスクも上 がる リスクに対して必要十分な対策を 今回紹介したものは比較的コスパ がいいものなので、サービス特性 や重要度に合わせてぜひ検討して みてください!

Slide 83

Slide 83 text

Special Thanks OWASP Mobile Application Security (MAS) モバイルアプリセキュリティの業界標準を定義 セキュリティ標準(OWASP MASVS) 弱点リスト(OWASP MASWE) 包括的なテストガイド(OWASP MASTG) Android Developers公式 ドキュメントがとても丁寧 一般的なセキュリティ知識についても触れられている セキュリティチェックリストもあるので一読をおすすめします 引用: https://mas.owasp.org/ 引用: https://developer.android.com/

Slide 84

Slide 84 text

参考文献 https://blog.flatt.tech/entry/testing_play_integrity https://developers.google.com/android/reference/com/google/android/gms/location https://nishikiout.net/entry/2023/05/06/021252 https://developer.android.com/google/play/integrity/remediation https://android-developers-jp.googleblog.com/2022/08/boost-security-of-your-app-with-nonce.html https://developer.apple.com/jp/videos/play/wwdc2021/10244/ https://qiita.com/owen/items/85dff1e45083d2805140 https://mas.owasp.org/ https://developer.android.com/privacy-and-security/security-tips https://firebase.google.com/docs/app-check https://jp.imyfone.com/ios-location-changer/ https://developer.apple.com/documentation/devicecheck/establishing-your-app-s-integrity https://medium.com/flutter-minds