LLM 不是亂猜!
Abstract
這份報告探討如何透過特定的 taskflows 引導大型語言模型(LLM)找出 Android 應用程式中的特定漏洞類別。資料指出,使用開源的 taskflow agent 找到了 24 個 Android 漏洞 [1] 。我們分析了 pipeline 架構、兩個代表性缺陷(exported activity 中的 intent-extras 信任失敗 ,以及 deeplink handler 中利用 suffix 的主機檢查),並討論了 LLM 嚴重性評估的限制。
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] 。
- # Run the mobile audit wrapper script, which lives under scripts/audit in the taskflow repository
- # The argument is the target repository in owner/name form (here the placeholder myorg/myrepo)
- ./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] 。在架構上,第一階段縮小可達範圍,第二階段縮小搜尋目標,使覆蓋範圍明確且可審計。
圖 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] 。
- // Private method that imports a settings file; it is reached from the exported MapActivity
- private void handleOsmAndSettingsImport(Uri intentUri, String fileName, Bundle extras) {
- // Remove the ZIP extension so only the bare settings name remains
- fileName = fileName.replace(ZIP_EXT, "");
- // Enter this branch when extras exist and contain a version key; only key presence is tested, never the sender
- if (extras != null && CollectionUtils.containsAny(extras.keySet(),
- // The two version-related keys that mark a "trusted" import request
- SETTINGS_VERSION_KEY, SETTINGS_LATEST_CHANGES_KEY)) {
- // Read the settings format version, defaulting to -1 when absent
- int version = extras.getInt(SETTINGS_VERSION_KEY, -1);
- // Read the change notes string attached to the request
- String latestChanges = extras.getString(SETTINGS_LATEST_CHANGES_KEY);
- // Attacker-controlled: true makes the import replace existing settings instead of adding to them
- boolean replace = extras.getBoolean(REPLACE_KEY);
- // Attacker-controlled: true suppresses the import notification
- boolean silentImport = extras.getBoolean(SILENT_IMPORT_KEY);
- // Attacker-controlled: list of setting categories to import without confirmation
- ArrayList<String> exportTypeKeys =
- extras.getStringArrayList(EXPORT_TYPE_LIST_KEY);
- // Start with no export types selected
- List<ExportType> exportTypes = null;
- // Convert the supplied key strings only when a list was provided
- if (exportTypeKeys != null) {
- // Map the strings to ExportType enum values
- exportTypes = ExportType.valuesOf(exportTypeKeys);
- }
- // Delegate to the overload, passing every attacker-influenced value
- handleOsmAndSettingsImport(intentUri, fileName, exportTypes,
- replace, silentImport, latestChanges, version);
- // Taken when no such extras were supplied
- } else {
- // Safe defaults: no types, no replace, no silent import, no notes, version -1
- handleOsmAndSettingsImport(intentUri, fileName,
- null, false, false, null, -1);
- // Close the else branch
- }
- // Close the method
- }
程式 2. 由 intent extras 驅動的設定匯入。
缺陷在於缺少 caller 檢查:本應透過 in-process channel 傳遞的 privileged flags 卻從 intent 接受。由於 silent 和 replace 由 caller 控制,attacker 可以在不被發現的情況下覆寫設定 [1] 。其中一項設定是 map tile URL,應用程式會從 template 格式化它。
- // Build each tile URL by substituting zoom, x and y into the configured template string
- // If an attacker replaced the template, every tile request reveals the viewed coordinates
- return MessageFormat.format(urlTemplate, zoom + "", x + "", y + "");
程式 3. Tile URL 建構。
透過匯入指向 attacker domain 的 template,每個載入 tile 的確切 x 和 y 都會傳送到 attacker server,server 會回應真實的 tiles,因此使用者不會察覺;同樣的操作也暴露了路線起點和終點,且不需要任何 permission [1] 。
圖 2. 位置追蹤鏈。
4. Case B: Suffix-Matching Host Validation in a Deeplink
Wikipedia Android app 註冊了 wikipedia:// deeplink。其 hostname 檢查中的邏輯錯誤允許載入非 Wikipedia 頁面 [1] 。
- // Handle an incoming intent delivered to the activity
- private fun handleIntent(intent: Intent) {
- // Proceed only for VIEW actions that carry a URI
- if (Intent.ACTION_VIEW == intent.action && intent.data != null) {
- // Existing note from the developers about unsupported content types
- // TODO: handle special cases of non-article content, e.g. shared reading lists.
- // Operate on the URI when it is non-null
- intent.data?.let {
- // Flawed check: a plain suffix test, so a host such as evil-wikipedia.org also passes
- if (it.authority.orEmpty().endsWith(WikiSite.BASE_DOMAIN)) {
- // Pass it right along to PageActivity
- // Rewrite the wikipedia:// scheme to the default web scheme
- val uri = Uri.parse(it.toString().replace("wikipedia://", WikiSite.DEFAULT_SCHEME + "://"))
- // Launch the page viewer with the rewritten URI
- startActivity(Intent(this, PageActivity::class.java)
- // Mark the new intent as a VIEW action
- .setAction(Intent.ACTION_VIEW)
- // Attach the attacker-influenced URI
- .setData(uri))
- // Close the domain check
- }
- // Close the let block
- }
- // Close the outer condition
- }
- // Close the function
- }
程式 4. 帶有後綴檢查的 deeplink handler。
這使得連結可以將使用者發送到 attacker 選擇的頁面,同時看起來像是 Wikipedia,並在 WebView 中啟用任意 JavaScript [1] 。相同的模式再次出現在應用程式決定哪些 cookies 屬於某個 domain 時 [1] 。
- // SharedPreferenceCookieManager.kt:101 (location noted in the source)
- // Suffix comparison again: cookies for the Wikipedia domain are attached to any host ending with it
- if (domain.endsWith(domainSpec)) {
- // Add the matching cookies to the outgoing cookie list
- buildCookieList(cookieList, cookiesForDomainSpec, null)
- // Close the condition
- }
程式 5. 透過後綴匹配 Cookie。
鏈接這兩個缺陷會導致 account takeover:受害者開啟惡意頁面並點擊 deeplink,應用程式載入仿冒 domain,並發送暴露 username、long-lived token 和適用於所有 Wikimedia projects 的 session token 的 cookies [1] 。根本原因是使用字串後綴比較而非 host-boundary 比較。
圖 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] 。