更嚴格的 Android Intent 應用程式進入點驗證
用於路由、進入點與快取引擎的 Android `Intent` extras 現在會受到嚴格驗證。
摘要
#
為了防止基於 Intent
的漏洞攻擊,
Flutter Android embedder
(FlutterActivity 與 FlutterFragmentActivity)
現在會在處理 EXTRA_INITIAL_ROUTE、EXTRA_DART_ENTRYPOINT、
EXTRA_DART_ENTRYPOINT_ARGS、EXTRA_CACHED_ENGINE_ID
以及 EXTRA_CACHED_ENGINE_GROUP_ID 之前,
先驗證 Intent 物件的發送者。
在 release 模式下,除非能驗證 Intent 來自應用程式本身,
否則外部的 Intent 物件無法設定這些 extras。
如果驗證失敗,embedder 會忽略這些 extras,
並針對初始路由與 Dart 進入點,
改用 manifest metadata 或預設值。
背景
#
過去,Flutter Android embedder 會直接接受來自 Intent extras 的
路由、進入點與引擎設定。
這使得外部應用程式或惡意攻擊者能夠:
-
劫持應用程式導航,進入敏感的內部路由
(
EXTRA_INITIAL_ROUTE)。 -
執行任意的 Dart 進入點,或傳入不安全的引數
(
EXTRA_DART_ENTRYPOINT與EXTRA_DART_ENTRYPOINT_ARGS)。 -
重複使用已啟動且已通過身分驗證的記憶體內引擎實例
(
EXTRA_CACHED_ENGINE_ID與EXTRA_CACHED_ENGINE_GROUP_ID)。
為了修補這些漏洞,Flutter 現在會驗證 Intent 的發送者。
Deep link 仍然受到支援,
但必須符合你在 AndroidManifest.xml 中宣告的
<intent-filter>。
變更說明
#
在 release 建置中,Android embedder 會嚴格驗證 Intent extras:
"route"、"dart_entrypoint"、"dart_entrypoint_args"、
"cached_engine_id" 與 "cached_engine_group_id"。
Intent 驗證規則
#符合下列任一條件的 Intent 會被視為安全:
-
Activity 未對外公開 (not exported):
設定為
android:exported="false"的 Activity 會自動被信任。 -
Android 14 及以上版本(API 34+):
啟動來源的 UID 與應用程式的 UID 相符(
getLaunchedFromUid())。 -
Android 13 及以下版本(API 33 及更低):
呼叫端的套件與應用程式套件相符(
getCallingPackage())。 這需要使用startActivityForResult()或startActivityIfNeeded()來啟動。
對於 deep link(Intent.ACTION_VIEW),
連結 URI 必須符合應用程式 manifest 中宣告的 <intent-filter>,
除非該 Intent 已被驗證為應用程式自行發送。
後備行為
#如果 release 建置中的驗證失敗,這些 extras 會被忽略:
-
初始路由(
EXTRA_INITIAL_ROUTE): 改用命令列引數,或AndroidManifest.xml中的io.flutter.InitialRoutemetadata。 如果兩者皆未指定,則預設為"/"。 -
Dart 進入點(
EXTRA_DART_ENTRYPOINT): 改用AndroidManifest.xml中的io.flutter.Entrypointmetadata。 如果未指定,則預設為"main"。 -
進入點引數(
EXTRA_DART_ENTRYPOINT_ARGS): 會被忽略,並回傳null。 -
快取引擎 ID(
EXTRA_CACHED_ENGINE_ID與EXTRA_CACHED_ENGINE_GROUP_ID): 會被忽略,並回傳null。
debug 與 profile 模式下的行為
#
在 debug 與 profile 建置中,
embedder 會略過驗證,以保留開發者工具
(例如 adb shell am start)與自動化測試的運作。
不過,如果 Intent 驗證失敗,
embedder 會記錄一則警告,
內容包含目標元件、intent extras 與遷移指示。
遷移指南
#在下列情況下,你不需要進行遷移:
-
你沒有在
Intent中傳遞route、dart_entrypoint、dart_entrypoint_args、cached_engine_id或cached_engine_group_idextras。 -
你的目標
FlutterActivity或FlutterFragmentActivity未對外公開(android:exported="false")。
如果你會將這些 extras 傳給已對外公開的 Activity, 請從下列遷移策略中擇一:
將宿主 Activity 設為不對外公開
#
如果外部應用程式不需要直接開啟你的 Activity,
請在 AndroidManifest.xml 中設定 android:exported="false":
<activity
android:name=".MyFlutterActivity"
android:exported="false">
</activity>
改用 deep link 進行外部路由
#
若要從外部來源、捷徑或通知路由到特定畫面,
請使用標準的 deep link,而不是 EXTRA_INITIAL_ROUTE:
Intent intent = new Intent(context, MainActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse("myapp://product_details"));
請在 AndroidManifest.xml 中宣告對應的 <intent-filter> 項目,
讓 embedder 能夠驗證傳入的 URL。
在 AndroidManifest.xml 中宣告進入點與路由
#
對於靜態的進入點或初始路由,
請在 AndroidManifest.xml 中將它們宣告為 <meta-data>:
<activity
android:name=".MyFlutterActivity"
android:exported="true">
<meta-data
android:name="io.flutter.Entrypoint"
android:value="myCustomEntrypoint" />
<meta-data
android:name="io.flutter.InitialRoute"
android:value="/customRoute" />
</activity>
在 Android 13 及以下版本使用 startActivityForResult
#
對於在 Android 13 及以下版本中,
由應用程式內部啟動已對外公開的 Activity,
請呼叫 startActivityForResult() 取代 startActivity():
- startActivity(intent);
+ startActivityForResult(intent, REQUEST_CODE);
這會填入 getCallingPackage(),讓 embedder 能夠驗證
該 Intent 來自你的應用程式。
你不需要在 onActivityResult() 中處理結果。
在 Activity 子類別中覆寫設定方法
#
若要在不依賴 Intent extras 的情況下提供動態進入點或引數,
請建立 FlutterActivity 或 FlutterFragmentActivity 的子類別,
並覆寫設定方法:
public class MyFlutterActivity extends FlutterActivity {
@NonNull
@Override
public String getDartEntrypointFunctionName() {
return ConfigManager.getEntrypoint();
}
@Nullable
@Override
public List<String> getDartEntrypointArgs() {
return ConfigManager.getArgs();
}
}
時程
#
登陸版本:3.50.0-0.1.pre
穩定版釋出:尚未
參考資料
#相關 issue:
相關 PR:
Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-10-25. View source or report an issue.