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

Категория 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. Их следует сочетать с тщательно реализованной логикой проверки данных для очистки любых данных, которыми обмениваются два устройства.

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

В следующем фрагменте показан пример логики проверки данных. Это было реализовано на примере реализации передачи данных Bluetooth разработчиками Android:

Котлин

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

Ява

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-устройства. Это не позволяет вредоносным устройствам выполнять такие атаки, как «резиновая уточка».

Эту проверку можно выполнить, создав новый поток для проверки содержимого буфера, например, при 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()
    }
}

Ява

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 – неправильное время обнаружения.

Как указано в документации Bluetooth для разработчиков Android , при настройке интерфейса 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 или теги NFC для внедрения вредоносных полезных данных в приложение, вызывая неожиданное поведение, которое может привести к загрузке вредоносного файла, внедрению команд или DoS.

Смягчения

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

В следующем фрагменте показан пример логики проверки данных, реализованной в виде метода с сообщением NDEF в качестве аргумента и его индексом в массиве сообщений. Это было реализовано на примере разработчиков 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
}

Ява

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

Ресурсы