Небезопасная настройка межмашинной связи

Категория OWASP: MASVS-CODE: Качество кода

Обзор

Нередко встречаются приложения, реализующие функциональность, позволяющую пользователям передавать данные или взаимодействовать с другими устройствами с помощью радиочастотной (РЧ) связи или проводных соединений. Наиболее распространенными технологиями, используемыми в Android для этих целей, являются классический Bluetooth (Bluetooth BR/EDR), Bluetooth Low Energy (BLE), Wi-Fi P2P, NFC и USB.

Эти технологии обычно используются в приложениях, предназначенных для взаимодействия с устройствами умного дома, приборами для мониторинга здоровья, киосками в общественном транспорте, платежными терминалами и другими устройствами на базе Android.

Как и любой другой канал связи, межмашинная связь подвержена атакам, направленным на нарушение доверительных отношений между двумя или более устройствами. Злоумышленники могут использовать такие методы, как подмена устройства, для осуществления широкого спектра атак на канал связи.

Android предоставляет разработчикам специальные API для настройки межмашинного взаимодействия.

Использование этих API следует осуществлять с осторожностью, поскольку ошибки при реализации протоколов связи могут привести к утечке данных пользователей или устройств к неавторизованным третьим лицам. В худшем случае злоумышленники могут удаленно захватить одно или несколько устройств, получив, таким образом, полный доступ к содержимому устройства.

Влияние

Влияние может варьироваться в зависимости от технологии взаимодействия между устройствами, используемой в приложении.

Неправильное использование или настройка каналов связи между машинами может сделать пользовательское устройство уязвимым для попыток связи с недоверенными устройствами. Это может привести к тому, что устройство станет уязвимым для дополнительных атак, таких как атака типа «человек посередине» (MiTM), внедрение команд, DoS-атака или атака с подменой личности.

Риск: Перехват конфиденциальных данных по беспроводным каналам связи.

При внедрении механизмов межмашинной связи следует тщательно учитывать как используемую технологию, так и тип передаваемых данных. Хотя проводные соединения на практике более безопасны для таких задач, поскольку требуют физической связи между участвующими устройствами, протоколы связи, использующие радиочастоты, такие как классический Bluetooth, BLE, NFC и Wi-Fi P2P, могут быть перехвачены. Злоумышленник может выдать себя за один из терминалов или точек доступа, участвующих в обмене данными, перехватывая связь по беспроводной сети и, следовательно, получая доступ к конфиденциальным данным пользователей. Кроме того, вредоносные приложения, установленные на устройстве, при наличии соответствующих разрешений во время выполнения , могут получить доступ к данным, которыми обмениваются устройства, путем чтения системных буферов сообщений.

Меры по смягчению последствий

Если приложение требует обмена конфиденциальными данными между машинами по беспроводным каналам, то в код приложения следует внедрить решения по обеспечению безопасности на уровне приложения, такие как шифрование. Это предотвратит перехват данных в канале связи злоумышленниками и получение передаваемых данных в открытом виде. Дополнительные ресурсы см. в документации по криптографии .


Риск: Беспроводная инъекция вредоносных данных

Беспроводные каналы связи между машинами (классический Bluetooth, BLE, NFC, Wi-Fi P2P) могут быть взломаны с помощью вредоносных данных. Достаточно опытные злоумышленники могут определить используемый протокол связи и изменить поток обмена данными, например, выдав себя за одну из конечных точек и отправив специально созданные полезные нагрузки. Такой вредоносный трафик может ухудшить функциональность приложения и, в худшем случае, вызвать неожиданное поведение приложения и устройства, или привести к таким атакам, как DoS-атака, внедрение команд или захват устройства.

Меры по смягчению последствий

Android предоставляет разработчикам мощные API для управления межмашинной связью, например, с помощью классического Bluetooth, BLE, NFC и Wi-Fi P2P. Их следует сочетать с тщательно разработанной логикой проверки данных для обеспечения чистоты любых данных, которыми обмениваются два устройства.

Данное решение следует реализовать на уровне приложения и включить проверки, подтверждающие, что данные имеют ожидаемую длину, формат и содержат допустимую полезную нагрузку, которая может быть интерпретирована приложением.

Следующий фрагмент кода демонстрирует пример логики проверки данных. Он был реализован на основе примера для разработчиков Android, демонстрирующего передачу данных по Bluetooth:

Котлин

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

Риск: внедрение вредоносных данных через USB-накопитель.

USB-соединения между двумя устройствами могут стать целью злоумышленника, заинтересованного в перехвате сообщений. В этом случае физическое соединение представляет собой дополнительный уровень безопасности, поскольку злоумышленнику необходимо получить доступ к кабелю, соединяющему терминалы, чтобы иметь возможность перехватывать любые сообщения. Другой вектор атаки представляют собой ненадежные USB-устройства, которые, намеренно или непреднамеренно, подключаются к устройству.

Если приложение фильтрует USB-устройства по PID/VID для запуска определенных функций приложения, злоумышленники могут изменить данные, передаваемые по USB-каналу, выдавая себя за легитимное устройство. Подобные атаки могут позволить злоумышленникам отправлять нажатия клавиш на устройство или выполнять действия приложения, которые в худшем случае могут привести к удаленному выполнению кода или нежелательной загрузке программного обеспечения.

Меры по смягчению последствий

Необходимо реализовать логику проверки на уровне приложения. Эта логика должна фильтровать данные, передаваемые по USB, проверяя, соответствуют ли длина, формат и содержимое сценарию использования приложения. Например, монитор сердечного ритма не должен иметь возможность отправлять команды нажатия клавиш.

Кроме того, по возможности следует ограничить количество USB-пакетов, которые приложение может получать от USB-устройства. Это предотвратит атаки со стороны вредоносных устройств, таких как Rubber Ducky.

Проверку можно выполнить, создав новый поток для проверки содержимого буфера, например, при выполнении операции bulkTransfer :

Котлин

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

Специфические риски

В этом разделе собраны риски, требующие нестандартных стратегий смягчения или которые были смягчены на определенном уровне SDK, и они приведены здесь для полноты информации.

Риск: Bluetooth – некорректное время обнаружения

Как указано в документации разработчиков Android по Bluetooth , при настройке интерфейса Bluetooth в приложении использование метода startActivityForResult(Intent, int) для включения обнаружения устройства и установка параметра EXTRA_DISCOVERABLE_DURATION в ноль приведет к тому, что устройство будет обнаруживаться до тех пор, пока приложение работает в фоновом или активном режиме. В соответствии с классической спецификацией Bluetooth , обнаруживаемые устройства постоянно передают определенные сообщения обнаружения, которые позволяют другим устройствам получать данные устройства или подключаться к нему. В таком сценарии злоумышленник может перехватить эти сообщения и подключиться к устройству на базе Android. После подключения злоумышленник может совершать дальнейшие атаки, такие как кража данных, DoS-атака или внедрение команд.

Меры по смягчению последствий

Параметр EXTRA_DISCOVERABLE_DURATION никогда не следует устанавливать равным нулю. Если параметр EXTRA_DISCOVERABLE_DURATION не задан, по умолчанию Android делает устройства доступными для обнаружения в течение 2 минут. Максимальное значение, которое можно установить для параметра EXTRA_DISCOVERABLE_DURATION составляет 2 часа (7200 секунд). Рекомендуется устанавливать время доступности для обнаружения как можно короче, в зависимости от сценария использования приложения.


Риск: NFC – клонированные фильтры намерений

Вредоносное приложение может зарегистрировать фильтры намерений для считывания определенных NFC-меток или устройств с поддержкой NFC. Эти фильтры могут дублировать фильтры, определенные легитимным приложением, что позволяет злоумышленнику считывать содержимое передаваемых NFC-данных. Следует отметить, что, если два приложения указывают одни и те же фильтры намерений для определенной NFC-метки, отображается окно выбора приложения , поэтому пользователю все равно потребуется выбрать вредоносное приложение для успешной атаки. Тем не менее, сочетание фильтров намерений с маскировкой делает этот сценарий возможным. Эта атака значима только в случаях, когда данные, передаваемые по NFC, могут считаться крайне конфиденциальными.

Меры по смягчению последствий

При реализации возможностей считывания NFC в приложении можно использовать фильтры намерений вместе с записями приложений Android (AAR). Встраивание записи AAR в сообщение NDEF обеспечит надежную гарантию того, что запускается только легитимное приложение и связанная с ним активность обработки NDEF. Это предотвратит считывание нежелательными приложениями или действиями конфиденциальных данных с меток или устройств, передаваемых через NFC.


Риск: NFC – отсутствие проверки подлинности сообщений NDEF.

Когда устройство под управлением Android получает данные от NFC-метки или устройства с поддержкой NFC, система автоматически запускает приложение или конкретное действие, настроенное для обработки содержащегося в ней сообщения NDEF. В соответствии с логикой, реализованной в приложении, данные, содержащиеся в метке или полученные от устройства, могут быть переданы другим действиям для запуска дальнейших операций, таких как открытие веб-страниц.

Приложение, не поддерживающее проверку содержимого сообщений NDEF, может позволить злоумышленникам использовать устройства или метки NFC для внедрения вредоносных программ в приложение, что приведет к непредсказуемому поведению, которое может вызвать загрузку вредоносных файлов, внедрение команд или DoS-атаку.

Меры по смягчению последствий

Перед отправкой полученного сообщения NDEF любому другому компоненту приложения необходимо проверить данные на соответствие ожидаемому формату и содержанию необходимой информации. Это предотвратит передачу вредоносных данных в другие компоненты приложений без фильтрации, снижая риск непредвиденного поведения или атак с использованием поддельных данных NFC.

В следующем фрагменте кода показана логика проверки данных, реализованная в виде метода, принимающего в качестве аргумента сообщение NDEF и его индекс в массиве messages. Эта реализация основана на примере для разработчиков Android, позволяющем получать данные со сканированного NFC-метки NDEF:

Котлин

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

Ресурсы