Deserializzazione non sicura

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