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. Em seguida, os dados vão passar por um processo de desserialização pelo aplicativo, atividade ou provedor receptor que acaba por processar os dados. Em circunstâncias normais, os dados são serializados e depois desserializados sem qualquer 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 ator malicioso que pode, por exemplo, interceptar e alterar objetos serializados. Isso permitiria que o invasor 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 de gerenciamento
da serialização, o Android tem a própria classe para processar a serialização chamada
Parcel
. Com a classe Parcel
, os dados do objeto podem ser serializados em dados de
stream 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
,
porque ela é um mecanismo de transporte de IPC de alta eficiência, mas
não deve ser usada para armazenar objetos serializados no armazenamento persistente
local, porque isso pode levar a 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 desserializar objetos de um tipo de classe personalizado é seguro. Na realidade, qualquer objeto fornecido por qualquer classe pode ser potencialmente substituído por conteúdo malicioso que, na piora das situações, pode interferir no mesmo carregador de classes de um aplicativo ou em outros. Essa interferência assume a forma de injetar valores perigosos que, de acordo com a finalidade da classe, podem levar, por exemplo, à exfiltração de dados ou à invasão de contas.
- Exploração de métodos de desserialização considerados não seguros por design (por exemplo, CVE-2023-35669, uma falha de escalonamento de privilégios local que permitiu a injeção arbitrária de código JavaScript por um vetor de desserialização de links diretos).
- Exploração de falhas na lógica do aplicativo (por exemplo, CVE-2023-20963, uma falha de elevação de privilégio local que permitia que um app fizesse o download e executasse código em um ambiente privilegiado por uma falha na lógica de pacotes do WorkSource do Android).
Impacto
Qualquer aplicativo que deserialize dados serializados não confiáveis ou maliciosos pode ser vulnerável a execução remota de código ou ataques 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 lotes na lógica do aplicativo para injetar objetos arbitrários que, depois de desserializados, podem forçar o aplicativo a executar 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, na sigla em inglês).
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, todos os objetos de dados
injetados serão desserializados, já que a intent trata os extras como um
Bundle
. Um usuário malicioso pode usar esse comportamento para injetar dados de objetos
que, uma vez desserializados, podem levar a RCE, comprometimento ou perda de dados.
Mitigações
Como prática recomendada, suponha que todos os dados serializados não são confiáveis e são potencialmente maliciosos. Para garantir a integridade dos dados serializados, faça verificações de verificação nos dados para garantir que eles sejam a classe e o formato corretos esperados pelo aplicativo.
Uma solução viável pode ser implementar o padrão de previsão 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 especificado explicitamente seja desserializado na intent.
A partir do Android 13 (nível 33 da API), vários métodos foram atualizados na
classe Intent
, que são considerados alternativas mais seguras aos métodos mais antigos e
agora descontinuados para o processamento de pacotes. Esses novos métodos com segurança de tipo, 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 pontos fracos de incompatibilidade que podem causar falhas
e potencialmente exploradas para ataques de escalonamento de privilégios, como
CVE-2021-0928.
O exemplo abaixo 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 será gravada em um Parcel
. O método
mais seguro do tipo readParcelable
a seguir pode ser usado para ler o
pacote 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);
Observe no exemplo em Java acima o uso de UserParcelable.CREATOR
no
método. Esse parâmetro obrigatório informa ao método readParcelable
qual tipo esperar
e tem melhor desempenho do que a versão 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 objeto indesejada
A implementação da interface Serializable
em uma classe faz com que
todos os subtipos dela implementem a interface automaticamente. Nesse
cenário, alguns objetos podem herdar a interface mencionada, o que significa que
objetos específicos que não se destinam a ser desserializados ainda serão processados.
Isso pode aumentar inadvertidamente a superfície de ataque.
Mitigações
Se uma classe herdar a interface Serializable
, de acordo com a orientação OWASP, o método readObject
precisará ser implementado da seguinte maneira para
evitar que um conjunto de objetos na classe possa ser 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: um breve histórico
- Android Parcels: The Bad, the Good and the Better (vídeo)
- Android Parcels: The Bad, the Good and the Better (slides de apresentação)
- CVE-2014-7911: escalonamento de privilégios do Android <5.0 usando ObjectInputStream (em inglês)
- CVE-CVE-2017-0412 (em inglês)
- CVE-2021-0928: incompatibilidade de serialização/desserialização de pacotes
- Orientações do OWASP