Categoria OWASP: MASVS-CODE: Qualità del codice
Panoramica
Quando archivi o trasferisci grandi quantità di dati di oggetti Java, spesso è più efficiente eseguire prima la serializzazione dei dati. I dati verranno quindi sottoposti a un processo di deserializzazione da parte dell'applicazione, dell'attività o del fornitore di servizi di destinazione che li gestisce. In circostanze normali, i dati vengono serializzati e quindi deserializzati senza alcun intervento dell'utente. Tuttavia, la relazione di fiducia tra il processo di deserializzazione e l'oggetto previsto può essere abusata da un utente malintenzionato che potrebbe, ad esempio, intercettare e modificare oggetti serializzati. Ciò consentirebbe all'utente malintenzionato di eseguire attacchi come denial of service (DoS), escalation dei privilegi ed esecuzione di codice 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 dell'oggetto possono essere serializzati in dati stream di byte e pacchettizzati in un Parcel
utilizzando l'interfaccia Parcelable
.
In questo modo, il Parcel
può essere trasportato o immagazzinato in modo più efficiente.
Tuttavia, occorre prestare attenzione quando si utilizza la classe Parcel
, poiché è 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 poiché ciò potrebbe causare problemi di compatibilità o perdita dei dati. Quando i dati devono essere letti, l'interfaccia Parcelable
può essere utilizzata per deserializzare il Parcel
e trasformarlo di nuovo in dati oggetto.
Esistono tre vettori principali per sfruttare la deserializzazione in Android:
- Sfrutta l'assunto errato di uno sviluppatore secondo cui la deserializzazione degli oggetti derivanti da un tipo di classe personalizzata è sicura. In realtà, qualsiasi oggetto fornito da qualsiasi classe può essere potenzialmente sostituito con contenuti dannosi che, nel peggiore dei casi, possono interferire con i caricatori delle classi della stessa o di altre applicazioni. Questa interferenza assume la forma di valori pericolosi che, a seconda dello scopo della classe, possono portare, ad esempio, all'esfiltrazione di dati o alla violazione di account.
- Sfruttamento di metodi di deserializzazione considerati non sicuri per progettazione (ad esempio CVE-2023-35669, un difetto di escalation dei privilegi locale che consentiva l'iniezione di codice JavaScript arbitrario tramite un vettore di deserializzazione dei link diretti)
- Sfruttamento di difetti nella logica di applicazione (ad esempio CVE-2023-20963, un difetto di elevazione dei privilegi locali che consentiva a un'app di scaricare e di eseguire codice in un ambiente privilegiato tramite un difetto nella logica del pacchetto WorkSource di Android).
Impatto
Qualsiasi applicazione che deserializza dati serializzati non attendibili o dannosi potrebbe essere vulnerabile a attacchi di esecuzione di codice da remoto o denial of service.
Rischio: deserializzazione di input non attendibili
Un utente malintenzionato può sfruttare la mancanza di verifica dei pacchi all'interno della logica dell'applicazione per inserire oggetti arbitrari che, una volta deserializzati, potrebbero costringere l'applicazione a eseguire codice dannoso che potrebbe comportare denial of service (DoS), escalation dei privilegi ed esecuzione di codice remota (RCE).
Questi tipi di attacchi possono essere sottili. Ad esempio, un'applicazione potrebbe contenere un intento 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 iniettati verranno deserializzati poiché l'intent tratta gli extra come un Bundle
. Un utente malintenzionato potrebbe utilizzare questo comportamento per iniettare dati oggetto che, una volta deserializzati, potrebbero comportare 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 sui dati per assicurarti che siano della classe e del formato corretti previsti dall'applicazione.
Una soluzione fattibile potrebbe essere implementare il pattern di previsione per la
java.io.ObjectInputStream
libreria. Modificando il codice responsabile della deserializzazione, puoi assicurarti che solo un insieme di classi specificato esplicitamente venga deserializzato all'interno dell'intent.
A partire da Android 13 (livello API 33), sono stati aggiornati diversi metodi all'interno della classe Intent
, che sono considerati alternative più sicure ai metodi precedenti e ora ritirati per la gestione dei pacchetti. Questi nuovi metodi più sicuri in termini di tipo, come getParcelableExtra(java.lang.String, java.lang.Class)
e getParcelableArrayListExtra(java.lang.String, java.lang.Class)
, eseguono controlli sul tipo di dati per rilevare le debolezze di mancata corrispondenza che potrebbero causare arresti anomali delle applicazioni e potenzialmente essere sfruttate per eseguire attacchi di escalation dei privilegi, come CVE-2021-0928.
L'esempio seguente mostra come implementare una versione sicura della classe Parcel
:
Supponiamo che la classe UserParcelable
implementi Parcelable
e crei un'istanza di dati utente che viene quindi scritta in un Parcel
. Per leggere il pacchetto serializzato, è possibile utilizzare il seguente metodo di readParcelable
più sicuro in termini di 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 da aspettarsi 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 riportati per completezza.
Rischio: deserializzazione di oggetti indesiderati
L'implementazione dell'interfaccia Serializable
all'interno di una classe causerà automaticamente l'implementazione dell'interfaccia in tutti i sottotipi della classe in questione. In questo
scenario, alcuni oggetti potrebbero ereditare l'interfaccia sopra menzionata, il che significa
che 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
- Parcelable
- Collo
- Serializable
- Intenzione
- Vulnerabilità di deserializzazione di Android: una breve storia
- Pacchetti Android: il cattivo, il buono e il meglio (video)
- Android Parcels: Il cattivo, il buono e il meglio (slide di presentazione)
- CVE-2014-7911: escalation dei privilegi in Android <5.0 tramite ObjectInputStream
- CVE-CVE-2017-0412
- CVE-2021-0928: mancata corrispondenza di serializzazione/deserializzazione dei pacchetti
- Linee guida OWASP