Configuración no segura de la comunicación entre máquinas

Categoría de OWASP: MASVS-CODE: Calidad de código

Descripción general

No es raro ver aplicaciones que implementan funciones que permitan transferir datos o interactuar con otros dispositivos mediante radiofrecuencia (RF) comunicaciones o conexiones por cable. Las tecnologías más comunes que se usan en Android para este fin son Bluetooth clásico (Bluetooth BR/EDR), Bluetooth de bajo consumo (BLE), Wi-Fi P2P, NFC y USB.

Por lo general, estas tecnologías se implementan en aplicaciones comunicarse con accesorios de casa inteligente, dispositivos de supervisión de la salud, datos kioscos de transporte, terminales de pago y otros dispositivos con Android.

Al igual que con cualquier otro canal, las comunicaciones entre máquinas son susceptibles a ataques que apuntan a comprometer el límite de confianza establecido entre dos o más dispositivos. Las técnicas como la suplantación de identidad del dispositivo pueden ser aprovechadas por que los usuarios maliciosos logren una gran cantidad de ataques contra canal.

Android crea APIs específicas para configurar máquinas entre máquinas. comunicaciones disponibles para los desarrolladores.

Estas APIs deben usarse con cuidado, ya que los errores durante la implementación de protocolos de comunicación pueden provocar que los datos del usuario o del dispositivo se expongan a terceros no autorizados. En el peor de los casos, los atacantes podrían intentar que se apoderan de uno o más dispositivos y, por lo tanto, obtienen acceso completo al contenido en el dispositivo.

Impacto

El impacto puede variar según la tecnología de dispositivo a dispositivo que se implemente en la aplicación.

El uso o la configuración incorrectos de máquina a máquina canales de comunicación podría dejar el dispositivo del usuario expuesto intentos de comunicación. Esto puede provocar que el dispositivo sea vulnerable a como ataques de intermediario (MiTM), inyección de comandos, DoS de identidad.

Riesgo: Espionaje de datos sensibles a través de canales inalámbricos

Cuando se implementan mecanismos de comunicación máquina a máquina, se debe tener en cuenta la tecnología utilizada y el tipo de datos que se deben transmitir. Si bien las conexiones por cable son, en la práctica, más seguras para estas tareas, ya que requieren un vínculo físico entre los dispositivos involucrados, protocolos de comunicación que usan frecuencias de radio, como Bluetooth clásico, BLE, NFC y Wi-Fi P2P se pueden interceptar. Un atacante podría suplantar la identidad de uno de las terminales o puntos de acceso involucrados en el intercambio de datos, interceptar la comunicación inalámbrica y, por consiguiente, obtener acceso a usuarios de datos no estructurados. Además, si se les otorgan los permisos de tiempo de ejecución específicos para la comunicación a las aplicaciones maliciosas instaladas en el dispositivo, es posible que puedan recuperar los datos que se intercambian entre los dispositivos leyendo los búferes de mensajes del sistema.

Mitigaciones

Si la aplicación requiere el intercambio de datos sensibles entre máquinas a través de canales inalámbricos, se deben implementar soluciones de seguridad de la capa de aplicación, como la encriptación, en el código de la aplicación. Esto evitará que los atacantes sniffeen el canal de comunicación y recuperen los datos intercambiados en texto simple. Para obtener recursos adicionales, consulta el Documentación de criptografía.


Riesgo: Inserción inalámbrica de datos maliciosos

Los canales de comunicación inalámbrica entre máquinas (Bluetooth clásico, BLE, NFC y Wi-Fi P2P) se pueden manipular con datos maliciosos. Los atacantes con suficiente habilidad pueden identificar el protocolo de comunicación en uso y manipular el flujo de intercambio de datos, por ejemplo, robando la identidad de uno de los extremos y enviando cargas útiles diseñadas específicamente. Este tipo de tráfico malicioso puede degradar la funcionalidad de la aplicación y, en el peor de los casos, provocar un comportamiento inesperado de la aplicación y del dispositivo, o bien ataques como DoS, inyección de comandos o usurpación de dispositivos.

Mitigaciones

Android ofrece a los desarrolladores API potentes para administrar comunicaciones entre máquinas, como Bluetooth clásico, BLE, NFC y Wi-Fi P2P: Se deben combinar con una lógica de validación de datos implementada con cuidado para limpiar los datos que se intercambian entre dos dispositivos.

Esta solución debe implementarse a nivel de la aplicación y debe incluir que verifican si los datos tienen la longitud y el formato esperados y si contienen una carga útil válida que puede interpretar la aplicación.

En el siguiente fragmento, se muestra un ejemplo de lógica de validación de datos. Esto se implementó en el ejemplo de los desarrolladores de Android para implementar la transferencia de datos por Bluetooth:

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;
           }
    }

Riesgo: Inserción de datos maliciosos por USB

Un usuario malicioso que esté interesado en interceptar comunicaciones puede atacar las conexiones USB entre dos dispositivos. En este caso, el vínculo físico requerido constituye una capa de seguridad adicional, ya que el atacante debe obtener acceso al cable que conecta las terminales para poder escuchar cualquier mensaje. Otro vector de ataque está representado por dispositivos USB no confiables que, ya sea de forma intencional o no, se conectaron al dispositivo.

Si la aplicación filtra dispositivos USB con PID/VID para activar funciones específicas en la app, los atacantes pueden manipular los datos enviados a través del canal USB suplantando la identidad del dispositivo legítimo. Este tipo de ataques pueden permitir a los usuarios maliciosos enviar pulsaciones de teclas al dispositivo o ejecutar aplicaciones actividades que, en el peor de los casos, pueden llevar a la ejecución remota de código o descargas de software no deseado.

Mitigaciones

Se debe implementar una lógica de validación a nivel de la aplicación. Esta lógica debe filtrar los datos enviados por USB y verificar que la longitud, el formato y el contenido coincidan con el caso de uso de la aplicación. Por ejemplo, un monitor de frecuencia cardíaca no debería enviar comandos de pulsación de teclas.

Además, siempre que sea posible, se debe considerar restringir la cantidad de paquetes USB que la aplicación puede recibir del dispositivo USB. Esto evita que los dispositivos maliciosos realicen ataques como el Rubber Ducky.

Para realizar esta validación, crea un subproceso nuevo para verificar el contenido del búfer, por ejemplo, en un 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();
        }
    }

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: Bluetooth: Tiempo de visibilidad incorrecto

Como se destaca en la documentación de Bluetooth para desarrolladores de Android, mientras se configura la interfaz de Bluetooth en la aplicación, usar el método startActivityForResult(Intent, int) para habilitar la visibilidad del dispositivo y establecer EXTRA_DISCOVERABLE_DURATION en cero hará que el dispositivo sea detectable, siempre y cuando la aplicación se ejecute en primer o segundo plano. En cuanto a la especificación Bluetooth clásica, los dispositivos detectables transmiten constantemente mensajes de detección específicos que permiten que otros dispositivos recuperen datos del dispositivo o se conecten a él. En una situación como esta, un tercero malicioso puede interceptar esos mensajes y conectarse al dispositivo con Android. Una vez conectado, un atacante puede realizar otros ataques, como robo de datos, DoS o inserción de comandos.

Mitigaciones

EXTRA_DISCOVERABLE_DURATION nunca debe establecerse en cero. Si el botón El parámetro EXTRA_DISCOVERABLE_DURATION no está configurado. De forma predeterminada, Android crea los dispositivos sean detectables durante 2 minutos. El valor máximo que se puede establecer para el parámetro EXTRA_DISCOVERABLE_DURATION es de 2 horas (7,200 segundos). Se recomienda mantener el tiempo de duración detectable en el período más corto posible, según el caso de uso de la aplicación.


Riesgo: NFC: Filtros de intents clonados

Una aplicación maliciosa puede registrar filtros de intents para leer etiquetas NFC específicas o dispositivos compatibles con NFC. Estos filtros pueden replicar los definidos por una una aplicación legítima, lo que posibilita que un atacante lea el contenido de los datos NFC intercambiados. Cabe señalar que, cuando dos actividades especifican los mismos filtros de intents para una etiqueta NFC específica, se presenta el Selector de actividades, por lo que el usuario aún deberá elegir la aplicación maliciosa para que el ataque se realice correctamente. Sin embargo, combinar filtros de intenciones con encubrimiento, esta situación es posible. Este ataque es significativo solo para los casos en los que los datos intercambiados a través de NFC se puedan considerar muy sensibles.

Mitigaciones

Cuando se implementan capacidades de lectura de NFC en una aplicación, los filtros de intents se pueden usar junto con los registros de aplicación de Android (AAR). Incorporar el registro AAR dentro de un mensaje NDEF brindará una garantía sólida de que solo se inicie la aplicación legítima y su actividad de manejo de NDEF asociada. Esto evitará que aplicaciones o actividades no deseadas lean datos de etiquetas o dispositivos altamente sensibles que se intercambian a través de NFC.


Riesgo: NFC – falta de validación del mensaje NDEF

Cuando un dispositivo con tecnología Android recibe datos de una etiqueta NFC o de otro el sistema activa automáticamente la aplicación o la función que está configurada para manejar el mensaje NDEF contenido dentro. Según la lógica implementada en la app, los datos contenidos en la o recibidas del dispositivo, pueden publicarse en otras actividades para activar acciones adicionales, como abrir páginas web.

Una aplicación que no tenga validación de contenido de mensajes NDEF puede permitir que los atacantes usen dispositivos o etiquetas NFC compatibles con NFC para inyectar cargas útiles maliciosas en la aplicación, lo que causa un comportamiento inesperado que puede provocar la descarga de archivos maliciosos, la inyección de comandos o un DoS.

Mitigaciones

Antes de enviar el mensaje NDEF recibido a cualquier otro componente de la aplicación, se deben validar los datos para verificar que tengan el formato esperado y contengan la información esperada. Esto evita que los datos maliciosos pasen a otros aplicaciones' componentes sin filtros, lo que reduce el riesgo de comportamientos inesperados o con datos de NFC alterados.

En el siguiente fragmento, se muestra un ejemplo de lógica de validación de datos implementada como una con un mensaje NDEF como argumento y su índice en el array de mensajes. Esto se implementó en el ejemplo de los desarrolladores de Android para obtener datos de una etiqueta NDEF NFC escaneada:

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