Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Flutterで実装する実践的な攻撃対策とセキュリティ向上
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
FujiKinaga
November 13, 2025
Technology
1.3k
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Flutterで実装する実践的な攻撃対策とセキュリティ向上
Flutter Kaigi 2025
FujiKinaga
November 13, 2025
More Decks by FujiKinaga
See All by FujiKinaga
最新のCompose Multiplatform を使うとiOSとAndroidアプリはどれくらい作れるのか
fujikinaga
2
710
Androidのテストの理解を深めてみた
fujikinaga
0
74
開発案件の進み方
fujikinaga
0
120
深いい勉強会 vol.10
fujikinaga
0
100
深いい勉強会 vol.9
fujikinaga
0
120
Understanding Dagger2 Part1
fujikinaga
0
69
Mater of Subscription
fujikinaga
0
85
深いい勉強会
fujikinaga
2
88
深いい勉強会 The Navigation Component
fujikinaga
0
78
Other Decks in Technology
See All in Technology
AIを「創る」と「使う」の循環 — HRテックが実践するリアルなAI組織実装
taketo957
0
1.9k
手塩にかけりゃいいってもんじゃない
ming_ayami
0
130
Snowflakeと仲良くなる第一歩
coco_se
4
370
Oracle AI Database@AWS:サービス概要のご紹介
oracle4engineer
PRO
4
2.9k
SIer20年! 培ったスキルがスタートアップで輝く時
shucho0103
0
810
[モダンアプリ勉強会]今更聞けないGit/GitHub入門
tsukuboshi
0
330
MCP Appsを作ってみよう
iwamot
PRO
4
370
AI活用を推進するために ファインディが下した、一つの小さな決断
starfish719
0
290
FDE という解 ― 暗黙知と明示知をつなぐ、伴走型エンジニアリング ―
otanet
0
110
AAIFに入ってみた ~内から見えるコミュニティ動向~
sato4
0
120
ChatworkとBPaaS 異なる特性で学んだAI機能開発の ベストプラクティス
kubell_hr
2
3.4k
LLMと共に進化するプロセスを目指して
ymatsuwitter
12
3.8k
Featured
See All Featured
Test your architecture with Archunit
thirion
1
2.3k
The State of eCommerce SEO: How to Win in Today's Products SERPs - #SEOweek
aleyda
2
11k
Digital Ethics as a Driver of Design Innovation
axbom
PRO
1
310
Building the Perfect Custom Keyboard
takai
2
790
Technical Leadership for Architectural Decision Making
baasie
3
400
Dealing with People You Can't Stand - Big Design 2015
cassininazir
367
27k
The Illustrated Guide to Node.js - THAT Conference 2024
reverentgeek
1
380
Tell your own story through comics
letsgokoyo
1
950
Utilizing Notion as your number one productivity tool
mfonobong
4
320
Visual Storytelling: How to be a Superhuman Communicator
reverentgeek
2
550
How to build an LLM SEO readiness audit: a practical framework
nmsamuel
1
770
The Illustrated Children's Guide to Kubernetes
chrisshort
51
52k
Transcript
Flutterで実装する実践的な攻撃対策と セキュリティ向上 FlutterKaigi 2025 株式会社サイバーエージェント Fuji Kinaga © 2025 WinTicket
Inc.
株式会社WinTicket Mobile Application Engineer Fuji Kinaga 職歴 2020年 株式会社サイバーエージェント 中途入社
株式会社AbemaTVでAndroidエンジニア 株式会社WinTicketでFlutterエンジニア 最近 アプリチームのマネージャー サーバーチーム バックエンド勉強中です 趣味 睡眠, 盆栽, レコード, 働いた後の家系ラーメン
© 2025 WinTicket Inc.
© 2025 WinTicket Inc.
日本一技術で事業価値を作る アプリチーム
日本一技術で事業価値を作るには? 専門性 技術や事業の課題 を解決できる腕力 FlutterKaigi登壇 週3リリース 収益性 可用性の高い サービス品質 自動テスト
誰でも見れるSLI/SLO 操作性 ユーザーインサイト を捉えたUI/UX コキャマン100本ノック
ベッティングというとtoCのエンタメに興味があったり、ビジョンに共感できる方ぜひ
アジェンダ まえがき 本セッションで取り扱う機能の紹介 不正の脅威モデルと対策方法の解説 サービス特性上の不正の実例 デバイスから取得するデータの偽造 サーバーリクエストの改ざん まとめ
免責事項 セキュリティの発表となるため、不正の手法 について触れる場面があります 攻撃を推奨するわけではなく、対策について の知見を共有することが目的です 絶対に悪用はしないようにしてください
注意喚起 本セッションではより具体的な不正対策につ いて触れるため、サービス内に存在する機能 も実例として取り扱います 今回紹介する不正対策は実際にサービス内で 取り扱っているものを含みますが、対策の中 身全てを解説したものではありません 絶対に攻撃の目的でサービス利用はしないで ください
まえがき 本セッションではサンプルコードによる解説 はあまり登場しません 実際の活用時は公式の導入ガイドを参照する ようにしてください 全体像や抽象的な解説が多くなってしまうこ とをご容赦ください
チェックイン機能 競輪場に来場してくれたユーザーさんにポイント で還元 競輪は映像よりも生で見た方が楽しい! 作るだけでなく、不正対策が重要になる
“As it turns out, mobile security is all about data
protection” 結局のところ、モバイルセキュリティとはデータ保護がすべてである OWASP MASVS 序文
“It depends on the particular client-side threats one aims to
defend against.” レジリエンスは、防御対象となるクライアント側の具体的な脅威によって異なる OWASP MASVS 序文
今回の不正の脅威モデリング 1日で2日分のポイント獲得 23:55にチェックインして24:05に 再度チェックインする 1回来場するだけで2回分のポイン トを獲得する 位置情報の偽造 チェックインイベント開催中にデ バイスの位置情報を偽造 競輪場にいたことにしてポイント
を獲得する サーバーリクエストの改ざん チェックイン処理のコードや通信 を解析して差し替えてリクエスト 競輪場付近の位置情報を投げてポ イントを獲得する
1日で2日分のポイント獲得 16:00 来場
1日で2日分のポイント獲得 16:30 チェックイン
1日で2日分のポイント獲得 19:30 観戦
1日で2日分のポイント獲得 23:30 カラオケ
1日で2日分のポイント獲得 24:30 再来場
1日で2日分のポイント獲得 24:30 チェックイン
1日で2日分のポイント獲得の対策 チェックイン可能時間を定める 競輪場の開場時刻 ~ 最終レースが終了する時刻
位置情報の偽造 デバイスから取得できる位置情報を 偽造してアプリケーションに返却する チェックイン開催地にいるように振る舞う
geolocatorを使いつつ 位置情報を偽造してみる pub.dev/packages/geolocator
Androidの位置情報偽造の手段 位置情報偽造ツールの使用 位置情報モックのアプリを選択。そこから注入された位置情報に差し 替わり、本当の位置情報のようにデバイス側で扱われる 開発者オプション → 仮の現在地情報アプリを選択 から選ぶ PC接続型のツールやアプリインストール型のツールがある デバイスからの応答を差し替え
アプリケーションにルートアクセスを提供(Root化)したり、動的解析 ツールを使用して、フレームワークやロケーションプロバイダに介入 偽の位置情報を返すようにフックする 理屈上は可能 引用: https://jp.imyfone.com/ 引用: https://github.com/topjohnwu/Magisk, https://frida.re/
iOSの位置情報偽造の手段 位置情報偽造ツールの使用 位置情報をデバイス上のソフトウェアシミュレーションを使用して生成 する手法 PC接続型のツールやVPN接続を駆使したアプリ型のツールがある デバイスからの応答を差し替え 脱獄(Jailbreak)したり、動的解析ツールを使用して、Core Locationに 介入 OSセキュリティレベルの向上により年々脱獄は難しくなっている
偽の位置情報を返すようにフックする 引用: https://jp.imyfone.com/ 引用: https://github.com/JJTech0130/TrollRestore, https://frida.re/
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
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
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
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
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
どう防ぐか Androidの対策 偽造ツール使用の検知は、isMockedがtrueかを見ればOK isMockedの値をフックして返されると偽造のチェックが 通ってしまうリスクがある iOSの対策 偽造ツール使用の検知は、isMockedがtrueかを見るだけで は不十分 ただ、常に固定で送られるフィールドの前後比較ができれば 検知は可能かもしれない
isMockedの値をフックして返されると偽造のチェックが 通ってしまうリスクがある アプリやデバイスの改ざんの対策が必要 偽造ツールの検知だけでなく、アプリやデバイスの不正利 用でも突破されづらくする多層防御を検討する必要がある
Root化や脱獄、動的解析 の検知方法はあるのか? pub.dev/packages/root_jailbreak_sniffer
root_jailbreak_sniffer Rjsniffer.amICompromised() Android Root化や危険アプリのインストール、Magisk使用の有 無などで判定 iOS IOSSecuritySuiteのライブラリとブリッジ Rjsniffer.amIEmulator() Android エミュレーターによる起動の判定
iOS IOSSecuritySuiteのライブラリとブリッジ Rjsniffer.amIDebugged() Android 開発者オプションの有効可否やfrifaの接続可否で判定 iOS IOSSecuritySuiteのライブラリとブリッジ
位置情報の偽造まとめ isMockedと特定の値の変化を確認しよう 偽造ツールを使用した位置情報の偽造はレスポンスの特定 の値を見ることでiOS/Androidともに大部分は検知可能 Root化や脱獄、動的解析も検知しよう デバイスの状態を元に不正の疑いを検知して利用を制限する ことでセキュリティリスクを下げられる flutterで利用可能なroot_jailbreak_snifferなどがある それでも対策は完全ではない それぞれの考慮をすれば不正が0になるということはない
あくまで多層的にリスクを減らしているに過ぎない ポイントの不正取得の観点においては、リクエストの中間者 攻撃や改ざんしたアプリで利用されるとこれらの対策では 不十分
サーバーリクエストの改ざん 実際の重要な処理を解析され、 本物のリクエストのように改ざんする チェックイン開催地にいるように振る舞う
リクエスト改ざんの例 中間者攻撃(MITM攻撃) 攻撃者がユーザーと利用サービス の間に割り込んで、受け渡すデー タを盗聴したり、改ざんしたりす る リクエストを覗かれて処理の詳細 を把握されてしまう リプレイ攻撃 ユーザーとサーバー間でやり取り
された認証情報や通信データを傍 受し、そのデータを再送信する 正しく考慮できていないと高負荷 につながったり、複数回有効な処 理を通してしまう 逆コンパイル ソースコードを解析し、特定の処 理についての情報を得る 正しいリクエストを生成されて有 効な処理として判定されてしまう
対策の基礎 通信の暗号化 HTTPS/TLS AES/RSA 秘匿情報を持たない/出力しない ソースコードに置かない デバイスストレージに保存しない ログに出力しない コードの難読化 --obfuscate
--split-debug-info iOS/Androidネイティブ側も忘れずに 最新OSやSDKに追従する 同時に低いバージョンを使い続けないこ とも大事 ストアコンソール機能でも対策できます ex. Play App Signing, Cloud- managed certificates, Xcode Cloud
重要なリクエストを守る さらに アプリの完全性が担保されている デバイスの完全性が担保されている アプリからリクエスト送信されている ことを目指したい
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/
Play Integrity APIの特徴 ② チャレンジ用文字列を生成 自社サーバー ① チャレンジリクエストを送信
Play Integrity APIの特徴 ③ チャレンジ用文字列を返却 自社サーバー
Play Integrity APIの特徴 ④ Tokenを要求 with ハッシュ化したチャレンジ用文字列
Play Integrity APIの特徴 ⑤ Tokenリクエストを生成 ⑥ Tokenリクエストを送信 Google Play Service
Play Integrity APIの特徴 ⑧ Tokenを返却 ⑦ Tokenを生成 Google Play Service
Play Integrity APIの特徴 ⑨ チェックインリクエスト with Tokenを送信 10. Tokenを検証 自社サーバー
Googleサーバー Google Play Service
Play Integrity APIの特徴 自社サーバー Googleサーバー Google Play Service 11. チェックイン処理
12. チェックイン処理結果を返却
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
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の中身
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
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: 評価の要件を満たせなかった (デバイスの完全性が不十分など)
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上でオプショナルなチェックをオプト インできる
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デバイスかどうか
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: 評価の要件を満たせなかった
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はまだβ版で情報が少なく割愛
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
Play Integrityは上限あり 1日あたり10000件のリクエスト制限 Tokenリクエストと復号それぞれ 標準リクエストとクラシックリクエストで共有 上限引き上げのリクエストは可能 割り当て設定アラートを入れておくとQuota消 費を検知できる https://support.google.com/googleplay/android-developer/contact/piaqr
開発時の考慮 Play Consoleの特定のメールリストを指定して Integrity API結果の差し替えが可能 テスト中や開発環境ではレスポンスを成功に差 し替えておく PlayConsoleでのアプリ登録が必要 PlayConsoleで管理される署名証明書情報を 比較するため
packageNameの異なるDevアプリで検証し たい場合など
アプリとデバイスの完全性 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/
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/
App Attestの特徴 自社サーバー Appleサーバー Secure Enclave Appleサーバー
1 2 3 4 5 6 7 8 9 10
11 12 // attestation { fmt 'apple-appattest', attStmt { x5c [ <Buffer cc ... >, <Buffer ... > ], receipt <Buffer ... > }, authData <Buffer c9 9e ... > } : : : : : 30 82 02 30 82 02 36 30 80 06 09 21 00 1 2 3 4 5 // assertion { signature <Buffer ... >, authenticatorData <Buffer c9 9e ... > } : : 30 45 02 20 21 00 Token payloadの中身 (実際はJSONではないので注意)
最初は段階公開を推奨 Integrity APIとは異なり、リクエスト数に関 する上限の明示はなし App Attestは内部的にDevice Checkフレー ムワークがAppleサーバーにアクセスする 急にリクエストが増えると過負荷状態を回避 するように振る舞う場合がある
【おさらい】 アプリとデバイスの完全性 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/
Firebase App Check 引用: https://firebase.google.com/products/app-check
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++
App Checkを使うことのメリット 導入と運用の簡素化 Integrity APIや App Attestを自前 で使う場合に比べて、プラット フォームAPIの呼び出しのコードを 書かなくて済む
Token発行や検証部分の具体実装 がプロバイダ単位で差し替え可能 になる Firebaseサービスとの親和性 コンソールで設定するだけで正規 アプリからのリクエスト以外を防 止できる Token管理の簡素化 TokenのTTL設定・ローテーション リプレイ攻撃への対策 デバッグキー管理 nonce生成, 署名検証 クライアントはgetTokenを呼ぶだ けでいい サーバーはverifyTokenを呼ぶだけ でいい
App Checkの特徴
App Checkの特徴 ① App Check Tokenを要求
App Checkの特徴 ② Integrity API Token生成チャレンジリクエストを生成 ③ チャレンジリクエストを送信 AppCheckサーバー
App Checkの特徴 ④ チャレンジ処理を実行 ⑤ 結果とTTLを返却 AppCheckサーバー
App Checkの特徴 ⑥ Integrity API Tokenリクエストを生成&送信 Google Play Service ⑦
Integrity API Tokenを返却
App Checkの特徴 ⑧ App Check Token変換リクエストを生成 ⑨ 交換リクエストを送信 AppCheckサーバー
App Checkの特徴 10 交換処理を実行 11 App Check TokenとTTLを返却 AppCheckサーバー
App Checkの特徴 12 App Check Tokenを返却 AppCheckサーバー
App Checkの特徴 13 チェックインリクエスト with App Check Tokenを送信 14 App
CheckTokenを検証 自社サーバー AppCheckサーバー AppCheckサーバー
App Checkの特徴 自社サーバー AppCheckサーバー AppCheckサーバー 16 チェックイン処理結果を返却 15 チェックイン処理
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で 想定より早く枯渇する可能性 がある
サーバーリクエスト改ざんまとめ データ保護のための基礎対策を怠らない 暗号化や難読化、秘匿情報の管理は早い段階で見直しをし てみよう OSやライブラリのバージョン更新、日々頑張っていく デバイスやアプリの完全性を担保しよう Play Integrity APIやApp Attestを使って検証ができる
Flutterでの利用の場合は特に実装が大変 Firebase App Checkを使えばかなり手軽に導入できる それでも不正とのイタチごっこ ポイントの不正取得の観点においては、かなり不正されづら くなったはず ただ、これまでに紹介した機能やサービスも完全に不正を 検知できるとは限らない 仰々しいセキュリティ対策だけでなく、サービス特性や機能 の特徴に応じた不正対策も重要
【おさらい】 今回の不正の脅威モデリング 1日で2日分のポイント獲得 23:55にチェックインして24:05に 再度チェックインする 1回来場するだけで2回分のポイン トを獲得する 位置情報の偽造 チェックインイベント開催中にデ バイスの位置情報を偽造
競輪場にいたことにしてポイント を獲得する サーバーリクエストの改ざん チェックイン処理のコードや通信 を解析して差し替えてリクエスト 競輪場付近の位置情報を投げてポ イントを獲得する
今回の不正の脅威モデリングの対策まとめ 1日で2日分のポイント獲得 想定される正常なユーザーさんの ユースケースを元にして機能仕様 のアップデートを検討した 位置情報の偽造 位置情報のモックに関してはロ ケーションライブラリから返却さ れるisMockedの値を確認するよう にした
位置情報が外部から差し替えられ る不正のケースを判定し、エラー に倒すようにした サーバーリクエストの改ざん セキュリティにおける基礎対策を 施し、処理の詳細が漏れづらくし た アプリやデバイスの完全性を保証 する仕組みを導入し、正常性判定 に活用するようにした
まとめ セキュリティの重要性 セキュリティは一発アウトのリス クや事業的な収益にも影響するリ スクを孕んでいる 多層防御で考える 複数の対策を組み合わせることで 攻撃者側のコストメリットを下 げ、不正リスクを下げられる 予防しすぎないこと
色々と大変になる 動作テストが難しくなる 処理が複雑になる 障害点が増え、バグリスクも上 がる リスクに対して必要十分な対策を 今回紹介したものは比較的コスパ がいいものなので、サービス特性 や重要度に合わせてぜひ検討して みてください!
Special Thanks OWASP Mobile Application Security (MAS) モバイルアプリセキュリティの業界標準を定義 セキュリティ標準(OWASP MASVS)
弱点リスト(OWASP MASWE) 包括的なテストガイド(OWASP MASTG) Android Developers公式 ドキュメントがとても丁寧 一般的なセキュリティ知識についても触れられている セキュリティチェックリストもあるので一読をおすすめします 引用: https://mas.owasp.org/ 引用: https://developer.android.com/
参考文献 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