FlawOpen

flawopen.com/Teardowns/cve-2026-65995-osmand-aidl-authorization

● CVE-2026-65995 · 高危
安全研究 · FlawOpen

CVE-2026-65995:OsmAnd AIDL 授权绕过

OsmAnd 5.3.3 会自动批准导出的 Android AIDL 服务中的未知调用方,使设备上已安装的任意应用都能触及位置和文件操作。公开通告确认问题已修复,但未说明首个修复版本,也未发布 CVSS 分数。

💡 通俗易懂的原理解析 (ELI5)

想象大楼前台本应核对访客名单,却在每位新访客到达时直接把他加入许可名单。OsmAnd 的服务对同一手机上的应用做了类似的事:它把未知调用方登记为已启用,再开放集成接口。修复应要求明确批准,并将批准绑定到调用方真实的 Android 身份。

核心概念与专有名词

AIDL
Android Interface Definition Language:一种通过 Binder IPC 让一个 Android 进程调用另一个进程所暴露方法的机制。
导出的服务
其他应用可以绑定的 Android 服务。敏感方法必须由权限或同等强度的调用方检查保护。
调用方 UID
操作系统分配给应用进程的身份。服务应从 Binder 获取 UID,不能相信调用方自行提供的包名。
CWE-862
FlawOpen 对共享授权边界缺失所作的编辑分类。GHSL 将 CWE-862 列为多个弱点之一;其通告未发布 CVSS 分数。

框架映射

  • FlawOpen 对 MITRE CWE 的概念性映射;没有厂商或 CVE 记录将此弱点编号分配给本研究演示。 CWE-862

根本原因剖析 (Root Cause)

GitHub Security Lab 测试了 OsmAnd 5.3.3,发现导出的 OsmandAidlServiceV2 没有 Android 权限,且 OsmandAidlApi.isAppEnabled() 会以 enabled=true 创建未知 ConnectedApp。由于 getApi() 依据此检查放行 AIDL 调用,新出现的本地应用会被保存为已授权,而不是在用户批准前拒绝。GHSL 称这使 97 个 AIDL 方法暴露,包括返回位置/导航数据的方法和存在路径遍历问题的文件操作。攻击者必须先在同一设备安装应用,但该应用无需 OsmAnd 的位置权限或额外用户操作。GHSL 描述的文件影响限于 OsmAnd 存储沙箱,并非整个设备文件系统。维护者于 2026 年 6 月 28 日确认已修复,但通告未指出首个修复版本。没有发布 CVSS 分数;FlawOpen 基于位置数据泄露和影响完整性的操作,将严重性编辑性评为高。CWE-862 是 FlawOpen 的主要编辑映射,并非 GHSL 只指定了这一种弱点。

攻击执行流程分解

步骤 1

本地应用连接导出的服务

易受影响的服务在没有 Android 权限的情况下导出。同一设备上已安装的应用可连接,而无需请求 OsmAnd 的位置权限。

步骤 2

检查逻辑自行创建授权

遇到未知包名时,isAppEnabled() 会创建并持久化 enabled=true 的 ConnectedApp。检查逻辑反而授予了它本应验证的访问权。

步骤 3

共享边界开放多种能力

getApi() 使用该结果处理 AIDL 调用。GHSL 表示该路径可到达 97 个方法,包括 getAppInfo() 和文件复制操作。

步骤 4

影响范围遵循已证实前提

恶意应用必须已经安装在设备上。GHSL 记录了位置/导航信息泄露和 OsmAnd 沙箱内的文件影响;未证明可访问整个设备或客户设备遭到真实攻击。

源代码对比:漏洞与安全实现

易受影响代码摘自 GHSL 对所测 5.3.3 版本的分析。下方示例是最小化的默认拒绝策略示意,不是 OsmAnd 补丁或完整授权实现:批准流程必须验证 UID、已安装包签名、用户同意以及每个方法所需的权限范围。生产服务应在 Binder 边界执行授权,并单独限制敏感方法。GHSL 确认问题已修复,但没有公布完整补丁或首个修复版本;请升级到受支持版本并核实供应方的最新说明。代码仅作教学用途,不含利用代码或位置/文件载荷。

源码摘录: 方法:isAppEnabled()

存在漏洞的实现
public boolean isAppEnabled(@NonNull String pack) {
    ConnectedApp connectedApp = connectedApps.get(pack);
    if (connectedApp == null) {
        connectedApp = new ConnectedApp(app, pack, true);
        connectedApps.put(pack, connectedApp);
        saveConnectedApps();
    }
    return connectedApp.isEnabled();
}
加固后的安全修复
private final Set<Integer> approvedCallerUids = new HashSet<>();

private boolean isAppEnabled() {
    // 仅在用户明确同意并完成身份验证后填写。
    return approvedCallerUids.contains(Binder.getCallingUid());
}

工程与系统安全加固清单

参考来源