Categoría de OWASP: MASVS-CODE: Calidad de código
Descripción general
Cuando se almacenan o transfieren grandes cantidades de datos de objetos de Java, suele ser más eficiente serializar los datos primero. Luego, la aplicación, la actividad o el proveedor receptores que terminan controlando los datos los someterán a un proceso de deserialización. En circunstancias normales, los datos se serializan y, luego, se deserializan sin ninguna intervención del usuario. Sin embargo, la relación de confianza entre el proceso de deserialización y su objeto previsto puede ser objeto de abuso por parte de un agente malicioso que podría, por ejemplo, interceptar y alterar objetos serializados. Esto permitiría al actor malicioso realizar ataques como denegación del servicio (DoS), elevación de privilegios y ejecución remota de código (RCE).
Si bien la clase Serializable es un método común para administrar
la serialización, Android tiene su propia clase para controlar la serialización llamada
Parcel. Con la clase Parcel, los datos de objetos se pueden serializar en datos de flujo de bytes
y empaquetar en un Parcel con la interfaz Parcelable.
Esto permite que el Parcel se transporte o almacene de manera más eficiente.
Sin embargo, se debe tener cuidado cuando se usa la clase Parcel, ya que está diseñada para ser un mecanismo de transporte de IPC de alta eficiencia, pero no se debe usar para almacenar objetos serializados dentro del almacenamiento persistente local, ya que esto podría generar problemas de compatibilidad o pérdida de datos. Cuando se deben leer los datos, se puede usar la interfaz Parcelable para deserializar el
Parcel y volver a convertirlo en datos de objetos.
Existen tres vectores principales para aprovechar la deserialización en Android:
- Aprovechar la suposición incorrecta de un desarrollador de que es seguro deserializar objetos que provienen de un tipo de clase personalizado. En realidad, cualquier objeto proveniente de cualquier clase se puede reemplazar potencialmente con contenido malicioso que, en el peor de los casos, puede interferir con los cargadores de clase de la misma aplicación o de otras. Esta interferencia toma la forma de inyectar valores peligrosos que, según el propósito de la clase, pueden provocar, por ejemplo, el robo de datos o la apropiación de cuentas.
- Aprovechar los métodos de deserialización que se consideran inseguros por diseño (por ejemplo CVE-2023-35669, una falla de elevación de privilegios locales que permitía la inyección de código JavaScript arbitrario a través de un vector de deserialización de vínculos directos )
- Aprovechar las fallas en la lógica de la aplicación (por ejemplo, CVE-2023-20963, una falla de elevación de privilegios locales que permitía que una app descargara y ejecutara código en un entorno con privilegios a través de una falla en la lógica de paquetes de WorkSource de Android).
Impacto
Cualquier aplicación que deserialice datos serializados no confiables o maliciosos podría ser vulnerable a ataques de ejecución remota de código o de denegación del servicio.
Riesgo: Deserialización de entrada no confiable
Un atacante puede aprovechar la falta de verificación de paquetes dentro de la lógica de la aplicación para inyectar objetos arbitrarios que, una vez deserializados, podrían obligar a la aplicación a ejecutar código malicioso que puede provocar denegación del servicio (DoS), elevación de privilegios y ejecución remota de código (RCE).
Estos tipos de ataques pueden ser sutiles. Por ejemplo, una aplicación puede contener un intent que espera solo un parámetro que, después de validarse, se deserializará. Si un atacante envía un segundo parámetro adicional malicioso inesperado junto con el esperado, esto hará que se deserialicen todos los objetos de datos inyectados, ya que el intent trata los extras como un
Bundle. Un usuario malicioso puede usar este comportamiento para inyectar datos de objetos que, una vez deserializados, pueden provocar RCE, pérdida o riesgo de datos.
Mitigaciones
Como práctica recomendada, supone que todos los datos serializados no son confiables y son potencialmente maliciosos. Para garantizar la integridad de los datos serializados, realiza verificaciones de los datos para asegurarte de que sean la clase y el formato correctos que espera la aplicación.
Una solución factible podría ser implementar el patrón de anticipación para la
java.io.ObjectInputStream biblioteca. Si modificas el código responsable de
la deserialización, puedes asegurarte de que solo se deserialice un conjunto de
clases especificado de forma explícita dentro del intent.
A partir de Android 13 (nivel de API 33), se actualizaron varios métodos dentro de la clase Intent que se consideran alternativas más seguras a los métodos más antiguos y ahora obsoletos para controlar paquetes. Estos nuevos métodos más seguros para tipos, como
getParcelableExtra(java.lang.String, java.lang.Class) y
getParcelableArrayListExtra(java.lang.String, java.lang.Class), realizan
verificaciones de tipo de datos para detectar debilidades de desajuste que podrían provocar que las aplicaciones
fallen y que se aprovechen para realizar ataques de elevación de privilegios, como
CVE-2021-0928.
En el siguiente ejemplo, se muestra cómo se podría implementar una versión segura de la clase Parcel:
Supongamos que la clase UserParcelable implementa Parcelable y crea una
instancia de datos del usuario que luego se escribe en un Parcel. Luego, se podría usar el siguiente
método más seguro para tipos de readParcelable para leer el
paquete serializado:
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);
Observa en el ejemplo de Java anterior el uso de UserParcelable.CREATOR dentro del método. Este parámetro obligatorio le indica al método readParcelable qué tipo esperar y tiene un mejor rendimiento que la versión ahora obsoleta del método readParcelable.
Riesgos específicos
En esta sección, se recopilan los riesgos que requieren estrategias de mitigación no estándar o que se mitigaron en cierto nivel de SDK y están aquí para lograr una integridad.
Riesgo: Deserialización de objetos no deseados
Si implementas la interfaz Serializable dentro de una clase, se hará que todos los subtipos de la clase determinada implementen la interfaz de forma automática. En este caso, algunos objetos pueden heredar la interfaz mencionada anteriormente, lo que significa que se procesarán objetos específicos que no están destinados a deserializarse.
Esto puede aumentar la superficie de ataque de forma involuntaria.
Mitigaciones
Si una clase hereda la interfaz Serializable, según las instrucciones de OWASP, el método readObject se debe implementar de la siguiente manera para
evitar que se pueda deserializar un conjunto de objetos en la clase:
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");
}
Recursos
- Parcelables
- Parcel
- Serializable
- Intent
- Android Deserialization Vulnerabilities: A Brief history
- Android Parcels: The Bad, the Good and the Better (video)
- Android Parcels: The Bad, the Good and the Better (presentation slides)
- CVE-2014-7911: Android <5.0 Privilege Escalation using ObjectInputStream
- CVE-CVE-2017-0412
- CVE-2021-0928: Parcel Serialization/Deserialization Mismatch
- Instrucciones de OWASP