cyber / cybersecurite-fondamentale
Bases de la sécurité mobile (Android/iOS)
Explication
Ce que vous allez apprendre
- Comprendre pourquoi un appareil mobile est un client fondamentalement non fiable
- Utiliser Android Keystore et iOS Keychain plutôt que des préférences en clair pour stocker un secret
- Mettre en place le certificate pinning pour se protéger d'un certificat racine installé de force
- Ne jamais confier une décision métier (prix, permission, quantité) à la seule validation côté client
- Comprendre pourquoi une clé API embarquée dans un binaire doit être considérée comme publique
Dans quel contexte ?
Une application mobile de e-commerce calcule et affiche localement le prix final d'un panier avant de l'envoyer au serveur pour paiement. Un utilisateur avec un appareil rooté intercepte et modifie la requête pour changer le montant avant qu'elle n'atteigne le serveur. Si le serveur fait confiance au prix reçu sans le recalculer lui-même à partir du panier réel, l'attaquant paie ce qu'il veut. C'est l'illustration directe de la règle : ne jamais faire confiance au client pour une décision métier.
Un environnement où l'attaquant peut littéralement tenir l'appareil
Contrairement à une application web où le code s'exécute sur un serveur que l'entreprise contrôle, une application mobile s'exécute sur un appareil que l'ATTAQUANT peut posséder physiquement, démonter, analyser et modifier à son rythme. Ce changement de contexte oblige à repenser certaines hypothèses de confiance : rien de ce qui tourne côté client ne doit être considéré comme totalement à l'abri d'un examen approfondi.
Pourquoi le stockage local doit passer par les coffres-forts de l'OS
| Mécanisme | Plateforme | Ce qu'il apporte |
|---|---|---|
| Android Keystore | Android | Secret lié matériellement à l'appareil |
| EncryptedSharedPreferences | Android | Chiffrement AES256 des préférences, clé dans le Keystore |
| Keychain | iOS | Stockage protégé, non extractible par un simple backup |
Écrire des données sensibles dans des préférences simples ou des fichiers en clair sur l'appareil revient à les laisser accessibles à quiconque a un accès physique ou root sur l'appareil.
Le certificate pinning, une défense contre un adversaire "de confiance"
Le TLS classique (leçon 7) protège contre l'interception, MAIS suppose que l'utilisateur (ou un logiciel malveillant sur son appareil) n'installe pas volontairement un certificat racine de confiance permettant d'intercepter le trafic. Le certificate pinning va plus loin : l'application vérifie que le certificat du serveur correspond EXACTEMENT à celui attendu, codé en dur, refusant de faire confiance à n'importe quel certificat même signé par une autorité normalement fiable.
La règle d'or : ne jamais faire confiance au client pour une décision métier
Piège fréquent
Un prix, une quantité, une permission calculée ou vérifiée uniquement côté application mobile peut être manipulée par un attaquant qui modifie l'application ou intercepte et rejoue les requêtes différemment. Toute décision qui a une valeur métier réelle doit être revérifiée côté serveur, sans exception.
Une clé API dans le code du binaire est déjà publique
Même "cachée" dans le code compilé, une clé API embarquée dans une application distribuée peut être extraite par rétro-ingénierie. Il faut la considérer comme publique dès sa distribution et concevoir l'architecture en conséquence (clé à portée limitée, ou logique sensible déplacée côté serveur).
Commandes & code
Bases de la sécurité mobile (Android/iOS)
Un mobile est un client non fiable par définition : l'attaquant contrôle l'OS, le stockage et peut intercepter le trafic réseau de l'app.
// Android - Stockage NON sécurisé (à éviter) : SharedPreferences en clair
val prefs = getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
prefs.edit().putString("auth_token", token).apply() // lisible en clair par tout accès root/backup non chiffré
// Stockage sécurisé : EncryptedSharedPreferences (Android Jetpack Security)
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val securePrefs = EncryptedSharedPreferences.create(
context,
"secure_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
securePrefs.edit().putString("auth_token", token).apply() // chiffré, clé stockée dans l'Android Keystore (matériel)// iOS - Ne JAMAIS stocker un secret dans UserDefaults (non chiffré, extractible via backup)
UserDefaults.standard.set(token, forKey: "auth_token") // MAUVAIS
// Stockage sécurisé : Keychain, avec une classe d'accessibilité restrictive
import Security
func saveToKeychain(token: String, account: String) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: account,
kSecValueData as String: token.data(using: .utf8)!,
// n'est déverrouillable que si l'appareil est déverrouillé, ET ne migre jamais vers un autre appareil
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
]
SecItemDelete(query as CFDictionary)
SecItemAdd(query as CFDictionary, nil)
}// Certificate pinning : empêcher un proxy MITM (même avec un certificat "de confiance" installé sur l'appareil)
// via OkHttp
val certificatePinner = CertificatePinner.Builder()
.add("api.technologik.fr", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.build()
val client = OkHttpClient.Builder()
.certificatePinner(certificatePinner) // rejette la connexion si le certificat serveur ne matche pas le pin
.build()
// Attention : prévoir un pin de secours (clé de rotation) sous peine de "casser" l'app au renouvellement du certificat# Checklist reverse-engineering / anti-tampering niveau expert
1. Obfuscation du code (R8/ProGuard sur Android, SwiftShield sur iOS) : ralentit, ne bloque pas un attaquant motivé
2. Détection root/jailbreak : signal d'alerte, jamais une garantie de sécurité à elle seule
3. Ne jamais faire confiance à une validation faite côté client (prix, quantité, permissions) : tout revalider serveur
4. Chiffrer les communications réseau en TLS 1.2+ uniquement, désactiver le cleartext traffic (usesCleartextTraffic=false)
5. Ne jamais embarquer de clé API secrète "en dur" dans l'APK/IPA : elle est extractible en quelques minutes (apktool, class-dump)Résumé
- Le stockage local doit toujours passer par les primitives sécurisées de l'OS (Keystore Android, Keychain iOS), jamais par des préférences en clair.
- Le certificate pinning empêche le MITM même avec un certificat racine de confiance installé sur l'appareil.
- Aucune validation métier (prix, permissions, quantité) ne doit être faite uniquement côté client mobile.
- Une clé API embarquée dans le binaire de l'app est considérée comme publique : elle sera extraite.
Exercices pratiques
Mission : le panier à prix négatif sur un appareil rooté
Objectif : Diagnostiquer une faille de confiance côté client sur une app mobile et concevoir la revalidation serveur correspondante.
Contexte
L'application mobile e-commerce de Technologik calcule et affiche localement le prix final d'un panier, puis envoie ce montant au serveur au moment du paiement dans un champ total_price. Un testeur de sécurité, avec un appareil Android rooté, intercepte la requête via un proxy et modifie total_price de 49.99 à 0.01 avant qu'elle n'atteigne le serveur. Le paiement de 0.01 est accepté.