Salta el contingut

Seguretat en Aplicacions Mòbils

Introducció

Les aplicacions mòbils gestionen dades altament sensibles: credencials bancàries, dades de salut, localització en temps real, missatges privats. A diferència de les aplicacions web, les apps mòbils corren en dispositius fora del control de l'empresa, en xarxes no confiables i en mans d'usuaris que poden tenir el dispositiu compromès (jailbreak/root).

L'OWASP MASVS (Mobile Application Security Verification Standard) és el referent per a la seguretat de les aplicacions mòbils, complementat pels OWASP Mobile Top 10, la llista de les 10 vulnerabilitats més crítiques en entorns mòbils.

flowchart TD
    APP[Aplicació Mòbil] --> STORAGE[Emmagatzematge local]
    APP --> NETWORK[Comunicació de xarxa]
    APP --> AUTH[Autenticació]
    APP --> CRYPTO[Criptografia]
    APP --> UI[Interfície d'usuari]

    STORAGE --> S1[Keychain/Keystore]
    STORAGE --> S2[SharedPreferences]
    STORAGE --> S3[SQLite DB]

    NETWORK --> N1[Certificate Pinning]
    NETWORK --> N2[TLS 1.3]

    AUTH --> A1[Biometria]
    AUTH --> A2[OAuth 2.0/OIDC]

    style S2 fill:#FF5722
    style S3 fill:#FF9800
    style S1 fill:#4CAF50

OWASP Mobile Top 10

M1 - Ús Inadequat de Credencials

Emmagatzemar credencials (contrasenyes, tokens, API keys) en llocs insegurs:

// ❌ MAL: Guardar token en SharedPreferences en text clar
val prefs = getSharedPreferences("app", Context.MODE_PRIVATE)
prefs.edit().putString("auth_token", "Bearer abc123...").apply()

// ❌ MAL: Guardar en fitxer de text
File("/sdcard/credentials.txt").writeText("user:password")

// ✅ BÉ: Usar Android Keystore + EncryptedSharedPreferences
val masterKey = MasterKey.Builder(context)
    .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
    .build()

val encryptedPrefs = EncryptedSharedPreferences.create(
    context,
    "secure_prefs",
    masterKey,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
encryptedPrefs.edit().putString("auth_token", "Bearer abc123...").apply()

M2 - Seguretat Inadequada de la Cadena de Subministrament

Les biblioteques de tercers poden introduir vulnerabilitats. Eines de detecció:

# Analitzar dependències Android per vulnerabilitats
./gradlew dependencyCheckAnalyze

# OWASP Dependency-Check
docker run --rm \
  -v $(pwd):/src \
  owasp/dependency-check \
  --project "MyApp" \
  --scan /src \
  --format HTML \
  --out /src/reports

# iOS: verificar amb CocoaPods Audit
pod audit

M3 - Autenticació/Autorització Insegura

// ❌ MAL: Autenticació basada en el client
if (userId == "admin") {
    showAdminPanel()  // Fàcil de bypassejar amb proxy
}

// ✅ BÉ: Sempre validar al servidor
// El client envia el token JWT, el servidor valida el rol
suspend fun getAdminData(): Result<AdminData> {
    val response = apiService.getAdminData(
        token = sessionManager.getToken()
    )
    // El servidor retorna 403 si no és admin
    return if (response.isSuccessful) {
        Result.success(response.body()!!)
    } else {
        Result.failure(UnauthorizedException())
    }
}

M4 - Validació d'Entrada Insuficient

// iOS - ❌ MAL: Confiar en les dades de l'usuari
let query = "SELECT * FROM users WHERE name = '\(userInput)'"
// SQL injection si l'usuari introdueix: ' OR '1'='1

// ✅ BÉ: Usar prepared statements
let query = "SELECT * FROM users WHERE name = ?"
let statement = try db.prepare(query)
try statement.bind(userInput)

M5 - Comunicació Insegura

// ❌ MAL: HTTP en text clar
val url = URL("http://api.empresa.cat/users")

// ❌ MAL: Desactivar validació de certificats (PRÀCTICA MOLT PERILLOSA)
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
    override fun checkClientTrusted(chain: Array<X509Certificate>?, authType: String?) {}
    override fun checkServerTrusted(chain: Array<X509Certificate>?, authType: String?) {}
    // Accepta QUALSEVOL certificat, inclosos els de l'atacant
})

// ✅ BÉ: HTTPS + Certificate Pinning
val certificatePinner = CertificatePinner.Builder()
    .add("api.empresa.cat", "sha256/HASH_DEL_CERTIFICAT_REAL")
    .build()

val client = OkHttpClient.Builder()
    .certificatePinner(certificatePinner)
    .build()

Certificate Pinning

El certificate pinning fa que l'app accepti ÚNICAMENT el certificat específic del servidor, evitant atacs Man-in-the-Middle fins i tot amb un certificat TLS "vàlid" però fraudulent. El desavantatge és que cal actualitzar l'app quan canvia el certificat del servidor.

M6 - Controls de Privacitat Inadequats

Les apps sol·liciten permisos excessius. Aplicar el principi de mínims privilegis:

<!-- AndroidManifest.xml -->
<!-- ❌ MAL: Demanar permisos innecessaris -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.CAMERA"/>
<!-- Per una app de conversi de moneda no cal cap d'aquests -->

<!-- ✅ BÉ: Únicament els permisos necessaris, i en runtime -->
<!-- Cap permís en l'exemple d'una app de conversió -->
// Demanar permisos en runtime (Android 6+) quan realment es necessitin
private fun requestCameraPermission() {
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
        != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(
            this,
            arrayOf(Manifest.permission.CAMERA),
            CAMERA_REQUEST_CODE
        )
    }
}

Emmagatzematge Segur

Android Keystore

L'Android Keystore és un repositori de claus criptogràfiques a nivell del hardware (en dispositius amb TEE - Trusted Execution Environment):

// Crear una clau en el Keystore (no exportable)
val keyGenerator = KeyGenerator.getInstance(
    KeyProperties.KEY_ALGORITHM_AES,
    "AndroidKeyStore"
)
keyGenerator.init(
    KeyGenParameterSpec.Builder(
        "my_key_alias",
        KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
    )
    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
    .setUserAuthenticationRequired(true)  // Requereix biometria/PIN
    .build()
)
val secretKey = keyGenerator.generateKey()

// La clau mai surt del Keystore en text clar
// Usada per xifrar dades sensibles localment

iOS Keychain

// Guardar un token de forma segura a iOS
import Security

func saveToken(_ token: String) {
    let data = token.data(using: .utf8)!
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: "auth_token",
        kSecValueData as String: data,
        kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
        // Únicament accessible quan el dispositiu està desbloquejat
        // i únicament en AQUEST dispositiu (no es migra al backup)
    ]
    SecItemAdd(query as CFDictionary, nil)
}

Model de Permisos

Android

flowchart TD
    APP[Aplicació] -->|Sol·licita permís| OS[Sistema Android]
    OS -->|Instal·lació o Runtime| USER[Usuari]
    USER -->|Accepta/Denega| OS
    OS -->|Concedeix/Denega| APP

    subgraph Permisos normals
        INTERNET[INTERNET]
        VIBRATE[VIBRATE]
    end
    subgraph Permisos perillosos
        CAMERA[CAMERA]
        LOCATION[ACCESS_FINE_LOCATION]
        CONTACTS[READ_CONTACTS]
        STORAGE[READ_EXTERNAL_STORAGE]
    end
    subgraph Permisos especials
        OVERLAY[SYSTEM_ALERT_WINDOW]
        ACCESSIBILITY[BIND_ACCESSIBILITY_SERVICE]
    end

Els permisos perillosos requereixen aprovació explícita de l'usuari en runtime (Android 6+). Els permisos especials requereixen navegació als ajustos del sistema.

iOS App Transport Security

<!-- Info.plist - iOS exigeix HTTPS per defecte -->
<key>NSAppTransportSecurity</key>
<dict>
    <!-- ❌ MAL: Desactivar ATS completament -->
    <key>NSAllowsArbitraryLoads</key>
    <true/>

    <!-- ✅ BÉ: Excepcions específiques si cal (i justificades) -->
    <key>NSExceptionDomains</key>
    <dict>
        <key>legacy-api.empresa.cat</key>
        <dict>
            <key>NSExceptionAllowsInsecureHTTPLoads</key>
            <true/>
            <!-- Documentar per quin motiu i quan s'eliminarà -->
        </dict>
    </dict>
</dict>

Anàlisi Estàtica d'Apps (SAST Mòbil)

# MobSF (Mobile Security Framework) - anàlisi estàtica i dinàmica
docker run -it --rm \
  --name mobsf-NOMCOGNOM \
  -p 8000:8000 \
  -v $(pwd)/mobsf-data:/home/mobsf/.MobSF \
  opensecurity/mobile-security-framework-mobsf:latest

# Accedir a http://localhost:8000
# Pujar l'APK o IPA per a anàlisi automàtica

# Analystic: eina per a Android específicament
docker run --rm \
  -v $(pwd)/app.apk:/input/app.apk \
  -v $(pwd)/output:/output \
  mobiletools/apkanalyzer /input/app.apk

Miniactivitat

Descarrega una app de codi obert d'Android (per exemple, Signal, Bitwarden) i analitza-la amb MobSF:

  1. Quin nivell MASVS correspon a les seves pràctiques de seguretat?
  2. Hi ha algun permís que trobes excessiu?
  3. El codi font (si és obert) confirma les pràctiques que MobSF detecta?

Monitoratge de Tràfic Mòbil

# mitmproxy: proxy HTTPS per a inspeccionar tràfic mòbil
docker run --rm -it \
  --name mitmproxy-NOMCOGNOM \
  -p 8080:8080 \
  -p 8081:8081 \
  mitmproxy/mitmproxy mitmweb --web-host 0.0.0.0

# En el dispositiu mòbil:
# 1. Configurar proxy HTTP: IP_DEL_PC:8080
# 2. Instal·lar el certificat mitmproxy (http://mitm.it)
# 3. Navegar normalment - el tràfic apareix a http://localhost:8081

# NOTA: Funciona únicament per a apps sense certificate pinning

Ús ètic de mitmproxy

L'ús d'un proxy per interceptar tràfic únicament és legal per a: - Aplicacions pròpies en dispositius propis - Pentesting amb autorització escrita - Entorns de laboratori controlats

Interceptar tràfic de tercers sense autorització és il·legal.

Exercici pràctic

Analitza la seguretat d'una aplicació mòbil:

  1. Usa MobSF per analitzar una APK (pots generar una app bàsica o usar una app de demo)
  2. Identifica almenys 3 vulnerabilitats de l'OWASP Mobile Top 10
  3. Per cada vulnerabilitat, proposa la correcció de codi
  4. Avalua quin nivell MASVS compleix l'app (L1 o L2)

Lliura un informe analisi_mobil_NOMCOGNOM.pdf amb captures de pantalla de MobSF i les teves conclusions.