Categoria do OWASP: MASVS-CODE - Qualidade do código (link em inglês)
Visão geral
Não é raro ver aplicativos que implementam funcionalidades que permitem aos usuários transferir dados ou interagir com outros dispositivos usando radiofrequência (RF) de comunicação ou conexões por cabo. As tecnologias mais comuns usadas Para esta finalidade, o Android é o Bluetooth clássico (Bluetooth BR/EDR), o Bluetooth Low Energia (BLE), Wi-Fi P2P, NFC e USB.
Essas tecnologias geralmente são implementadas em aplicativos que precisam comunicar-se com acessórios de casa inteligente, dispositivos de monitoramento de saúde, quiosques de transporte, terminais de pagamento e outros dispositivos com tecnologia Android.
Como em qualquer outro canal, as comunicações máquina a máquina são suscetíveis a ataques que visam comprometer a fronteira de confiança estabelecida entre dois ou mais dispositivos. Técnicas como a personificação de dispositivos podem ser usuários maliciosos para atingir um amplo número de ataques contra a comunicação canal.
O Android disponibiliza APIs específicas para configurar comunicações entre máquinas para os desenvolvedores.
Essas APIs precisam ser usadas com cuidado, porque erros na implementação de protocolos de comunicação podem levar à exposição de dados do usuário ou do dispositivo a terceiros não autorizados. Na pior das hipóteses, os atacantes podem assumem um ou mais dispositivos e, consequentemente, têm acesso total ao conteúdo no dispositivo.
Impacto
O impacto pode variar de acordo com a tecnologia de dispositivo para dispositivo implementada em o aplicativo.
O uso ou a configuração incorreta da máquina para máquina os canais de comunicação podem deixar o dispositivo do usuário exposto a sites tentativas de comunicação. Isso pode deixar o dispositivo vulnerável a outros ataques, como "man-in-the-middle" (MiTM, na sigla em inglês), injeção de comandos, DoS ou usurpação de identidade.
Risco: espionagem de dados confidenciais em canais sem fio
Ao implementar mecanismos de comunicação entre máquinas, é preciso considerar cuidadosamente a tecnologia usada e o tipo de dados que serão transmitidos. Embora as conexões por cabo sejam mais seguras para essas tarefas, porque exigem uma ligação física entre os dispositivos envolvidos, protocolos de comunicação que usam frequências de rádio, como Bluetooth clássico, BLE, NFC e Wi-Fi P2P, podem ser interceptados. Um invasor pode falsificar um dos terminais ou pontos de acesso envolvidos na troca de dados, interceptando a comunicação sem fio e, consequentemente, ganhando acesso a dados sensíveis do usuário. Além disso, aplicativos maliciosos instalados no dispositivo, caso sejam as permissões de execução específicas da comunicação, podem recuperar dados trocados entre os dispositivos pela leitura dos buffers de mensagem do sistema.
Mitigações
Se o aplicativo exigir a troca de dados sensíveis entre máquinas por canais sem fio, as soluções de segurança na camada do aplicativo, como criptografia, precisam ser implementadas no código do aplicativo. Isso vai impedir que invasores farejem no canal de comunicação e recuperem os dados trocados em texto não criptografado. Para mais recursos, consulte a documentação Cryptography.
Risco: injeção de dados maliciosos sem fio
Canais de comunicação máquina a máquina sem fio (Bluetooth clássico, BLE, NFC, Wi-Fi P2P) pode ser adulterado com o uso de dados maliciosos. Atacantes habilidosos podem identificar o protocolo de comunicação em uso e adulterar o fluxo de troca de dados, por exemplo, falsificando um dos endpoints e enviando payloads criados especificamente. Esse tipo de tráfego malicioso pode degradar a funcionalidade do aplicativo e, no pior cenário, causar um comportamento inesperado do aplicativo e do dispositivo ou resultar em ataques como DoS, injeção de comandos ou invasão de dispositivos.
Mitigações
O Android oferece aos desenvolvedores APIs eficientes para gerenciar comunicações máquina a máquina, como Bluetooth clássico, BLE, NFC e Wi-Fi P2P. Elas precisam ser combinadas com uma lógica de validação de dados implementada com cuidado para limpar todos os dados trocados entre dois dispositivos.
Essa solução precisa ser implementada no nível do aplicativo e incluir verificações que verifiquem se os dados têm o comprimento e o formato esperados e contêm um payload válido que possa ser interpretado pelo aplicativo.
O snippet a seguir mostra um exemplo de lógica de validação de dados. Isso foi implementado no exemplo de transferência de dados Bluetooth para desenvolvedores do Android:
Kotlin
class MyThread(private val mmInStream: InputStream, private val handler: Handler) : Thread() {
private val mmBuffer = ByteArray(1024)
override fun run() {
while (true) {
try {
val numBytes = mmInStream.read(mmBuffer)
if (numBytes > 0) {
val data = mmBuffer.copyOf(numBytes)
if (isValidBinaryData(data)) {
val readMsg = handler.obtainMessage(
MessageConstants.MESSAGE_READ, numBytes, -1, data
)
readMsg.sendToTarget()
} else {
Log.w(TAG, "Invalid data received: $data")
}
}
} catch (e: IOException) {
Log.d(TAG, "Input stream was disconnected", e)
break
}
}
}
private fun isValidBinaryData(data: ByteArray): Boolean {
if (// Implement data validation rules here) {
return false
} else {
// Data is in the expected format
return true
}
}
}
Java
public void run() {
mmBuffer = new byte[1024];
int numBytes; // bytes returned from read()
// Keep listening to the InputStream until an exception occurs.
while (true) {
try {
// Read from the InputStream.
numBytes = mmInStream.read(mmBuffer);
if (numBytes > 0) {
// Handle raw data directly
byte[] data = Arrays.copyOf(mmBuffer, numBytes);
// Validate the data before sending it to the UI activity
if (isValidBinaryData(data)) {
// Data is valid, send it to the UI activity
Message readMsg = handler.obtainMessage(
MessageConstants.MESSAGE_READ, numBytes, -1,
data);
readMsg.sendToTarget();
} else {
// Data is invalid
Log.w(TAG, "Invalid data received: " + data);
}
}
} catch (IOException e) {
Log.d(TAG, "Input stream was disconnected", e);
break;
}
}
}
private boolean isValidBinaryData(byte[] data) {
if (// Implement data validation rules here) {
return false;
} else {
// Data is in the expected format
return true;
}
}
Risco: injeção de dados maliciosos por USB
As conexões USB entre dois dispositivos podem ser alvo de um usuário malicioso interessados em interceptar comunicações. Nesse caso, o link físico necessário constitui uma camada de segurança adicional, pois o atacante precisa obter ao cabo que conecta os terminais para espionar mensagem. Outro vetor de ataque é representado por dispositivos USB não confiáveis que, intencionalmente ou não, são conectados ao dispositivo.
Se o aplicativo filtrar dispositivos USB usando PID/VID para acionar uma funcionalidade específica, os invasores poderão adulterar os dados enviados pelo canal USB, falsificando o dispositivo legítimo. Ataques desse tipo podem permitir que usuários mal-intencionados digitem teclas para o dispositivo ou executem aplicativos atividades que, na pior das hipóteses, podem levar à execução remota do código ou download de software indesejado.
Mitigações
É necessário implementar uma lógica de validação no nível do aplicativo. Essa lógica precisa filtrar os dados enviados por USB, verificando se o comprimento, o formato e o conteúdo correspondem ao caso de uso do aplicativo. Por exemplo, um monitor de batimentos cardíacos não pode enviar comandos de tecla.
Além disso, quando possível, é preciso considerar a restrição número de pacotes USB que o aplicativo pode receber do dispositivo USB. Isso impede que dispositivos maliciosos realizem ataques como o patinho de borracha.
Para isso, crie uma nova sequência para verificar o
conteúdo do buffer, por exemplo, em uma bulkTransfer
:
Kotlin
fun performBulkTransfer() {
// Stores data received from a device to the host in a buffer
val bytesTransferred = connection.bulkTransfer(endpointIn, buffer, buffer.size, 5000)
if (bytesTransferred > 0) {
if (//Checks against buffer content) {
processValidData(buffer)
} else {
handleInvalidData()
}
} else {
handleTransferError()
}
}
Java
public void performBulkTransfer() {
//Stores data received from a device to the host in a buffer
int bytesTransferred = connection.bulkTransfer(endpointIn, buffer, buffer.length, 5000);
if (bytesTransferred > 0) {
if (//Checks against buffer content) {
processValidData(buffer);
} else {
handleInvalidData();
}
} else {
handleTransferError();
}
}
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: Bluetooth – tempo de detecção incorreto
Conforme destacado na documentação sobre Bluetooth para desenvolvedores Android, enquanto
configurando a interface Bluetooth dentro do aplicativo, usando o
Método startActivityForResult(Intent, int)
para ativar o dispositivo
a detecção do dispositivo e definir EXTRA_DISCOVERABLE_DURATION
como zero
fazer com que o dispositivo seja detectável enquanto o aplicativo estiver em execução
em segundo ou primeiro plano. Na especificação Bluetooth clássico, os dispositivos detectáveis transmitem constantemente mensagens de detecção
específicas que permitem que outros dispositivos extraiam dados do dispositivo ou se conectem a ele. Nesse
cenário, terceiros mal-intencionados podem interceptar essas mensagens e se conectar
ao dispositivo Android. Depois de conectado, um invasor pode realizar outros
ataques, como roubo de dados, DoS ou injeção de comando.
Mitigações
O EXTRA_DISCOVERABLE_DURATION
nunca pode ser definido como zero. Se o
EXTRA_DISCOVERABLE_DURATION
não estiver definido, por padrão, o Android faz
os dispositivos detectáveis por 2 minutos. O valor máximo que pode ser definido para o
O parâmetro EXTRA_DISCOVERABLE_DURATION
é de 2 horas (7.200 segundos). É
recomendado para manter a duração detectável no menor tempo possível
possível, de acordo com o caso de uso do aplicativo.
Risco: NFC: filtros de intent clonados
Um aplicativo malicioso pode registrar filtros de intent para ler tags NFC específicas ou dispositivos com NFC. Esses filtros podem replicar os definidos por um aplicativo legítimo, permitindo que um invasor leia o conteúdo dos dados NFC trocados. É importante notar que, quando duas atividades especificam os mesmos filtros de intent para uma tag NFC específica, o Seletor de atividade é apresentado, o usuário ainda precisa escolher para que o ataque seja bem-sucedido. No entanto, a combinação filtros de intent com técnicas de cloaking, esse cenário ainda é possível. Esse ataque é significativo apenas para casos em que os dados trocados por NFC podem ser considerados altamente sensíveis.
Mitigações
Ao implementar recursos de leitura de NFC em um aplicativo, os filtros de intent podem ser usadas com registros de aplicativos Android (AARs). A incorporação do registro AAR em uma mensagem NDEF garante que apenas o aplicativo legítimo e a atividade de processamento NDEF associada sejam iniciados. Isso impede que aplicativos ou atividades indesejados leiam dados de dispositivos ou tags altamente sensíveis trocados por NFC.
Risco: NFC: falta de validação de mensagens NDEF
Quando um dispositivo Android recebe dados de uma tag NFC ou de um dispositivo com NFC, o sistema aciona automaticamente o aplicativo ou a atividade específica que foi configurada para processar a mensagem NDEF contida. De acordo com a lógica implementada no aplicativo, os dados contidos na tag ou recebidos do dispositivo podem ser veiculados para outras atividades para acionar outras ações, como abrir páginas da Web.
Um aplicativo sem validação de conteúdo da mensagem NDEF pode permitir que os atacantes usar dispositivos com NFC ou etiquetas NFC para injetar payloads maliciosos dentro do aplicativo, causando um comportamento inesperado que pode resultar em por download, injeção de comando ou DoS.
Mitigações
Antes de enviar a mensagem NDEF recebida para qualquer outro componente do aplicativo, os dados precisam ser validados para estar no formato esperado e conter as informações esperadas. Isso evita que dados maliciosos sejam transmitidos para outros componentes de aplicativos sem filtragem, reduzindo o risco de comportamento inesperado ou ataques usando dados NFC adulterados.
O snippet a seguir mostra um exemplo de lógica de validação de dados implementada como um método com uma mensagem NDEF como argumento e o índice dela na matriz de mensagens. Isso foi implementado com base no exemplo dos desenvolvedores Android para coletar dados de um tag NDEF de NFC lida:
Kotlin
//The method takes as input an element from the received NDEF messages array
fun isValidNDEFMessage(messages: Array<NdefMessage>, index: Int): Boolean {
// Checks if the index is out of bounds
if (index < 0 || index >= messages.size) {
return false
}
val ndefMessage = messages[index]
// Retrieves the record from the NDEF message
for (record in ndefMessage.records) {
// Checks if the TNF is TNF_ABSOLUTE_URI (0x03), if the Length Type is 1
if (record.tnf == NdefRecord.TNF_ABSOLUTE_URI && record.type.size == 1) {
// Loads payload in a byte array
val payload = record.payload
// Declares the Magic Number that should be matched inside the payload
val gifMagicNumber = byteArrayOf(0x47, 0x49, 0x46, 0x38, 0x39, 0x61) // GIF89a
// Checks the Payload for the Magic Number
for (i in gifMagicNumber.indices) {
if (payload[i] != gifMagicNumber[i]) {
return false
}
}
// Checks that the Payload length is, at least, the length of the Magic Number + The Descriptor
if (payload.size == 13) {
return true
}
}
}
return false
}
Java
//The method takes as input an element from the received NDEF messages array
public boolean isValidNDEFMessage(NdefMessage[] messages, int index) {
//Checks if the index is out of bounds
if (index < 0 || index >= messages.length) {
return false;
}
NdefMessage ndefMessage = messages[index];
//Retrieve the record from the NDEF message
for (NdefRecord record : ndefMessage.getRecords()) {
//Check if the TNF is TNF_ABSOLUTE_URI (0x03), if the Length Type is 1
if ((record.getTnf() == NdefRecord.TNF_ABSOLUTE_URI) && (record.getType().length == 1)) {
//Loads payload in a byte array
byte[] payload = record.getPayload();
//Declares the Magic Number that should be matched inside the payload
byte[] gifMagicNumber = {0x47, 0x49, 0x46, 0x38, 0x39, 0x61}; // GIF89a
//Checks the Payload for the Magic Number
for (int i = 0; i < gifMagicNumber.length; i++) {
if (payload[i] != gifMagicNumber[i]) {
return false;
}
}
//Checks that the Payload length is, at least, the length of the Magic Number + The Descriptor
if (payload.length == 13) {
return true;
}
}
}
return false;
}
Recursos
- Permissões de tempo de execução
- Guias de conectividade
- Exemplo
- bulkTransfer
- Criptografia
- Configurar o Bluetooth
- Base para NFC
- Registros de aplicativos Android
- Especificação do Bluetooth Classic