Categoria OWASP: MASVS-CODE: Code Quality
Panoramica
Quando si archiviano o trasferiscono grandi quantità di dati di oggetti Java, spesso è più efficiente serializzare prima i dati. I dati verranno quindi sottoposti a una procedura di deserializzazione dall'applicazione, dall'attività o dal provider ricevente che alla fine gestirà i dati. In circostanze normali, i dati vengono serializzati e poi deserializzati senza alcun intervento dell'utente. Tuttavia, la relazione di attendibilità tra il processo di deserializzazione e l'oggetto previsto può essere sfruttata da un malintenzionato che potrebbe, ad esempio, intercettare e alterare gli oggetti serializzati. Ciò consentirebbe all'attore malintenzionato di eseguire attacchi come denial of service (DoS), escalation dei privilegi ed esecuzione di codice da remoto (RCE).
Sebbene la classe Serializable sia un metodo comune per gestire la serializzazione, Android ha una propria classe per la gestione della serializzazione chiamata Parcel. Utilizzando la classe Parcel, i dati degli oggetti possono essere serializzati in dati di flusso di byte e inseriti in un Parcel utilizzando l'interfaccia Parcelable.
Ciò consente di trasportare o conservare il Parcel in modo più efficiente.
Tuttavia, è necessario prestare attenzione quando si utilizza la classe Parcel, in quanto è pensata per essere un meccanismo di trasporto IPC ad alta efficienza, ma non deve essere utilizzata per archiviare oggetti serializzati all'interno dell'archiviazione permanente locale, in quanto ciò potrebbe causare problemi di compatibilità o perdita dei dati. Quando i dati
devono essere letti, l'interfaccia Parcelable può essere utilizzata per deserializzare
Parcel e trasformarli nuovamente in dati dell'oggetto.
Esistono tre vettori principali per sfruttare la deserializzazione in Android:
- Sfruttando l'errata ipotesi di uno sviluppatore secondo cui la deserializzazione di oggetti provenienti da un tipo di classe personalizzata è sicura. In realtà, qualsiasi oggetto proveniente da qualsiasi classe può essere potenzialmente sostituito con contenuti dannosi che, nel peggiore dei casi, possono interferire con i class loader della stessa applicazione o di altre. Questa interferenza assume la forma di inserimento di valori pericolosi che, in base allo scopo della classe, possono portare, ad esempio, all'esfiltrazione di dati o al takeover dell'account.
- Sfruttamento di metodi di deserializzazione considerati non sicuri per progettazione (ad esempio CVE-2023-35669, un difetto di escalation dei privilegi locali che consentiva l'iniezione di codice JavaScript arbitrario tramite un vettore di deserializzazione di link diretti)
- Sfruttamento di difetti nella logica dell'applicazione (ad esempio CVE-2023-20963, un difetto di escalation dei privilegi locali che consentiva a un'app di scaricare ed eseguire codice in un ambiente con privilegi tramite un difetto nella logica dei pacchetti WorkSource di Android).
Impatto
Qualsiasi applicazione che deserializza dati serializzati non attendibili o dannosi potrebbe essere vulnerabile all'esecuzione di codice da remoto o ad attacchi denial of service.
Rischio: deserializzazione di input non attendibili
Un malintenzionato può sfruttare la mancanza di verifica dei pacchetti all'interno della logica dell'applicazione per inserire oggetti arbitrari che, una volta deserializzati, potrebbero forzare l'applicazione a eseguire codice dannoso che potrebbe causare Denial of Service (DoS), escalation dei privilegi ed esecuzione di codice remoto (RCE).
Questi tipi di attacchi possono essere subdoli. Ad esempio, un'applicazione può contenere un intent che prevede un solo parametro che, dopo essere stato convalidato, verrà deserializzato. Se un malintenzionato invia un secondo parametro extra dannoso imprevisto
insieme a quello previsto, tutti gli oggetti dati
inseriti verranno deserializzati perché l'intent considera gli extra come
Bundle. Un utente malintenzionato potrebbe sfruttare questo comportamento per inserire dati di oggetti che, una volta deserializzati, potrebbero portare a RCE, compromissione o perdita di dati.
Mitigazioni
Come best practice, presupponi che tutti i dati serializzati non siano attendibili e potenzialmente dannosi. Per garantire l'integrità dei dati serializzati, esegui controlli di verifica per assicurarti che siano della classe e del formato corretti previsti dall'applicazione.
Una soluzione fattibile potrebbe essere l'implementazione del pattern di previsione per la
java.io.ObjectInputStream libreria. Modificando il codice responsabile della
deserializzazione, puoi assicurarti che nell'intent venga deserializzato solo un insieme di
classi specificato in modo esplicito.
A partire da Android 13 (livello API 33), diversi metodi sono stati aggiornati all'interno della
classe Intent, che sono considerati alternative più sicure ai metodi precedenti e
ora ritirati per la gestione dei pacchi. Questi nuovi metodi più sicuri per i tipi, come
getParcelableExtra(java.lang.String, java.lang.Class) e
getParcelableArrayListExtra(java.lang.String, java.lang.Class), eseguono
controlli dei tipi di dati per rilevare le debolezze di mancata corrispondenza che potrebbero causare l'arresto anomalo delle applicazioni e potenzialmente essere sfruttate per eseguire attacchi di escalation dei privilegi, come
CVE-2021-0928.
Il seguente esempio mostra come potrebbe essere implementata una versione sicura della classe Parcel:
Supponiamo che la classe UserParcelable implementi Parcelable e crei un'istanza di dati utente che viene poi scritta in un Parcel. Per leggere il pacco serializzato, si potrebbe quindi utilizzare il seguente metodo di readParcelable più sicuro per il tipo:
Kotlin
val parcel = Parcel.obtain()
val userParcelable = parcel.readParcelable(UserParcelable::class.java.classLoader)
Java
Parcel parcel = Parcel.obtain();
UserParcelable userParcelable = parcel.readParcelable(UserParcelable.class, UserParcelable.CREATOR);
Nell'esempio Java riportato sopra, nota l'utilizzo di UserParcelable.CREATOR all'interno del
metodo. Questo parametro obbligatorio indica al metodo readParcelable il tipo di
dati da prevedere ed è più efficiente rispetto alla versione ora ritirata del
metodo readParcelable.
Rischi specifici
Questa sezione raccoglie i rischi che richiedono strategie di mitigazione non standard o che sono stati mitigati a un determinato livello di SDK e sono qui per completezza.
Rischio: deserializzazione di oggetti indesiderati
L'implementazione dell'interfaccia Serializable all'interno di una classe farà sì che tutti i sottotipi della classe specificata implementino automaticamente l'interfaccia. In questo
scenario, alcuni oggetti potrebbero ereditare l'interfaccia menzionata in precedenza, il che significa
che gli oggetti specifici che non devono essere deserializzati verranno comunque elaborati.
Ciò può aumentare inavvertitamente la superficie di attacco.
Mitigazioni
Se una classe eredita l'interfaccia Serializable, come indicato nelle linee guida OWASP, il metodo readObject deve essere implementato come segue per evitare che un insieme di oggetti nella classe possa essere deserializzato:
Kotlin
@Throws(IOException::class)
private final fun readObject(in: ObjectInputStream) {
throw IOException("Cannot be deserialized")
}
Java
private final void readObject(ObjectInputStream in) throws java.io.IOException {
throw new java.io.IOException("Cannot be deserialized");
}
Risorse
- Parcelables
- Pacco
- Serializable
- Intenzione
- Vulnerabilità di deserializzazione di Android: una breve storia
- Android Parcels: The Bad, the Good and the Better (video)
- Android Parcels: The Bad, the Good and the Better (slide della presentazione)
- CVE-2014-7911: Escalation dei privilegi di Android <5.0 tramite ObjectInputStream
- CVE-CVE-2017-0412
- CVE-2021-0928: Mancata corrispondenza di serializzazione/deserializzazione dei pacchetti
- Indicazioni OWASP