Categoria do OWASP: MASVS-CODE - Qualidade do código (link em inglês)
Visão geral
Ao armazenar ou transferir grandes quantidades de dados de objetos Java, geralmente é mais eficiente serializar os dados primeiro. Os dados passam por um processo de desserialização pelo aplicativo, atividade ou provedor que os processa. Em circunstâncias normais, os dados são serializados e desserializados sem intervenção do usuário. No entanto, a relação de confiança entre o processo de desserialização e o objeto pretendido pode ser abusada por um agente malicioso que poderia, por exemplo, interceptar e alterar objetos serializados. Isso permitiria que o agente malicioso realizasse ataques como negação de serviço (DoS), escalonamento de privilégios e execução remota de código (RCE).
Embora a classe Serializable seja um método comum para gerenciar a serialização, o Android tem a própria classe para processar a serialização, chamada Parcel. Usando a classe Parcel, os dados do objeto podem ser serializados em dados de fluxo de bytes e empacotados em um Parcel usando a interface Parcelable.
Isso permite que o Parcel seja transportado ou armazenado com mais eficiência.
No entanto, é preciso ter cuidado ao usar a classe Parcel, já que ela é um mecanismo de transporte de IPC de alta eficiência, mas não deve ser usada para armazenar objetos serializados no armazenamento permanente local, porque isso pode causar problemas de compatibilidade ou perda de dados. Quando os dados
precisam ser lidos, a interface Parcelable pode ser usada para desserializar o
Parcel e transformá-lo novamente em dados de objeto.
Há três vetores principais para explorar a desserialização no Android:
- Aproveitar a suposição incorreta de um desenvolvedor de que a desserialização de objetos de um tipo de classe personalizada é segura. Na realidade, qualquer objeto fornecido por qualquer classe pode ser substituído por conteúdo malicioso que, no pior cenário, pode interferir nos carregadores de classe do mesmo ou de outros aplicativos. Essa interferência injeta valores perigosos que, de acordo com a finalidade da classe, podem levar, por exemplo, à exfiltração de dados ou ao sequestro de conta.
- Explorar métodos de desserialização considerados inseguros por design (por exemplo, CVE-2023-35669, uma falha de escalonamento de privilégios local que permitia a injeção de código JavaScript arbitrário por um vetor de desserialização de link direto).
- Explorar falhas na lógica do aplicativo (por exemplo, CVE-2023-20963, uma falha de escalonamento de privilégios local que permitia que um app fizesse o download e executasse código em um ambiente privilegiado por uma falha na lógica de pacote WorkSource do Android).
Impacto
Qualquer aplicativo que desserializa dados serializados não confiáveis ou maliciosos pode ser vulnerável a ataques de execução remota de código ou de negação de serviço.
Risco: desserialização de entrada não confiável
Um invasor pode explorar a falta de verificação de pacotes na lógica do aplicativo para injetar objetos arbitrários que, depois de desserializados, podem forçar o aplicativo a executar um código malicioso que pode resultar em negação de serviço (DoS), escalonamento de privilégios e execução remota de código (RCE).
Esses tipos de ataques podem ser sutis. Por exemplo, um aplicativo pode conter uma
intent que espera apenas um parâmetro que, após ser validado, será
desserializado. Se um invasor enviar um segundo parâmetro extra malicioso inesperado junto com o esperado, isso fará com que todos os objetos de dados injetados sejam desserializados, já que a intent trata os extras como um Bundle. Um usuário malicioso pode usar esse comportamento para injetar dados de objeto que, depois de desserializados, podem levar a RCE, comprometimento ou perda de dados.
Mitigações
Como prática recomendada, suponha que todos os dados serializados não sejam confiáveis e possam ser maliciosos. Para garantir a integridade dos dados serializados, faça verificações nos dados para garantir que eles sejam a classe e o formato corretos esperados pelo aplicativo.
Uma solução possível seria implementar o padrão de look-ahead para a biblioteca java.io.ObjectInputStream. Ao modificar o código responsável pela
desserialização, você pode garantir que apenas um conjunto de
classes explicitamente especificado seja desserializado na intent.
No Android 13 (nível 33 da API), vários métodos foram atualizados na classe
Intent, que são alternativas mais seguras aos métodos mais antigos e
agora descontinuados para processar pacotes. Esses novos métodos mais seguros em relação a tipos, como getParcelableExtra(java.lang.String, java.lang.Class) e getParcelableArrayListExtra(java.lang.String, java.lang.Class), realizam verificações de tipo de dados para detectar falhas de incompatibilidade que podem causar falhas nos aplicativos e possivelmente serem exploradas para realizar ataques de escalonamento de privilégios, como CVE-2021-0928.
O exemplo a seguir demonstra como uma versão segura da classe Parcel
pode ser implementada:
Suponha que a classe UserParcelable implemente Parcelable e crie uma
instância de dados do usuário que é gravada em um Parcel. O seguinte método
com segurança de tipos de readParcelable pode ser usado para ler o
parcel 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);
No exemplo em Java acima, observe o uso de UserParcelable.CREATOR no
método. Esse parâmetro obrigatório informa ao método readParcelable qual tipo esperar e tem uma performance melhor do que a versão agora descontinuada do método readParcelable.
Riscos específicos
Esta seção reúne riscos que exigem estratégias de mitigação não padrão ou que foram mitigados em determinados níveis do SDK e estão aqui para referência.
Risco: desserialização de objetos indesejada
A implementação da interface Serializable em uma classe faz com que todos os subtipos dessa classe implementem a interface automaticamente. Nesse
cenário, alguns objetos podem herdar a interface mencionada, o que significa
que objetos específicos que não devem ser desserializados ainda serão processados.
Isso pode aumentar inadvertidamente a superfície de ataque.
Mitigações
Se uma classe herdar a interface Serializable, conforme orientações da OWASP, o método readObject precisará ser implementado da seguinte maneira para evitar que um conjunto de objetos na classe seja desserializado:
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
- Lote
- Serializable
- Intent
- Vulnerabilidades de desserialização do Android: uma breve história
- Android Parcels: The Bad, the Good and the Better (vídeo)
- Android Parcels: The Bad, the Good and the Better (slides da apresentação)
- CVE-2014-7911: escalonamento de privilégios do Android <5.0 usando ObjectInputStream
- CVE-CVE-2017-0412
- CVE-2021-0928: incompatibilidade de serialização/desserialização de parcelas
- Orientações do OWASP