Abstract

這份報告探討如何透過特定的 taskflows 引導大型語言模型(LLM)找出 Android 應用程式中的特定漏洞類別。資料指出,使用開源的 taskflow agent 找到了 24 個 Android 漏洞 [1] 。我們分析了 pipeline 架構、兩個代表性缺陷(exported activity 中的 intent-extras 信任失敗 ,以及 deeplink handler 中利用 suffix 的主機檢查),並討論了 LLM 嚴重性評估的限制。

LLM 不是亂猜! Taskflow 分段審計讓 OsmAnd 與 Deeplink 漏洞無所遁形! | 資訊安全新聞

1. Introduction

Android 應用程式透過 components 如 activities、intents 和 deeplinks 暴露攻擊面。行動裝置的漏洞類別不如網頁漏洞廣為人知,且 LLM 輸出具有非確定性,因此通用提示可能會忽略這些漏洞 [1] 。原始資料的方法將研究拆分為增量步驟,讓模型能夠發現原本可能錯過的複雜漏洞 [1] 。這份報告從工程角度重建該方法。

2. Pipeline Architecture

審計從一個 wrapper script 開始,針對 codespace 內的 repository 執行;結果會存入 SQLite viewer,其中的 audit_results table 包含 has_vulnerability column [1] 。

  1. # Run the mobile audit wrapper script, which lives under scripts/audit in the taskflow repository
  2. # The argument is the target repository in owner/name form (here the placeholder myorg/myrepo)
  3. ./scripts/audit/run_mobile.sh myorg/myrepo

程式 1. 啟動 mobile audit [1] 。

兩個 taskflows 專門處理通用審計(generic audit)。第一個,gather_mobile_entry_point_info.yaml,將 mobile entry points 與 non-mobile ones 分開,因此混合了 mobile、server 和 desktop code 的 repositories 能保持正確的攻擊面 [1] 。第二個,classify_application_local.yaml,嵌入了一個 vulnerability classes 列表,用於評估每個 entry point 和 component;例如,intent-based entry point 會觸發 confused deputy 和 insecure broadcasts 的檢查 [1] 。嚴格的提示搭配重複執行可防止明顯遺漏,而廣泛的提示則保留模型的創造力 [1] 。在架構上,第一階段縮小可達範圍,第二階段縮小搜尋目標,使覆蓋範圍明確且可審計。

sequenceDiagram %% Actors in the audit pipeline described in the source participant R as Researcher participant S as run_mobile.sh participant E as Entry point taskflow participant C as Classify taskflow participant L as LLM participant D as SQLite results %% The researcher starts the audit for one repository R->>S: run against myorg/myrepo %% The script launches entry point gathering S->>E: start gathering %% The LLM splits entry points by application type E->>L: separate mobile and non-mobile entry points %% The LLM returns the mobile attack surface L-->>E: mobile attack surface %% The surface is handed to the classification step E->>C: mobile entry points %% The checklist of vulnerability classes is applied per component C->>L: check listed vulnerability classes %% The LLM answers with candidate findings L-->>C: candidate findings %% Findings are stored with the has_vulnerability flag C->>D: write audit_results rows %% The researcher reviews the flagged rows D-->>R: open viewer and review

圖 1. 重建的分階段審計流程。

3. Case A: Trusting Intent Extras in an Exported Activity

導航應用程式 OsmAnd export 了 MapActivity,因此任何 app 都可以啟動它 [1] 。當開啟設定檔案時,activity 會讀取 extras settings_version、silent_import、replace 和 export_type_list_key。這些原本預期透過 AIDL service 傳遞,但任何 caller 都可以將任意 extras 附加到 intent,而 Android 沒有提供限制它們的方法 [1] 。

  1. // Private method that imports a settings file; it is reached from the exported MapActivity
  2. private void handleOsmAndSettingsImport(Uri intentUri, String fileName, Bundle extras) {
  3. // Remove the ZIP extension so only the bare settings name remains
  4. fileName = fileName.replace(ZIP_EXT, "");
  5. // Enter this branch when extras exist and contain a version key; only key presence is tested, never the sender
  6. if (extras != null && CollectionUtils.containsAny(extras.keySet(),
  7. // The two version-related keys that mark a "trusted" import request
  8. SETTINGS_VERSION_KEY, SETTINGS_LATEST_CHANGES_KEY)) {
  9. // Read the settings format version, defaulting to -1 when absent
  10. int version = extras.getInt(SETTINGS_VERSION_KEY, -1);
  11. // Read the change notes string attached to the request
  12. String latestChanges = extras.getString(SETTINGS_LATEST_CHANGES_KEY);
  13. // Attacker-controlled: true makes the import replace existing settings instead of adding to them
  14. boolean replace = extras.getBoolean(REPLACE_KEY);
  15. // Attacker-controlled: true suppresses the import notification
  16. boolean silentImport = extras.getBoolean(SILENT_IMPORT_KEY);
  17. // Attacker-controlled: list of setting categories to import without confirmation
  18. ArrayList<String> exportTypeKeys =
  19. extras.getStringArrayList(EXPORT_TYPE_LIST_KEY);
  20. // Start with no export types selected
  21. List<ExportType> exportTypes = null;
  22. // Convert the supplied key strings only when a list was provided
  23. if (exportTypeKeys != null) {
  24. // Map the strings to ExportType enum values
  25. exportTypes = ExportType.valuesOf(exportTypeKeys);
  26. }
  27. // Delegate to the overload, passing every attacker-influenced value
  28. handleOsmAndSettingsImport(intentUri, fileName, exportTypes,
  29. replace, silentImport, latestChanges, version);
  30. // Taken when no such extras were supplied
  31. } else {
  32. // Safe defaults: no types, no replace, no silent import, no notes, version -1
  33. handleOsmAndSettingsImport(intentUri, fileName,
  34. null, false, false, null, -1);
  35. // Close the else branch
  36. }
  37. // Close the method
  38. }

程式 2. 由 intent extras 驅動的設定匯入。

缺陷在於缺少 caller 檢查:本應透過 in-process channel 傳遞的 privileged flags 卻從 intent 接受。由於 silent 和 replace 由 caller 控制,attacker 可以在不被發現的情況下覆寫設定 [1] 。其中一項設定是 map tile URL,應用程式會從 template 格式化它。

  1. // Build each tile URL by substituting zoom, x and y into the configured template string
  2. // If an attacker replaced the template, every tile request reveals the viewed coordinates
  3. return MessageFormat.format(urlTemplate, zoom + "", x + "", y + "");

程式 3. Tile URL 建構。

透過匯入指向 attacker domain 的 template,每個載入 tile 的確切 x 和 y 都會傳送到 attacker server,server 會回應真實的 tiles,因此使用者不會察覺;同樣的操作也暴露了路線起點和終點,且不需要任何 permission [1] 。

sequenceDiagram %% Actors in the tracking attack participant A as Malicious app participant M as MapActivity participant I as Settings import participant T as Attacker tile server %% The attacker sends an explicit intent with crafted extras A->>M: intent with silent_import, replace, export types %% The exported activity forwards the extras to the importer M->>I: handleOsmAndSettingsImport %% The tile template is replaced without any notification I-->>M: tile URL template overwritten %% Later map usage requests tiles from the attacker domain M->>T: request tile with zoom, x, y %% The server logs coordinates and returns a genuine tile T-->>M: genuine tile image

圖 2. 位置追蹤鏈。

4. Case B: Suffix-Matching Host Validation in a Deeplink

Wikipedia Android app 註冊了 wikipedia:// deeplink。其 hostname 檢查中的邏輯錯誤允許載入非 Wikipedia 頁面 [1] 。

  1. // Handle an incoming intent delivered to the activity
  2. private fun handleIntent(intent: Intent) {
  3. // Proceed only for VIEW actions that carry a URI
  4. if (Intent.ACTION_VIEW == intent.action && intent.data != null) {
  5. // Existing note from the developers about unsupported content types
  6. // TODO: handle special cases of non-article content, e.g. shared reading lists.
  7. // Operate on the URI when it is non-null
  8. intent.data?.let {
  9. // Flawed check: a plain suffix test, so a host such as evil-wikipedia.org also passes
  10. if (it.authority.orEmpty().endsWith(WikiSite.BASE_DOMAIN)) {
  11. // Pass it right along to PageActivity
  12. // Rewrite the wikipedia:// scheme to the default web scheme
  13. val uri = Uri.parse(it.toString().replace("wikipedia://", WikiSite.DEFAULT_SCHEME + "://"))
  14. // Launch the page viewer with the rewritten URI
  15. startActivity(Intent(this, PageActivity::class.java)
  16. // Mark the new intent as a VIEW action
  17. .setAction(Intent.ACTION_VIEW)
  18. // Attach the attacker-influenced URI
  19. .setData(uri))
  20. // Close the domain check
  21. }
  22. // Close the let block
  23. }
  24. // Close the outer condition
  25. }
  26. // Close the function
  27. }

程式 4. 帶有後綴檢查的 deeplink handler。

這使得連結可以將使用者發送到 attacker 選擇的頁面,同時看起來像是 Wikipedia,並在 WebView 中啟用任意 JavaScript [1] 。相同的模式再次出現在應用程式決定哪些 cookies 屬於某個 domain 時 [1] 。

  1. // SharedPreferenceCookieManager.kt:101 (location noted in the source)
  2. // Suffix comparison again: cookies for the Wikipedia domain are attached to any host ending with it
  3. if (domain.endsWith(domainSpec)) {
  4. // Add the matching cookies to the outgoing cookie list
  5. buildCookieList(cookieList, cookiesForDomainSpec, null)
  6. // Close the condition
  7. }

程式 5. 透過後綴匹配 Cookie。

鏈接這兩個缺陷會導致 account takeover:受害者開啟惡意頁面並點擊 deeplink,應用程式載入仿冒 domain,並發送暴露 username、long-lived token 和適用於所有 Wikimedia projects 的 session token 的 cookies [1] 。根本原因是使用字串後綴比較而非 host-boundary 比較。

sequenceDiagram %% Actors in the account takeover chain participant V as Victim participant B as Malicious web page participant W as Wikipedia app participant X as Attacker domain %% The victim visits the malicious page and taps the link V->>B: open page and tap deeplink %% The browser hands the deeplink to the app B->>W: wikipedia:// deeplink %% The suffix check accepts the look-alike host W->>X: load evil-wikipedia.org page %% The cookie suffix check attaches session cookies W->>X: send cookies %% The attacker now holds the tokens X-->>V: attacker holds username, long-lived and session tokens

圖 3. account takeover chain。

5. Reliability of LLM Findings

LLM 即使被告知不要,仍會回報低嚴重性或不切實際狀態的問題;它們常常誤判嚴重性,因為緩解因素不易察覺(例如限於 external storage 的 path traversal) [1] 。要求提供概念驗證(Proof of Concept,PoC) 會強制進行 exploitation attempt,但會增加額外執行次數,且 false positives 仍然存在,例如當 internal storage 覆寫 attacker 寫入的 external data 時 [1] 。提出的補救措施是給予模型 debugger 或針對這些情況進行提示 [1] 。正面來看,模型很好地理解了與 security 相關的 API 行為,生成的 proofs of concept 幾乎不需要修改 [1] 。因此,每項發現都需要由 mobile security researcher 進行審查 [1] 。

6. Related Background and Comparison

平台文件指出,exported component 可以由任何 app 啟動,而未 export 的 component 僅限於同一 app、同一 user ID 或 privileged system components;建議始終明確設定該屬性 [2] 。弱點目錄將 improper export 分類為 CWE-926,並建議在不需要共享時使用 unexported components 或 signature-level protection [3] 。與這些以 export 為重點的指南相比,原始資料案例顯示 export 僅是進入條件;真正的缺陷在於啟動後如何解析 extras 和 URLs [1] [2] [3] 。

7. Conclusion

Targeted taskflows 為 LLM 提供分階段、checklist-driven 的 Android attack surface 視圖,並浮現關鍵邏輯缺陷,而不僅是通用 bug 類別 [1] 。它們在嚴重性評估方面的限制意味著人工審查和 proof-of-concept 驗證仍然至關重要 [1] 。

References

  1. How we found 24 Android vulnerabilities using our open source AI security agent
  2. android:exported
  3. CWE-926: Improper Export of Android Application Components