OWASP category: MASVS-CODE: Code Quality
Overview
When storing or transferring large amounts of Java object data, it is often more efficient to serialize the data first. The data will then undergo a deserialization process by the receiving application, activity, or provider that ends up handling the data. Under normal circumstances, data is serialized and then deserialized without any user intervention. However, the trust relationship between the deserialization process and its intended object can be abused by a malicious actor who could, for example, intercept and alter serialized objects. This would enable the malicious actor to perform attacks such as denial of service (DoS), privilege escalation, and remote code execution (RCE).
While the Serializable
class is a common method for managing
serialization, Android has its own class for handling serialization called
Parcel
. Using the Parcel
class, object data can be serialized into byte
stream data and packed into a Parcel
using the Parcelable
interface.
This allows the Parcel
to be transported or stored more efficiently.
Nevertheless, careful consideration should be given when using the Parcel
class, as it is meant to be a high-efficiency IPC transport mechanism, but
shouldn't be used to store serialized objects within the local persistent
storage as this could lead to data compatibility issues or loss. When the data
needs to be read, the Parcelable
interface can be used to deserialize the
Parcel
and turn it back into object data.
There are three primary vectors for exploiting deserialization in Android:
- Taking advantage of a developer's incorrect assumption that deserializing objects proceeding from a custom class type is safe. In reality, any object sourced by any class can be potentially replaced with malicious content that, in the worst case scenario, can interfere with the same or other applications' class loaders. This interference takes the form of injecting dangerous values that, according to the class purpose, may lead, for example, to data exfiltration or account takeover.
- Exploiting deserialization methods that are considered unsafe by design (for example CVE-2023-35669, a local privilege escalation flaw that allowed arbitrary JavaScript code injection through a deep-link deserialization vector)
- Exploiting flaws in the application logic (for example CVE-2023-20963, a local privilege escalation flaw that allowed an app to download and execute code within a privileged environment through a flaw within Android's WorkSource parcel logic).
Impact
Any application that deserializes untrusted or malicious serialized data could be vulnerable to remote code execution or denial of service attacks.
Risk: Deserialization of untrusted input
An attacker can exploit the lack of parcel verification within the application logic in order to inject arbitrary objects that, once deserialized, could force the application to execute malicious code that may result in denial of service (DoS), privilege escalation, and remote code execution (RCE).
These types of attacks may be subtle. For example, an application may contain an
intent expecting only one parameter that, after being validated, will be
deserialized. If an attacker sends a second, unexpected malicious extra
parameter along with the expected one, this will cause all the data objects
injected to be deserialized since the intent treats the extras as a
Bundle
. A malicious user may make use of this behavior to inject object
data that, once deserialized, may lead to RCE, data compromise, or loss.
Mitigations
As a best practice, assume that all serialized data is untrusted and potentially malicious. To ensure the integrity of serialized data, perform verification checks on the data to make sure it's the correct class and format expected by the application.
A feasible solution could be to implement the look-ahead pattern for the
java.io.ObjectInputStream
library. By modifying the code responsible for
deserialization, you can make sure that only an explicitly specified set of
classes is deserialized within the intent.
As of Android 13 (API level 33), several methods have been updated within the
Intent
class that are considered safer alternatives to older and
now-deprecated methods for handling parcels. These new type-safer methods, such
as getParcelableExtra(java.lang.String, java.lang.Class)
and
getParcelableArrayListExtra(java.lang.String, java.lang.Class)
perform
data type checks to catch mismatch weaknesses that might cause applications to
crash and potentially be exploited to perform privilege escalation attacks, such
as CVE-2021-0928.
The following example demonstrates how a safe version of the Parcel
class
could be implemented:
Suppose the class UserParcelable
implements Parcelable
and creates an
instance of user data that's then written to a Parcel
. The following
type-safer method of readParcelable
could then be used to read the
serialized parcel:
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);
Notice in the Java example above the use of UserParcelable.CREATOR
within the
method. This required parameter tells the readParcelable
method what type to
expect and is more performant than the now-deprecated version of the
readParcelable
method.
Specific Risks
This section gathers risks that require non-standard mitigation strategies or were mitigated at certain SDK level and are here for completeness.
Risk: Unwanted Object Deserialization
Implementing the Serializable
interface within a class will automatically
cause all subtypes of the given class to implement the interface. In this
scenario, some objects may inherit the aforementioned interface, meaning
specific objects that are not meant to be deserialized will still be processed.
This can inadvertently increase the attack surface.
Mitigations
If a class inherits the Serializable
interface, as per OWASP guidance, the readObject
method should be implemented as follows in order to
avoid that a set of objects in the class can be deserialized:
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");
}
Resources
- 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
- OWASP guidance