Unsichere Einrichtung der Computer-zu-Computer-Kommunikation

OWASP-Kategorie: MASVS-CODE: Codequalität

Übersicht

Es ist keine Seltenheit, dass Apps Funktionen implementieren, mit denen Nutzer Daten per Funkschnittstelle oder Kabelverbindung übertragen oder mit anderen Geräten interagieren können. Die gängigsten Technologien, die in Android zu diesem Zweck verwendet werden, sind klassisches Bluetooth (Bluetooth BR/EDR), Bluetooth Low Energy (BLE), Wifi P2P, NFC und USB.

Diese Technologien werden in der Regel in Anwendungen implementiert, die mit Smart-Home-Zubehör, Gesundheitsüberwachungsgeräten, Kiosken für öffentliche Verkehrsmittel, Zahlungsterminals und anderen Android-Geräten kommunizieren sollen.

Wie bei jedem anderen Kanal ist auch die Kommunikation zwischen Maschinen anfällig für Angriffe, die darauf abzielen, die Vertrauensgrenze zwischen zwei oder mehr Geräten zu schwächen. Böswillige Nutzer können mithilfe von Techniken wie der Geräteidentitätsdiebstahl eine Vielzahl von Angriffen auf den Kommunikationskanal ausführen.

Android erstellt spezifische APIs zum Konfigurieren von Gerät zu Gerät. Mitteilungen für Entwickler.

Diese APIs sollten mit Bedacht verwendet werden, da Fehler bei der Implementierung von Kommunikationsprotokollen dazu führen können, dass Nutzer- oder Gerätedaten für unbefugte Dritte freigegeben werden. Im schlimmsten Fall können Angreifer ein oder mehrere Geräte übernehmen, um vollen Zugriff auf die Inhalte zu erhalten, auf dem Gerät.

Positiv beeinflussen

Die Auswirkungen können je nach in der Anwendung implementierter Technologie für die Gerätekommunikation variieren.

Falsche Nutzung oder Konfiguration von Maschine zu Maschine Kommunikationskanäle können dazu führen, dass das Nutzergerät nicht vertrauenswürdig ist. Kommunikationsversuche. Das kann dazu führen, dass das Gerät anfällig für zusätzliche Angriffe wie Man-in-the-Middle-Angriffe (MiTM), Befehlseinschleusung, DoS- oder Identitätsdiebstahl-Angriffe ist.

Risiko: Ausspionieren sensibler Daten über Funkkanäle

Achten Sie bei der Implementierung von Mechanismen der Maschine-zu-Maschine-Kommunikation darauf, sollten sowohl die verwendete Technologie als auch die Art der Daten berücksichtigt werden. die zu übertragen sind. Kabelverbindungen sind in der Praxis sicherer da eine physische Verbindung zwischen den beteiligten Geräten erforderlich ist. mit Funkfrequenzen wie klassischem Bluetooth, BLE, NFC und Wifi P2P können abgefangen werden. Ein Angreifer kann sich als eines der Terminals oder Zugangspunkte ausgeben, die am Datenaustausch beteiligt sind, die Kommunikation abfangen und so Zugriff auf sensible Nutzerdaten erhalten. Darüber hinaus werden schädliche Apps auf dem Gerät installiert, sofern diese die kommunikationsspezifischen Laufzeitberechtigungen, die Sie möglicherweise zwischen den Geräten ausgetauschte Daten durch Lesen von Systemnachrichtenpuffern.

Abhilfemaßnahmen

Ob die Anwendung sensible Daten zwischen Rechnern austauschen muss über WLAN-Kanäle und dann Sicherheitslösungen auf Anwendungsebene, Verschlüsselung, sollte im Code der Anwendung implementiert werden. So wird verhindert, dass Angreifer den Kommunikationskanal anzapfen und die ausgetauschten Daten im Klartext abrufen. Zusätzliche Ressourcen finden Sie in der Dokumentation zur Kryptografie


Risiko: Kabellose Einschleusung schädlicher Daten

Drahtlose Machine-to-Machine-Kommunikationskanäle (klassisches Bluetooth, BLE, NFC, Wifi P2P) können mit schädlichen Daten manipuliert werden. Ausreichend kompetent das verwendete Kommunikationsprotokoll identifizieren und der Datenaustausch, z. B. durch die Nachahmung eines der Endpunkte, Senden entwickelten Nutzlasten. Diese Art von schädlichem Traffic kann und im schlimmsten Fall unerwartete Folgen haben, das Verhalten von Anwendungen und Geräten oder Angriffe wie DoS, Injektion oder Geräteübernahme.

Abwehrmaßnahmen

Android bietet Entwicklern leistungsfähige APIs für die Verwaltung. Kommunikation zwischen Geräten wie klassisches Bluetooth, BLE, NFC und WLAN P2P. Diese sollten mit einer sorgfältig implementierten Datenvalidierungslogik kombiniert werden. um zwischen zwei Geräten ausgetauschte Daten zu bereinigen.

Diese Lösung sollte auf Anwendungsebene implementiert werden und Prüfungen umfassen, die prüfen, ob die Daten die erwartete Länge und das erwartete Format haben und eine gültige Nutzlast enthalten, die von der Anwendung interpretiert werden kann.

Das folgende Snippet zeigt eine Beispiellogik für die Datenvalidierung. Dies wurde anhand des Beispiels der Android-Entwickler zur Implementierung der Bluetooth-Datenübertragung implementiert:

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

Risiko: Einschleusung schädlicher Daten über USB

USB-Verbindungen zwischen zwei Geräten können von böswilligen Nutzern angegriffen werden die Kommunikation abzufangen ist. In diesem Fall ist der physische Link stellt eine zusätzliche Sicherheitsebene dar, da der Angreifer Zugang zum Kabel, mit dem die Klemmen verbunden sind, damit man alles mithören kann. . Ein weiterer Angriffsvektor sind nicht vertrauenswürdige USB-Geräte, die entweder absichtlich oder versehentlich an das Gerät angeschlossen werden.

Wenn die Anwendung USB-Geräte mithilfe von PID/VID filtert, um bestimmte In-App-Funktionen auszulösen, können Angreifer die über den USB-Kanal gesendeten Daten manipulieren, indem sie sich als legitimes Gerät ausgeben. Bei solchen Angriffen können böswillige Nutzer Tastenanschläge an das Gerät senden oder Anwendungsaktivitäten ausführen, die im schlimmsten Fall zur Remote-Codeausführung oder zum Download unerwünschter Software führen können.

Abwehrmaßnahmen

Es sollte eine Validierungslogik auf Anwendungsebene implementiert werden. Diese Logik sollte Die über USB gesendeten Daten filtern und überprüfen, ob Länge, Format und Inhalt zum Anwendungsfall passen. Ein Heartbeat-Tracker sollte z. B. nicht in der Lage ist, Tastaturbefehle zu senden.

Außerdem sollte nach Möglichkeit die Anzahl der USB-Pakete eingeschränkt werden, die die Anwendung vom USB-Gerät empfangen kann. Dieses verhindert, dass schädliche Geräte wie eine Gummi-Ducky-Funktion Angriffe ausführen.

Diese Validierung kann durch Erstellen eines neuen Threads zum Prüfen des Pufferinhalts erfolgen, z. B. bei einem 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();
        }
    }

Spezifische Risiken

In diesem Abschnitt finden Sie Risiken, die nicht standardmäßige Risikominderungsstrategien erfordern oder auf einer bestimmten SDK-Ebene gemindert wurden. Er dient der Vollständigkeit.

Risiko: Bluetooth – falsche Zeit für die Sichtbarkeit

Wie in der Bluetooth-Dokumentation für Android-Entwickler erwähnt, Konfigurieren der Bluetooth-Oberfläche in der Anwendung mithilfe der Methode startActivityForResult(Intent, int) zum Aktivieren des Geräts und wenn EXTRA_DISCOVERABLE_DURATION auf null gesetzt wird, führen dazu, dass das Gerät sichtbar ist, solange die App entweder im Hintergrund oder im Vordergrund. Gemäß der klassischen Bluetooth-Spezifikation senden Geräte mit sichtbarer Sichtbarkeit ständig bestimmte Meldungen Nachrichten, mit denen andere Geräte Gerätedaten abrufen oder sich mit ihnen verbinden können. In könnte ein böswilliger Drittanbieter diese Nachrichten abfangen und eine Verbindung auf das Android-Gerät. Sobald die Verbindung hergestellt ist, kann ein Angreifer wie Datendiebstahl, DoS oder Command Injection.

Abwehrmaßnahmen

EXTRA_DISCOVERABLE_DURATION sollte niemals auf null gesetzt werden. Wenn der Parameter EXTRA_DISCOVERABLE_DURATION-Parameter nicht festgelegt ist, erstellt Android die Geräte 2 Minuten lang sichtbar sind. Der Maximalwert, der für den Der Parameter EXTRA_DISCOVERABLE_DURATION beträgt 2 Stunden (7.200 Sekunden). Wir empfehlen, die Dauer der Sichtbarkeit gemäß dem Anwendungsfall so kurz wie möglich zu halten.


Risiko: NFC – geklonte Intent-Filter

Schädliche Anwendungen können Intent-Filter registrieren, um bestimmte NFC-Tags zu lesen. NFC-fähige Geräte. Diese Filter können die von einer legitimen Anwendung definierten Filter nachahmen, sodass ein Angreifer den Inhalt der ausgetauschten NFC-Daten lesen kann. Wenn zwei Aktivitäten Intent-Filter für ein bestimmtes NFC-Tag enthält, ist die Aktivitätsauswahl angezeigt wird, muss der Nutzer trotzdem die schädliche App auswählen, damit der Angriff erfolgreich ist. Wenn Sie jedoch Intent-Filter mit Cloaking kombinieren, ist dieses Szenario weiterhin möglich. Dieser Angriff ist nur dann von Bedeutung, wenn die über NFC ausgetauschten Daten als hochsensibel eingestuft werden können.

Abwehrmaßnahmen

Wenn Sie NFC-Lesefunktionen in einer Anwendung implementieren, können Intent-Filter zusammen mit Android-Anwendungseinträgen (AARs) verwendet werden. Einbetten des Ein AAR-Eintrag in einer NDEF-Nachricht bietet eine deutliche Gewissheit, dass nur der legitime Anwendung und die damit verbundenen NDEF-Verarbeitungsaktivitäten gestartet werden. Dadurch wird verhindert, dass unerwünschte Anwendungen oder Aktivitäten Daten stark lesen. werden sensible Tags oder Gerätedaten über NFC ausgetauscht.


Risiko: NFC – fehlende Validierung von NDEF-Nachrichten

Wenn ein Android-Gerät Daten von einem NFC-Tag oder einem NFC-fähigen Gerät empfängt verwendet, löst das System die Anwendung oder die spezifische Aktivität, die für die Verarbeitung der darin enthaltenen NDEF-Nachricht konfiguriert ist. Gemäß der in der Anwendung implementierten Logik werden die in der oder vom Gerät empfangen, an andere Aktivitäten gesendet werden, weitere Aktionen durchführen, wie z. B. Webseiten öffnen.

Bei einer Anwendung ohne Validierung des NDEF-Nachrichteninhalts können Angreifer NFC-fähige Geräte oder NFC-Tags verwenden, um schädliche Nutzlasten in die Anwendung einzuschleusen. Dies kann zu unerwartetem Verhalten führen, das zum Download schädlicher Dateien, zur Befehlseinschleusung oder zu einem DoS-Angriff führen kann.

Abwehrmaßnahmen

Bevor Sie die empfangene NDEF-Nachricht an eine andere Anwendungskomponente senden, -Daten sollten auf das erwartete Format und den erwartete Informationen. So wird verhindert, dass schädliche Daten an andere Anwendungen Komponenten ungefiltert sind und so das Risiko unerwarteten Verhaltens mit manipulierten NFC-Daten.

Im folgenden Snippet wird eine Beispiellogik zur Datenvalidierung als Methode mit einer NDEF-Nachricht als Argument und ihrem Index im Nachrichtenarray implementiert. Dies wurde anhand des Beispiels für Android-Entwickler implementiert, um Daten aus einem gescannten NFC-NDEF-Tag abzurufen:

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

Ressourcen