OWASP-Kategorie:MASVS-CODE: Code Quality
Übersicht
Es ist nicht ungewöhnlich, dass Anwendungen Funktionen implementieren, mit denen Nutzer Daten übertragen oder über Funkfrequenzkommunikation oder Kabelverbindungen mit anderen Geräten interagieren können. Die gängigsten Technologien, die in Android für diesen Zweck verwendet werden, sind Classic Bluetooth (Bluetooth BR/EDR), Bluetooth Low Energy (BLE), WLAN P2P, NFC und USB.
Diese Technologien werden in der Regel in Apps implementiert, die mit Smart-Home-Zubehör, Geräten zur Gesundheitsüberwachung, Kiosken für öffentliche Verkehrsmittel, Zahlungsterminals und anderen Android-Geräten kommunizieren sollen.
Wie bei jedem anderen Kanal sind auch die Machine-to-Machine-Kommunikation anfällig für Angriffe, die darauf abzielen, die zwischen zwei oder mehr Geräten eingerichtete Vertrauensgrenze zu gefährden. Techniken wie die Geräte-Imitation können von böswilligen Nutzern eingesetzt werden, um eine Vielzahl von Angriffen auf den Kommunikationskanal zu starten.
Android stellt Entwicklern bestimmte APIs zur Konfiguration der Machine-to-Machine-Kommunikation zur Verfügung.
Diese APIs sollten mit Bedacht verwendet werden, da Fehler bei der Implementierung von Kommunikationsprotokollen dazu führen können, dass Nutzer- oder Gerätedaten unbefugten Dritten zugänglich gemacht werden. Im schlimmsten Fall können Angreifer ein oder mehrere Geräte per Fernzugriff übernehmen und so vollen Zugriff auf die Inhalte auf dem Gerät erhalten.
Auswirkungen
Die Auswirkungen können je nach der in der Anwendung implementierten Gerät-zu-Gerät-Technologie variieren.
Die falsche Verwendung oder Konfiguration von Machine-to-Machine-Kommunikationskanälen kann dazu führen, dass das Nutzergerät nicht vertrauenswürdigen Kommunikationsversuchen ausgesetzt ist. Dies kann dazu führen, dass das Gerät anfällig für zusätzliche Angriffe wie Man-in-the-Middle-Angriffe (MiTM), Command Injection, DoS-Angriffe oder Identitätsdiebstahl wird.
Risiko: Abhören sensibler Daten über drahtlose Kanäle
Bei der Implementierung von Mechanismen für die Machine-to-Machine-Kommunikation sollten sowohl die verwendete Technologie als auch die Art der zu übertragenden Daten sorgfältig berücksichtigt werden. Kabelverbindungen sind für solche Aufgaben in der Praxis sicherer, da sie eine physische Verbindung zwischen den beteiligten Geräten erfordern. Kommunikationsprotokolle, die Funkfrequenzen verwenden, wie z. B. klassisches Bluetooth, BLE, NFC und Wifi P2P, können jedoch abgefangen werden. Ein Angreifer kann möglicherweise die Identität eines der am Datenaustausch beteiligten Terminals oder Zugriffspunkte übernehmen, die Kommunikation über die Luftschnittstelle abfangen und so auf vertrauliche Nutzerdaten zugreifen. Außerdem können schädliche Anwendungen, die auf dem Gerät installiert sind, möglicherweise Daten abrufen, die zwischen den Geräten ausgetauscht werden, indem sie Systemnachrichtenpuffer lesen, wenn ihnen die kommunikationsspezifischen Laufzeitberechtigungen gewährt wurden.
Gegenmaßnahmen
Wenn die Anwendung einen Machine-to-Machine-Austausch sensibler Daten über drahtlose Kanäle erfordert, sollten Sicherheitstools auf Anwendungsebene wie die Verschlüsselung im Code der Anwendung implementiert werden. So wird verhindert, dass Angreifer den Kommunikationskanal abhören und die ausgetauschten Daten im Klartext abrufen. Weitere Informationen finden Sie in der Dokumentation zum Thema Kryptografie.
Risiko: Einschleusung schädlicher Daten über WLAN
Drahtlose Kanäle für die Machine-to-Machine-Kommunikation (klassisches Bluetooth, BLE, NFC, Wifi P2P) können durch schädliche Daten manipuliert werden. Angreifer mit ausreichenden Kenntnissen können das verwendete Kommunikationsprotokoll identifizieren und den Datenaustausch manipulieren, indem sie sich beispielsweise als einer der Endpunkte ausgeben und speziell entwickelte Nutzlasten senden. Diese Art von schädlichem Traffic kann die Funktionalität der App beeinträchtigen und im schlimmsten Fall zu unerwartetem App- und Geräteverhalten oder zu Angriffen wie DoS, Command Injection oder Geräteübernahme führen.
Gegenmaßnahmen
Android bietet Entwicklern leistungsstarke APIs zur Verwaltung von Machine-to-Machine-Kommunikation wie Classic Bluetooth, BLE, NFC und WLAN P2P. Diese sollten mit einer sorgfältig implementierten Datenvalidierungslogik kombiniert werden, um alle Daten zu bereinigen, die zwischen zwei Geräten ausgetauscht werden.
Diese Lösung sollte auf Anwendungsebene implementiert werden und Prüfungen enthalten, mit denen sich feststellen lässt, 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 ein Beispiel für die Logik zur Datenvalidierung. Die Implementierung erfolgte anhand des Beispiels für die Implementierung der Bluetooth-Datenübertragung für Android-Entwickler:
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: Böswillige Dateninjektion über USB
USB-Verbindungen zwischen zwei Geräten können von einem böswilligen Nutzer angegriffen werden, der die Kommunikation abfangen möchte. In diesem Fall stellt die erforderliche physische Verbindung eine zusätzliche Sicherheitsebene dar, da der Angreifer Zugriff auf das Kabel benötigt, das die Terminals verbindet, um Nachrichten abfangen zu können. Ein weiterer Angriffsvektor sind nicht vertrauenswürdige USB-Geräte, die absichtlich oder unabsichtlich 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 möglicherweise die über den USB-Kanal gesendeten Daten manipulieren, indem sie das legitime Gerät nachahmen. Angriffe dieser Art können es böswilligen Nutzern ermöglichen, Tasteneingaben an das Gerät zu senden oder Anwendungsaktivitäten auszuführen, die im schlimmsten Fall zur Remote-Codeausführung oder zum unerwünschten Software-Download führen können.
Gegenmaßnahmen
Es sollte eine Validierungslogik auf Anwendungsebene implementiert werden. Diese Logik sollte die über USB gesendeten Daten filtern und prüfen, ob Länge, Format und Inhalt dem Anwendungsfall entsprechen. Ein Herzfrequenzmesser sollte beispielsweise keine Tastaturbefehle senden können.
Außerdem sollte nach Möglichkeit die Anzahl der USB-Pakete, die die Anwendung vom USB-Gerät empfangen kann, eingeschränkt werden. So wird verhindert, dass schädliche Geräte Angriffe wie den Rubber Ducky-Angriff 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 werden Risiken zusammengefasst, für die nicht standardmäßige Maßnahmen erforderlich sind oder die auf einer bestimmten SDK-Ebene gemindert wurden und hier der Vollständigkeit halber aufgeführt sind.
Risiko: Bluetooth – falsche Sichtbarkeitsdauer
Wie in der Bluetooth-Dokumentation für Android-Entwickler beschrieben, wird das Gerät durch die Konfiguration der Bluetooth-Schnittstelle in der Anwendung mit der Methode startActivityForResult(Intent, int) zum Aktivieren der Geräteerkennung und durch das Festlegen von EXTRA_DISCOVERABLE_DURATION auf null so lange erkannt, wie die Anwendung im Hintergrund oder Vordergrund ausgeführt wird. Gemäß der Classic Bluetooth-Spezifikation senden erkennbare Geräte ständig bestimmte Erkennungsnachrichten, die es anderen Geräten ermöglichen, Gerätedaten abzurufen oder eine Verbindung zu ihnen herzustellen. In einem solchen Fall kann ein böswilliger Dritter diese Nachrichten abfangen und eine Verbindung zum Android-Gerät herstellen. Sobald eine Verbindung hergestellt ist, kann ein Angreifer weitere Angriffe wie Datendiebstahl, DoS oder Befehlsinjektion durchführen.
Gegenmaßnahmen
EXTRA_DISCOVERABLE_DURATION sollte niemals auf null gesetzt werden. Wenn der Parameter EXTRA_DISCOVERABLE_DURATION nicht festgelegt ist, werden Geräte unter Android standardmäßig 2 Minuten lang erkannt. Der Höchstwert, der für den Parameter EXTRA_DISCOVERABLE_DURATION festgelegt werden kann, beträgt 2 Stunden (7.200 Sekunden). Es wird empfohlen, die Dauer der Auffindbarkeit entsprechend dem Anwendungsfall der App so kurz wie möglich zu halten.
Risiko: NFC – geklonte Intent-Filter
Eine schädliche Anwendung kann Intent-Filter registrieren, um bestimmte NFC-Tags oder NFC-fähige Geräte zu lesen. Diese Filter können die Filter einer legitimen Anwendung replizieren, sodass ein Angreifer den Inhalt der ausgetauschten NFC-Daten lesen kann. Wenn zwei Aktivitäten dieselben Intent-Filter für ein bestimmtes NFC-Tag angeben, wird die Aktivitätsauswahl angezeigt. Der Nutzer muss also die schädliche Anwendung auswählen, damit der Angriff erfolgreich ist. Durch die Kombination von Intent-Filtern mit Cloaking ist dieses Szenario jedoch weiterhin möglich. Dieser Angriff ist nur in Fällen von Bedeutung, in denen die über NFC ausgetauschten Daten als hochsensibel eingestuft werden können.
Gegenmaßnahmen
Wenn Sie NFC-Lesefunktionen in einer Android-App implementieren, können Intent-Filter zusammen mit Android-Anwendungsdatensätzen (AARs) verwendet werden. Durch das Einbetten des AAR-Eintrags in eine NDEF-Nachricht wird sichergestellt, dass nur die legitime Anwendung und die zugehörige NDEF-Verarbeitungsaktivität gestartet werden. So wird verhindert, dass unerwünschte Anwendungen oder Aktivitäten hochsensible Tag- oder Gerätedaten lesen, die über NFC ausgetauscht werden.
Risiko: NFC – fehlende NDEF-Nachrichtenvalidierung
Wenn ein Android-Gerät Daten von einem NFC-Tag oder einem NFC-fähigen Gerät empfängt, wird automatisch die Anwendung oder die bestimmte Aktivität gestartet, die für die Verarbeitung der darin enthaltenen NDEF-Nachricht konfiguriert ist. Gemäß der in der Anwendung implementierten Logik können die im Tag enthaltenen oder vom Gerät empfangenen Daten an andere Aktivitäten weitergegeben werden, um weitere Aktionen auszulösen, z. B. das Öffnen von Webseiten.
Wenn eine Anwendung keine Validierung des NDEF-Nachrichteninhalts durchführt, 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 zu einem Download schädlicher Dateien, einer Command Injection oder einem DoS-Angriff führen kann.
Gegenmaßnahmen
Bevor die empfangene NDEF-Nachricht an eine andere Anwendungskomponente gesendet wird, sollten die darin enthaltenen Daten auf das erwartete Format und die erwarteten Informationen hin validiert werden. So wird verhindert, dass schädliche Daten ungefiltert an die Komponenten anderer Anwendungen weitergegeben werden. Das Risiko von unerwartetem Verhalten oder Angriffen mit manipulierten NFC-Daten wird dadurch verringert.
Im folgenden Snippet sehen Sie ein Beispiel für die Datenvalidierungslogik, die als Methode mit einer NDEF-Nachricht als Argument und ihrem Index im Nachrichtenarray implementiert wird. Dies wurde anhand des Beispiels für Android-Entwickler implementiert, um Daten von 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
- Laufzeitberechtigungen
- Leitfäden zur Konnektivität
- Beispiel
- bulkTransfer
- Kryptografie
- Bluetooth einrichten
- NFC-Grundlagen
- Einträge für Android-Apps
- Classic Bluetooth-Spezifikation