Deserializzazione non sicura

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